chahinebrini 5fb441817f feat(magic): RE-hardening Quick Wins (ACL, #if DEBUG guards, rate-limit)
Härtung der öffentlich downloadbaren Magic-Apps gegen Reverse Engineering
(Assessment: docs/specs/magic-re-hardening.md):
- Windows: protection.json per ACL auf SYSTEM+Admins (DNS-Token nicht mehr von
  Standard-Usern lesbar) — setup.rs
- Mac: MacProfileInstaller.remove() + Debug-Supervision-Modi/Reset nur noch
  #if DEBUG (kein Removal-/Debug-Pfad im Release-Binary)
- Mac: staging-URL einmal als Konstante statt 4x Literal; interne Infra-Notizen
  aus String-Literalen raus
- Backend: Rate-Limit (10/IP/min) auf /api/magic/pair/redeem

NUR Backend-Teil deployt via Push; Mac/Win brauchen Xcode-/Cargo-Release-Build
(zied) + Smoke-Tests vor Release. MagicAPIClient.swift trägt etwas vorbestehenden
WIP mit (gleiche Magic-Client-Domäne).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 05:19:10 +02:00

111 lines
4.1 KiB
Swift

import Foundation
import AppKit
/// Service für Mac-DNS-Profile-Download + Installation.
enum MacProfileInstaller {
enum InstallerError: Error, LocalizedError {
case noRegistration
case downloadFailed(String)
case installFailed(String)
var errorDescription: String? {
switch self {
case .noRegistration:
return "Mac ist nicht registriert. Bitte zuerst registrieren."
case .downloadFailed(let msg):
return "Profile-Download fehlgeschlagen: \(msg)"
case .installFailed(let msg):
return "Profile-Installation fehlgeschlagen: \(msg)"
}
}
}
/// Lädt Mac-DNS-Profile von Backend und öffnet es in System Settings Profiles.
/// Ab macOS 15+ ist `profiles install` für Configuration Profiles entfernt
/// ("profiles tool no longer supports installs. Use System Settings
/// Profiles to add configuration profiles."). Einzig zulässiger Weg ohne
/// MDM-Enrollment: NSWorkspace öffnet die .mobileconfig Profiles-Pane
/// erscheint User muss manuell Installieren" klicken + Admin-PW geben.
static func downloadAndInstall(registration: MagicRegistration) async throws {
// 1. Download profile
let profileURL: URL
do {
profileURL = try await MagicAPIClient.shared.downloadProfile(token: registration.dnsToken)
} catch {
throw InstallerError.downloadFailed(error.localizedDescription)
}
// 2. Open in System Settings Profiles (user must confirm in UI)
let opened = await MainActor.run {
NSWorkspace.shared.open(profileURL)
}
if !opened {
try? FileManager.default.removeItem(at: profileURL)
throw InstallerError.installFailed(
"System Settings konnte das Profil nicht öffnen. Datei liegt unter: \(profileURL.path)"
)
}
// NICHT löschen System Settings braucht die Datei evtl. noch.
// OS räumt /tmp selbst auf.
}
/// Prüft ob ReBreak-DNS-Profile bereits installiert ist.
/// Verwendet `profiles show -type configuration`.
static func isInstalled() async -> Bool {
guard let result = try? await ProcessRunner.run(
"/usr/bin/profiles",
arguments: ["show", "-type", "configuration"]
), result.exitCode == 0 else {
return false
}
// Suche nach PayloadIdentifier pattern org.rebreak.protection.dns.filter*
return result.stdout.localizedCaseInsensitiveContains("org.rebreak.protection.dns.filter")
|| result.stdout.localizedCaseInsensitiveContains("org.rebreak.protection.profile")
}
// MARK: - Debug only
#if DEBUG
/// Entfernt ReBreak-DNS-Profile.
/// NUR in Debug-Builds verfügbar darf nicht im Release-Binary landen.
static func remove() async throws {
// 1. Find identifier
guard let result = try? await ProcessRunner.run(
"/usr/bin/profiles",
arguments: ["show", "-type", "configuration"]
), result.exitCode == 0 else {
return
}
// Parse identifier aus Output (format: " <identifier>: <displayName>")
let lines = result.stdout.split(separator: "\n")
var identifier: String?
for line in lines {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("org.rebreak.protection.profile") {
// Format: "org.rebreak.protection.profile.abc123: ReBreak Protection"
identifier = trimmed.split(separator: ":").first.map(String.init)
break
}
}
guard let id = identifier else { return }
// 2. Remove profile
let removeResult = try await ProcessRunner.run(
"/usr/bin/profiles",
arguments: ["remove", "-identifier", id]
)
if removeResult.exitCode != 0 {
throw InstallerError.installFailed(removeResult.stderr)
}
}
#endif
}