From 98e2f38e46b936613fcbbc056d52902715e95df5 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Fri, 19 Jun 2026 21:53:19 -0700 Subject: [PATCH] WIP --- Sources/Packages/Sources/Brief/Updater.swift | 4 +- .../SecretAgentKit/SocketController.swift | 60 ++++++++++++++----- .../Packages/Sources/XPCWrappers/TeamID.swift | 2 +- Sources/SecretAgent/AppDelegate.swift | 22 +++++-- Sources/SecretAgent/SecretAgent.entitlements | 4 +- Sources/Secretive.xcodeproj/project.pbxproj | 30 +++++++--- .../xcschemes/SecretAgent.xcscheme | 2 +- .../xcshareddata/xcschemes/Secretive.xcscheme | 3 + Sources/Secretive/App.swift | 16 ++--- .../Views/Views/AgentStatusView.swift | 8 --- ...com.maxgoedjen.Secretive.SecretAgent.plist | 18 ++++++ 11 files changed, 116 insertions(+), 53 deletions(-) create mode 100644 Sources/Secretive/com.maxgoedjen.Secretive.SecretAgent.plist diff --git a/Sources/Packages/Sources/Brief/Updater.swift b/Sources/Packages/Sources/Brief/Updater.swift index 12be1ee..fcd022d 100644 --- a/Sources/Packages/Sources/Brief/Updater.swift +++ b/Sources/Packages/Sources/Brief/Updater.swift @@ -36,11 +36,11 @@ import XPCWrappers self.currentVersion = currentVersion Task { if checkOnLaunch { - try await checkForUpdates() + try? await checkForUpdates() } while !Task.isCancelled { try? await Task.sleep(for: .seconds(Int(checkFrequency))) - try await checkForUpdates() + try? await checkForUpdates() } } } diff --git a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift index 7839037..f5ec990 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift @@ -1,6 +1,7 @@ import Foundation import OSLog import SecretKit +import launch /// A controller that manages socket configuration and request dispatching. public struct SocketController { @@ -12,7 +13,8 @@ public struct SocketController { private let sessionsContinuation: AsyncStream.Continuation /// The active SocketPort. Must be retained to be kept valid. - private let port: SocketPort + /// Only applicable for legacy non-launchd sockets. + private let port: SocketPort? /// The FileHandle for the main socket. private let fileHandle: FileHandle @@ -23,19 +25,41 @@ public struct SocketController { /// Tracer which determines who originates a socket connection. private let requestTracer = SigningRequestTracer() - /// Initializes a socket controller with a specified path. - /// - Parameter path: The path to use as a socket. - public init(path: String) { + public enum Socket { + case launchd(String) + case path(String) + } + + public init(_ socket: Socket) { (sessions, sessionsContinuation) = AsyncStream.makeStream() - logger.debug("Socket controller setting up at \(path)") - if let _ = try? FileManager.default.removeItem(atPath: path) { - logger.debug("Socket controller removed existing socket") + switch socket { + case .path(let path): + logger.debug("Socket controller setting up at \(path)") + if let _ = try? FileManager.default.removeItem(atPath: path) { + logger.debug("Socket controller removed existing socket") + } + let exists = FileManager.default.fileExists(atPath: path) + assert(!exists) + logger.debug("Socket controller path is clear") + let port = SocketPort(path: path) + fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true) + self.port = port + logger.debug("Socket listening at \(path)") + case .launchd(let name): + logger.debug("Socket controller setting for launchd-controlled socket \(name)") + port = nil + var fileDescriptors: UnsafeMutablePointer? = nil + var count = 0 + let result = unsafe launch_activate_socket(name, &fileDescriptors, &count) + guard result == kOSReturnSuccess, let socket = unsafe fileDescriptors?.pointee else { + fatalError() + } + fileHandle = FileHandle(fileDescriptor: socket, closeOnDealloc: true) } - let exists = FileManager.default.fileExists(atPath: path) - assert(!exists) - logger.debug("Socket controller path is clear") - port = SocketPort(path: path) - fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true) + listen() + } + + func listen() { Task { @MainActor [fileHandle, sessionsContinuation, logger] in // Create the sequence before triggering the notification to // ensure it will not be missed. @@ -51,7 +75,7 @@ public struct SocketController { fileHandle.acceptConnectionInBackgroundAndNotify() } } - logger.debug("Socket listening at \(path)") + } } @@ -94,7 +118,7 @@ extension SocketController { guard !data.isEmpty else { logger.debug("Socket controller received empty data, ending continuation.") messagesContinuation.finish() - try fileHandle.close() + try? fileHandle.close() return } messagesContinuation.yield(data) @@ -126,8 +150,8 @@ private extension SocketPort { convenience init(path: String) { var addr = sockaddr_un() - let length = unsafe withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in - unsafe path.withCString { cstring in + let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in + path.withCString { cstring in let len = unsafe strlen(cstring) unsafe strncpy(pointer, cstring, len) return len @@ -144,3 +168,7 @@ private extension SocketPort { } } + +// Changes the header from `UnsafeMutablePointer?>?` -> `UnsafeMutablePointer?>!` +@_silgen_name("launch_activate_socket") +func launch_activate_socket(_ name: UnsafePointer, _ fds: UnsafeMutablePointer?>!, _ cnt: UnsafeMutablePointer!) -> Int32 diff --git a/Sources/Packages/Sources/XPCWrappers/TeamID.swift b/Sources/Packages/Sources/XPCWrappers/TeamID.swift index 56848a1..22d715e 100644 --- a/Sources/Packages/Sources/XPCWrappers/TeamID.swift +++ b/Sources/Packages/Sources/XPCWrappers/TeamID.swift @@ -11,7 +11,7 @@ extension ProcessInfo { } guard let value = SecTaskCopyValueForEntitlement(task, "com.apple.developer.team-identifier" as CFString, nil) as? String else { - assertionFailure("SecTaskCopyValueForEntitlement(com.apple.developer.team-identifier) failed") +// assertionFailure("SecTaskCopyValueForEntitlement(com.apple.developer.team-identifier) failed") return fallbackTeamID } diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift index a3a7507..a7fe2e4 100644 --- a/Sources/SecretAgent/AppDelegate.swift +++ b/Sources/SecretAgent/AppDelegate.swift @@ -41,10 +41,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { @MainActor private lazy var agent: Agent = { Agent(storeList: storeList, certificateStore: EnvironmentValues._certificateStore, witness: notifier) }() - private lazy var socketController: SocketController = { - let path = URL.socketPath as String - return SocketController(path: path) - }() + private var shutdownTask: Task? + private let socketController = SocketController(.launchd("SecureListener")) private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate") func applicationDidFinishLaunching(_ aNotification: Notification) { @@ -52,16 +50,17 @@ class AppDelegate: NSObject, NSApplicationDelegate { Task { for await session in socketController.sessions { Task { - let inputParser = try await XPCAgentInputParser() do { + let inputParser = try await XPCAgentInputParser() for await message in session.messages { let request = try await inputParser.parse(data: message) let agentResponse = await agent.handle(request: request, provenance: session.provenance) try session.write(agentResponse) } } catch { - try session.close() + try? session.close() } + startCountdownClock() } } } @@ -90,5 +89,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + func startCountdownClock() { + // FIXME: ACCOUNT FOR STORED AUTH + logger.log("Beginning countdown clock") + shutdownTask?.cancel() + shutdownTask = Task { [logger] in + try await Task.sleep(for: .seconds(30)) + logger.log("Shutting down") + await NSApplication.shared.terminate(nil) + } + } + } diff --git a/Sources/SecretAgent/SecretAgent.entitlements b/Sources/SecretAgent/SecretAgent.entitlements index 35188de..28f4467 100644 --- a/Sources/SecretAgent/SecretAgent.entitlements +++ b/Sources/SecretAgent/SecretAgent.entitlements @@ -16,10 +16,10 @@ 1 com.apple.security.hardened-process.hardened-heap - com.apple.security.smartcard - com.apple.security.hardened-process.platform-restrictions-string 2 + com.apple.security.smartcard + keychain-access-groups $(AppIdentifierPrefix)com.maxgoedjen.Secretive diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 54f5f08..fa51b1e 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -21,11 +21,12 @@ 5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; }; 5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; }; 501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; }; - 501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 501421652781268000BBAA70 /* SecretAgent.app in Copy SecretAgent */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; }; 50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; }; 501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; }; 5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; }; + 502452F92FE2026E009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist in Copy SecretAgent plist */ = {isa = PBXBuildFile; fileRef = 502452F32FE1FF89009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist */; }; 504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; }; 504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; }; 504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; }; @@ -181,6 +182,17 @@ name = "Embed XPC Services"; runOnlyForDeploymentPostprocessing = 0; }; + 502452F82FE2024D009EE753 /* Copy SecretAgent plist */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Contents/Library/LaunchAgents; + dstSubfolderSpec = 1; + files = ( + 502452F92FE2026E009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist in Copy SecretAgent plist */, + ); + name = "Copy SecretAgent plist"; + runOnlyForDeploymentPostprocessing = 0; + }; 50617DBF23FCE4AB0099B055 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -201,14 +213,15 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - 50C385AF240E438B00AF2719 /* CopyFiles */ = { + 50C385AF240E438B00AF2719 /* Copy SecretAgent */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LoginItems; dstSubfolderSpec = 1; files = ( - 501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */, + 501421652781268000BBAA70 /* SecretAgent.app in Copy SecretAgent */, ); + name = "Copy SecretAgent"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -224,6 +237,7 @@ 50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = ""; }; 501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = ""; }; 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = ""; }; + 502452F32FE1FF89009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.maxgoedjen.Secretive.SecretAgent.plist; sourceTree = ""; }; 504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = ""; }; 504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = ""; }; 504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = ""; }; @@ -445,6 +459,7 @@ 508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */, 50E4C4C72E777E4200C73783 /* AppIcon.icon */, 50617D8F23FCE48E0099B055 /* Secretive.entitlements */, + 502452F32FE1FF89009EE753 /* com.maxgoedjen.Secretive.SecretAgent.plist */, 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */, 50617D8823FCE48E0099B055 /* Preview Content */, ); @@ -569,7 +584,8 @@ 50617D7C23FCE48D0099B055 /* Frameworks */, 50617D7D23FCE48D0099B055 /* Resources */, 50617DBF23FCE4AB0099B055 /* Embed Frameworks */, - 50C385AF240E438B00AF2719 /* CopyFiles */, + 50C385AF240E438B00AF2719 /* Copy SecretAgent */, + 502452F82FE2024D009EE753 /* Copy SecretAgent plist */, 501577C92E6BC5B4004A37D0 /* Embed XPC Services */, ); buildRules = ( @@ -1523,7 +1539,7 @@ ENABLE_APP_SANDBOX = YES; ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; - ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; @@ -1560,7 +1576,7 @@ ENABLE_APP_SANDBOX = YES; ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; - ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; @@ -1598,7 +1614,7 @@ ENABLE_APP_SANDBOX = YES; ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; - ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; diff --git a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgent.xcscheme b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgent.xcscheme index 726d20d..a10b163 100644 --- a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgent.xcscheme +++ b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgent.xcscheme @@ -70,7 +70,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - launchStyle = "0" + launchStyle = "1" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" diff --git a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme index b7eccb7..77ddc38 100644 --- a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme +++ b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme @@ -87,6 +87,9 @@ ReferencedContainer = "container:Secretive.xcodeproj"> + + + + + + Label + com.maxgoedjen.Secretive.SecretAgent + BundleProgram + Contents/Library/LoginItems/SecretAgent.app/Contents/MacOS/SecretAgent + Sockets + + SecureListener + + SecureSocketWithKey + SECRETAGENT_SOCK + + + +