This commit is contained in:
Max Goedjen 2025-09-06 14:06:32 -07:00
parent 9c8810cc56
commit 40438076e9
No known key found for this signature in database
11 changed files with 426 additions and 91 deletions

View File

@ -0,0 +1,17 @@
import Foundation
import SecretAgentKit
//@objc public protocol RemoteAgentInputParserProtocol {
//
// func parse(data: Data, with reply: (SSHAgent.Request?, (any Error)?) -> Void)
//
//}
//
//class AgentInputParser: NSObject, RemoteAgentInputParserProtocol {
//
// /// This implements the example protocol. Replace the body of this class with the implementation of this service's protocol.
// @objc func performCalculation(firstNumber: Int, secondNumber: Int, with reply: @escaping (Int) -> Void) {
// let response = firstNumber + secondNumber
// reply(response)
// }
//}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>XPCService</key>
<dict>
<key>ServiceType</key>
<string>Application</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,40 @@
////
//// main.swift
//// AgentRequestParser
////
//// Created by Max Goedjen on 9/5/25.
//// Copyright © 2025 Max Goedjen. All rights reserved.
////
//
//import Foundation
//
//class ServiceDelegate: NSObject, NSXPCListenerDelegate {
//
// /// This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.
// func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
//
// // Configure the connection.
// // First, set the interface that the exported object implements.
// newConnection.exportedInterface = NSXPCInterface(with: (any AgentRequestParserProtocol).self)
//
// // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object.
// let exportedObject = AgentRequestParser()
// newConnection.exportedObject = exportedObject
//
// // Resuming the connection allows the system to deliver more incoming messages.
// newConnection.resume()
//
// // Returning true from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call invalidate() on the connection and return false.
// return true
// }
//}
//
//// Create the delegate for the service.
//let delegate = ServiceDelegate()
//
//// Set up the one NSXPCListener for this service. It will handle all incoming connections.
//let listener = NSXPCListener.service()
//listener.delegate = delegate
//
//// Resuming the serviceListener starts this service. This method does not return.
//listener.resume()

View File

@ -31,49 +31,27 @@ public final class Agent: Sendable {
extension Agent {
/// Handles an incoming request.
/// - Parameters:
/// - data: The data to handle.
/// - provenance: The origin of the request.
/// - Returns: A response data payload.
public func handle(data: Data, provenance: SigningRequestProvenance) async throws -> Data {
logger.debug("Agent handling new data")
guard data.count > 4 else {
throw InvalidDataProvidedError()
}
let requestTypeInt = data[4]
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else {
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription) for unknown request type \(requestTypeInt)")
return SSHAgent.ResponseType.agentFailure.data.lengthAndData
}
logger.debug("Agent handling request of type \(requestType.debugDescription)")
let subData = Data(data[5...])
let response = await handle(requestType: requestType, data: subData, provenance: provenance)
return response
}
private func handle(requestType: SSHAgent.RequestType, data: Data, provenance: SigningRequestProvenance) async -> Data {
public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data {
// Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting
await reloadSecretsIfNeccessary()
var response = Data()
do {
switch requestType {
switch request {
case .requestIdentities:
response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data)
response.append(SSHAgent.Response.agentIdentitiesAnswer.data)
response.append(await identities())
logger.debug("Agent returned \(SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)")
case .signRequest:
response.append(SSHAgent.ResponseType.agentSignResponse.data)
response.append(try await sign(data: data, provenance: provenance))
logger.debug("Agent returned \(SSHAgent.ResponseType.agentSignResponse.debugDescription)")
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
case .signRequest(let context):
response.append(SSHAgent.Response.agentSignResponse.data)
response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance))
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
default:
logger.debug("Agent received valid request of type \(requestType.debugDescription), but not currently supported.")
response.append(SSHAgent.ResponseType.agentFailure.data)
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
throw UnhandledRequestError()
}
} catch {
response = SSHAgent.ResponseType.agentFailure.data
logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)")
response = SSHAgent.Response.agentFailure.data
logger.debug("Agent returned \(SSHAgent.Response.agentFailure.debugDescription)")
}
return response.lengthAndData
}
@ -113,27 +91,23 @@ extension Agent {
/// - data: The data to sign.
/// - provenance: A ``SecretKit.SigningRequestProvenance`` object describing the origin of the request.
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
func sign(data: Data, provenance: SigningRequestProvenance) async throws -> Data {
let reader = OpenSSHReader(data: data)
let payloadHash = try reader.readNextChunk()
let hash: Data
func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data {
// Check if hash is actually an openssh certificate and reconstruct the public key if it is
if let certificatePublicKey = await certificateHandler.publicKeyHash(from: payloadHash) {
hash = certificatePublicKey
let resolvedBlob: Data
if let certificatePublicKey = await certificateHandler.publicKeyHash(from: keyBlob) {
resolvedBlob = certificatePublicKey
} else {
hash = payloadHash
resolvedBlob = keyBlob
}
guard let (secret, store) = await secret(matching: hash) else {
logger.debug("Agent did not have a key matching \(hash as NSData)")
guard let (secret, store) = await secret(matching: resolvedBlob) else {
logger.debug("Agent did not have a key matching \(resolvedBlob as NSData)")
throw NoMatchingKeyError()
}
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
let dataToSign = try reader.readNextChunk()
let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance)
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance)
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
try await witness?.witness(accessTo: secret, from: store, by: provenance)
@ -172,16 +146,16 @@ extension Agent {
extension Agent {
struct InvalidDataProvidedError: Error {}
struct NoMatchingKeyError: Error {}
struct UnhandledRequestError: Error {}
}
extension SSHAgent.ResponseType {
extension SSHAgent.Response {
var data: Data {
var raw = self.rawValue
return Data(bytes: &raw, count: UInt8.bitWidth/8)
return Data(bytes: &raw, count: MemoryLayout<UInt8>.size)
}
}

View File

@ -1,26 +1,6 @@
import Foundation
/// Protocol abstraction of the reading aspects of FileHandle.
public protocol FileHandleReader: Sendable {
/// Gets data that is available for reading.
var availableData: Data { get }
/// A file descriptor of the handle.
var fileDescriptor: Int32 { get }
/// The process ID of the process coonnected to the other end of the FileHandle.
var pidOfConnectedProcess: Int32 { get }
}
/// Protocol abstraction of the writing aspects of FileHandle.
public protocol FileHandleWriter: Sendable {
/// Writes data to the handle.
func write(_ data: Data)
}
extension FileHandle: FileHandleReader, FileHandleWriter {
extension FileHandle {
public var pidOfConnectedProcess: Int32 {
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<Int32>.size, alignment: 1)

View File

@ -0,0 +1,76 @@
import Foundation
import OSLog
import SecretKit
public protocol SSHAgentInputParserProtocol: Sendable {
func parse(data: Data) async throws -> SSHAgent.Request
}
public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
public init() {
}
public func parse(data: Data) async throws -> SSHAgent.Request {
logger.debug("Parsing new data")
guard data.count > 4 else {
throw InvalidDataProvidedError()
}
let specifiedLength = (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4
let rawRequestInt = data[4]
let remainingDataRange = 4..<min(Int(specifiedLength), data.count)
lazy var body: Data = { Data(data[remainingDataRange]) }()
switch rawRequestInt {
case SSHAgent.Request.requestIdentities.protocolID:
return .requestIdentities
case SSHAgent.Request.signRequest(.empty).protocolID:
return .signRequest(try signatureRequestContext(from: body))
case SSHAgent.Request.addIdentity.protocolID:
return .addIdentity
case SSHAgent.Request.removeIdentity.protocolID:
return .removeIdentity
case SSHAgent.Request.removeAllIdentities.protocolID:
return .removeAllIdentities
case SSHAgent.Request.addIDConstrained.protocolID:
return .addIDConstrained
case SSHAgent.Request.addSmartcardKey.protocolID:
return .addSmartcardKey
case SSHAgent.Request.removeSmartcardKey.protocolID:
return .removeSmartcardKey
case SSHAgent.Request.lock.protocolID:
return .lock
case SSHAgent.Request.unlock.protocolID:
return .unlock
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
return .addSmartcardKeyConstrained
case SSHAgent.Request.protocolExtension.protocolID:
return .protocolExtension
default:
return .unknown(rawRequestInt)
}
}
}
extension SSHAgentInputParser {
func signatureRequestContext(from data: Data) throws -> SSHAgent.Request.SignatureRequestContext {
let reader = OpenSSHReader(data: data)
let keyBlob = try reader.readNextChunk()
let dataToSign = try reader.readNextChunk()
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign)
}
}
extension SSHAgentInputParser {
struct AgentUnknownRequestError: Error {}
struct AgentUnhandledRequestError: Error {}
struct InvalidDataProvidedError: Error {}
}

View File

@ -6,21 +6,39 @@ public enum SSHAgent {}
extension SSHAgent {
/// The type of the SSH Agent Request, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
public enum RequestType: UInt8, CustomDebugStringConvertible {
public enum Request: CustomDebugStringConvertible, Codable {
case requestIdentities = 11
case signRequest = 13
case addIdentity = 17
case removeIdentity = 18
case removeAllIdentities = 19
case addIDConstrained = 25
case addSmartcardKey = 20
case removeSmartcardKey = 21
case lock = 22
case unlock = 23
case addSmartcardKeyConstrained = 26
case protocolExtension = 27
case requestIdentities
case signRequest(SignatureRequestContext)
case addIdentity
case removeIdentity
case removeAllIdentities
case addIDConstrained
case addSmartcardKey
case removeSmartcardKey
case lock
case unlock
case addSmartcardKeyConstrained
case protocolExtension
case unknown(UInt8)
public var protocolID: UInt8 {
switch self {
case .requestIdentities: 11
case .signRequest: 13
case .addIdentity: 17
case .removeIdentity: 18
case .removeAllIdentities: 19
case .addIDConstrained: 25
case .addSmartcardKey: 20
case .removeSmartcardKey: 21
case .lock: 22
case .unlock: 23
case .addSmartcardKeyConstrained: 26
case .protocolExtension: 27
case .unknown(let value): value
}
}
public var debugDescription: String {
switch self {
@ -36,12 +54,28 @@ extension SSHAgent {
case .unlock: "SSH_AGENTC_UNLOCK"
case .addSmartcardKeyConstrained: "SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED"
case .protocolExtension: "SSH_AGENTC_EXTENSION"
case .unknown: "UNKNOWN_MESSAGE"
}
}
public struct SignatureRequestContext: Sendable, Codable {
public let keyBlob: Data
public let dataToSign: Data
public init(keyBlob: Data, dataToSign: Data) {
self.keyBlob = keyBlob
self.dataToSign = dataToSign
}
public static var empty: SignatureRequestContext {
SignatureRequestContext(keyBlob: Data(), dataToSign: Data())
}
}
}
/// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
public enum ResponseType: UInt8, CustomDebugStringConvertible {
public enum Response: UInt8, CustomDebugStringConvertible {
case agentFailure = 5
case agentSuccess = 6

View File

@ -13,9 +13,8 @@ extension SigningRequestTracer {
/// Generates a ``SecretKit.SigningRequestProvenance`` from a ``FileHandleReader``.
/// - Parameter fileHandleReader: The reader involved in processing the request.
/// - Returns: A ``SecretKit.SigningRequestProvenance`` describing the origin of the request.
func provenance(from fileHandleReader: FileHandleReader) -> SigningRequestProvenance {
let firstInfo = process(from: fileHandleReader.pidOfConnectedProcess)
func provenance(from fileHandle: FileHandle) -> SigningRequestProvenance {
let firstInfo = process(from: fileHandle.pidOfConnectedProcess)
var provenance = SigningRequestProvenance(root: firstInfo)
while NSRunningApplication(processIdentifier: provenance.origin.pid) == nil && provenance.origin.parentPID != nil {
provenance.chain.append(process(from: provenance.origin.parentPID!))

View File

@ -36,9 +36,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
Task {
for await session in socketController.sessions {
Task {
let inputParser = SSHAgentInputParser()
do {
for await message in session.messages {
let agentResponse = try await agent.handle(data: message, provenance: session.provenance)
let request = try await inputParser.parse(data: message)
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
try await session.write(agentResponse)
}
} catch {

View File

@ -0,0 +1,10 @@
import Foundation
import SecretAgentKit
struct XPCAgentInputParser: SSHAgentInputParserProtocol {
func parse(data: Data) async throws -> SSHAgent.Request {
fatalError()
}
}

View File

@ -30,6 +30,11 @@
501577DA2E6BC5F3004A37D0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501577D62E6BC5F3004A37D0 /* main.swift */; };
501577DB2E6BC5F3004A37D0 /* ReleasesDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501577D72E6BC5F3004A37D0 /* ReleasesDownloader.swift */; };
501577DF2E6BC647004A37D0 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501577DE2E6BC647004A37D0 /* Brief */; };
501578032E6C019F004A37D0 /* AgentRequestParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 501577F82E6C019F004A37D0 /* AgentRequestParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
5015780F2E6C01F1004A37D0 /* AgentRequestParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578092E6C01F1004A37D0 /* AgentRequestParser.swift */; };
501578112E6C01F1004A37D0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5015780C2E6C01F1004A37D0 /* main.swift */; };
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; };
501578152E6C0558004A37D0 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 501578142E6C0558004A37D0 /* SecretAgentKit */; };
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
504788EC2E680DC800B4556F /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788EB2E680DC400B4556F /* URLs.swift */; };
504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; };
@ -97,6 +102,13 @@
remoteGlobalIDString = 501577BC2E6BC5B4004A37D0;
remoteInfo = ReleasesDownloader;
};
501578012E6C019F004A37D0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 501577F72E6C019F004A37D0;
remoteInfo = AgentRequestParser;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -106,6 +118,7 @@
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
dstSubfolderSpec = 16;
files = (
501578032E6C019F004A37D0 /* AgentRequestParser.xpc in Embed XPC Services */,
501577C82E6BC5B4004A37D0 /* ReleasesDownloader.xpc in Embed XPC Services */,
);
name = "Embed XPC Services";
@ -166,6 +179,11 @@
501577D52E6BC5F3004A37D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
501577D62E6BC5F3004A37D0 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
501577D72E6BC5F3004A37D0 /* ReleasesDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleasesDownloader.swift; sourceTree = "<group>"; };
501577F82E6C019F004A37D0 /* AgentRequestParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = AgentRequestParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
501578092E6C01F1004A37D0 /* AgentRequestParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentRequestParser.swift; sourceTree = "<group>"; };
5015780B2E6C01F1004A37D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5015780C2E6C01F1004A37D0 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = "<group>"; };
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = "<group>"; };
504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = "<group>"; };
@ -221,6 +239,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
501577F52E6C019F004A37D0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
501578152E6C0558004A37D0 /* SecretAgentKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
50617D7C23FCE48D0099B055 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -265,6 +291,16 @@
path = ReleasesDownloader;
sourceTree = "<group>";
};
5015780D2E6C01F1004A37D0 /* AgentRequestParser */ = {
isa = PBXGroup;
children = (
501578092E6C01F1004A37D0 /* AgentRequestParser.swift */,
5015780B2E6C01F1004A37D0 /* Info.plist */,
5015780C2E6C01F1004A37D0 /* main.swift */,
);
path = AgentRequestParser;
sourceTree = "<group>";
};
504788ED2E681EB200B4556F /* Styles */ = {
isa = PBXGroup;
children = (
@ -321,6 +357,7 @@
5003EF39278005C800DF2006 /* Packages */,
50617D8123FCE48E0099B055 /* Secretive */,
50A3B78B24026B7500D209EA /* SecretAgent */,
5015780D2E6C01F1004A37D0 /* AgentRequestParser */,
501577D92E6BC5F3004A37D0 /* ReleasesDownloader */,
508A58AF241E144C0069DC07 /* Config */,
50617D8023FCE48E0099B055 /* Products */,
@ -334,6 +371,7 @@
50617D7F23FCE48E0099B055 /* Secretive.app */,
50A3B78A24026B7500D209EA /* SecretAgent.app */,
501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */,
501577F82E6C019F004A37D0 /* AgentRequestParser.xpc */,
);
name = Products;
sourceTree = "<group>";
@ -411,6 +449,7 @@
children = (
50020BAF24064869003D4025 /* AppDelegate.swift */,
5018F54E24064786002EB505 /* Notifier.swift */,
501578122E6C0479004A37D0 /* XPCInputParser.swift */,
50A3B79524026B7600D209EA /* Main.storyboard */,
50A3B79824026B7600D209EA /* Info.plist */,
508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */,
@ -451,6 +490,26 @@
productReference = 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */;
productType = "com.apple.product-type.xpc-service";
};
501577F72E6C019F004A37D0 /* AgentRequestParser */ = {
isa = PBXNativeTarget;
buildConfigurationList = 501578052E6C019F004A37D0 /* Build configuration list for PBXNativeTarget "AgentRequestParser" */;
buildPhases = (
501577F42E6C019F004A37D0 /* Sources */,
501577F52E6C019F004A37D0 /* Frameworks */,
501577F62E6C019F004A37D0 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = AgentRequestParser;
packageProductDependencies = (
501578142E6C0558004A37D0 /* SecretAgentKit */,
);
productName = AgentRequestParser;
productReference = 501577F82E6C019F004A37D0 /* AgentRequestParser.xpc */;
productType = "com.apple.product-type.xpc-service";
};
50617D7E23FCE48D0099B055 /* Secretive */ = {
isa = PBXNativeTarget;
buildConfigurationList = 50617D9D23FCE48E0099B055 /* Build configuration list for PBXNativeTarget "Secretive" */;
@ -467,6 +526,7 @@
dependencies = (
50142167278126B500BBAA70 /* PBXTargetDependency */,
501577C72E6BC5B4004A37D0 /* PBXTargetDependency */,
501578022E6C019F004A37D0 /* PBXTargetDependency */,
);
name = Secretive;
packageProductDependencies = (
@ -521,6 +581,9 @@
501577BC2E6BC5B4004A37D0 = {
CreatedOnToolsVersion = 26.0;
};
501577F72E6C019F004A37D0 = {
CreatedOnToolsVersion = 26.0;
};
50617D7E23FCE48D0099B055 = {
CreatedOnToolsVersion = 11.3;
};
@ -554,6 +617,7 @@
50617D7E23FCE48D0099B055 /* Secretive */,
50A3B78924026B7500D209EA /* SecretAgent */,
501577BC2E6BC5B4004A37D0 /* ReleasesDownloader */,
501577F72E6C019F004A37D0 /* AgentRequestParser */,
);
};
/* End PBXProject section */
@ -566,6 +630,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
501577F62E6C019F004A37D0 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
50617D7D23FCE48D0099B055 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -602,6 +673,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
501577F42E6C019F004A37D0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5015780F2E6C01F1004A37D0 /* AgentRequestParser.swift in Sources */,
501578112E6C01F1004A37D0 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
50617D7B23FCE48D0099B055 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -647,6 +727,7 @@
files = (
50020BB024064869003D4025 /* AppDelegate.swift in Sources */,
5018F54F24064786002EB505 /* Notifier.swift in Sources */,
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -673,6 +754,11 @@
target = 501577BC2E6BC5B4004A37D0 /* ReleasesDownloader */;
targetProxy = 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */;
};
501578022E6C019F004A37D0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 501577F72E6C019F004A37D0 /* AgentRequestParser */;
targetProxy = 501578012E6C019F004A37D0 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -714,7 +800,7 @@
INFOPLIST_KEY_CFBundleDisplayName = ReleasesDownloader;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.ReleasesDownloader;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -756,7 +842,7 @@
INFOPLIST_KEY_CFBundleDisplayName = ReleasesDownloader;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.ReleasesDownloader;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -798,7 +884,7 @@
INFOPLIST_KEY_CFBundleDisplayName = ReleasesDownloader;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.ReleasesDownloader;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -812,6 +898,98 @@
};
name = Release;
};
501578062E6C019F004A37D0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AgentRequestParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AgentRequestParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.AgentRequestParser;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
501578072E6C019F004A37D0 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AgentRequestParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AgentRequestParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.AgentRequestParser;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Test;
};
501578082E6C019F004A37D0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Z72PRUAWF6;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AgentRequestParser/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AgentRequestParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.AgentRequestParser;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
50617D9B23FCE48E0099B055 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
@ -1262,6 +1440,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
501578052E6C019F004A37D0 /* Build configuration list for PBXNativeTarget "AgentRequestParser" */ = {
isa = XCConfigurationList;
buildConfigurations = (
501578062E6C019F004A37D0 /* Debug */,
501578072E6C019F004A37D0 /* Test */,
501578082E6C019F004A37D0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -1335,6 +1523,10 @@
isa = XCSwiftPackageProductDependency;
productName = Brief;
};
501578142E6C0558004A37D0 /* SecretAgentKit */ = {
isa = XCSwiftPackageProductDependency;
productName = SecretAgentKit;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 50617D7723FCE48D0099B055 /* Project object */;