Peter Steinbergerfix: harden Doubao arkcli usagee10a6a33 months agoHistory
1281 lines · 47.0 KB · Swift
RawDownload
import Foundation#if canImport(Darwin)import Darwin#elseif canImport(Glibc)import Glibc#elseif canImport(Musl)import Musl#endif#if os(macOS)import Security#endif #if os(Linux)@_silgen_name("pipe2")private func linuxPipe2(_ pipeDescriptors: UnsafeMutablePointer<Int32>, _ flags: Int32) -> Int32#endif public enum PathPurpose: Hashable, Sendable {    case rpc    case tty    case nodeTooling} public struct PathDebugSnapshot: Equatable, Sendable {    public let codexBinary: String?    public let claudeBinary: String?    public let geminiBinary: String?    public let effectivePATH: String    public let loginShellPATH: String?     public static let empty = PathDebugSnapshot(        codexBinary: nil,        claudeBinary: nil,        geminiBinary: nil,        effectivePATH: "",        loginShellPATH: nil)     public init(        codexBinary: String?,        claudeBinary: String?,        geminiBinary: String? = nil,        effectivePATH: String,        loginShellPATH: String?)    {        self.codexBinary = codexBinary        self.claudeBinary = claudeBinary        self.geminiBinary = geminiBinary        self.effectivePATH = effectivePATH        self.loginShellPATH = loginShellPATH    }} public enum BinaryLocator {    /// Test-only override so parallel Gemini suites can point at fake binaries    /// without mutating process-wide `GEMINI_CLI_PATH`.    @TaskLocal public static var geminiBinaryPathOverrideForTesting: String?     public static func resolveClaudeBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "claude",            overrideKey: "CLAUDE_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.claudeWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     public static func resolveArkcliBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "arkcli",            overrideKey: "ARKCLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: [                "\(home)/.local/bin/arkcli",                "/opt/homebrew/bin/arkcli",                "/usr/local/bin/arkcli",            ],            fileManager: fileManager,            home: home)    }     /// Well-known installation paths for the Claude CLI binary.    /// Covers Anthropic's native installer (`~/.local/bin`), the `claude migrate-installer`    /// self-updating location (`~/.claude/local`), the legacy per-user installer    /// (`~/.claude/bin`), Homebrew, and the macOS Terminal installer (cmux.app).    static func claudeWellKnownPaths(home: String) -> [String] {        [            "\(home)/.local/bin/claude",            "\(home)/.claude/local/claude",            "\(home)/.claude/bin/claude",            "/opt/homebrew/bin/claude",            "/usr/local/bin/claude",            "/Applications/cmux.app/Contents/Resources/bin/claude",        ]    }     public static func resolveAntigravityBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "agy",            overrideKey: "ANTIGRAVITY_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: [                "\(home)/.local/bin/agy",                "/opt/homebrew/bin/agy",                "/usr/local/bin/agy",            ],            fileManager: fileManager,            home: home)    }     public static func resolveCodexBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        launchCandidateFilter: (String, FileManager) -> Bool = CodexLaunchPreflight.isLaunchCandidateAllowed,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "codex",            overrideKey: "CODEX_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.codexWellKnownPaths(home: home),            launchCandidateFilter: launchCandidateFilter,            fileManager: fileManager,            home: home)    }     /// Well-known installation paths for the signed Codex CLI bundled with current and legacy desktop apps.    /// Keep these after PATH lookups, but use them as a safe fallback when a PATH shim is blocked.    static func codexWellKnownPaths(home: String) -> [String] {        #if os(macOS)        [            "\(home)/Applications/ChatGPT.app/Contents/Resources/codex",            "\(home)/Applications/Codex.app/Contents/Resources/codex",            "/Applications/ChatGPT.app/Contents/Resources/codex",            "/Applications/Codex.app/Contents/Resources/codex",        ]        #else        []        #endif    }     public static func resolveGeminiBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        if let override = self.geminiBinaryPathOverrideForTesting,           fileManager.isExecutableFile(atPath: override)        {            return override        }        return self.resolveBinary(            name: "gemini",            overrideKey: "GEMINI_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            fileManager: fileManager,            home: home)    }     public static func resolveGrokBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "grok",            overrideKey: "GROK_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.grokWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     public static func resolveAmpBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "amp",            overrideKey: "AMP_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.ampWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     static func ampWellKnownPaths(home: String) -> [String] {        [            "\(home)/.local/bin/amp",            "\(home)/.amp/bin/amp",            "/opt/homebrew/bin/amp",            "/usr/local/bin/amp",        ]    }     /// Well-known install locations for the Grok Build CLI binary.    /// Covers the installer's default (`~/.grok/bin/grok`) and the symlinks it sometimes    /// creates into `~/.local/bin` and `/usr/local/bin`.    static func grokWellKnownPaths(home: String) -> [String] {        [            "\(home)/.grok/bin/grok",            "\(home)/.local/bin/grok",            "/usr/local/bin/grok",            "/opt/homebrew/bin/grok",        ]    }     public static func resolveAWSBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "aws",            overrideKey: "AWS_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.awsWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     /// Well-known install locations for the AWS CLI v2 (`aws`).    /// Covers Homebrew (Apple Silicon + Intel) and the per-user pip/uv install path.    static func awsWellKnownPaths(home: String) -> [String] {        [            "/opt/homebrew/bin/aws",            "/usr/local/bin/aws",            "\(home)/.local/bin/aws",        ]    }     public static func resolveAuggieBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "auggie",            overrideKey: "AUGGIE_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            fileManager: fileManager,            home: home)    }     // swiftlint:disable function_parameter_count    private static func resolveBinary(        name: String,        overrideKey: String,        env: [String: String],        loginPATH: [String]?,        commandV: (String, String?, TimeInterval, FileManager) -> String?,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String?,        wellKnownPaths: [String] = [],        launchCandidateFilter: (String, FileManager) -> Bool = { _, _ in true },        fileManager: FileManager,        home: String) -> String?    {        // swiftlint:enable function_parameter_count        // 1) Explicit override        if let override = env[overrideKey], fileManager.isExecutableFile(atPath: override) {            return override        }         // 2) Login-shell PATH (captured once per launch)        if let loginPATH,           let pathHit = self.find(               name,               in: loginPATH,               fileManager: fileManager,               launchCandidateFilter: launchCandidateFilter)        {            return pathHit        }         // 3) Existing PATH        if let existingPATH = env["PATH"],           let pathHit = self.find(               name,               in: existingPATH.split(separator: ":").map(String.init),               fileManager: fileManager,               launchCandidateFilter: launchCandidateFilter)        {            return pathHit        }         // 4) Well-known installation paths (e.g. Homebrew, cmux.app bundle, ~/.claude/bin).        // Prefer these before shell probing to avoid running interactive shell init for common installs.        for candidate in wellKnownPaths            where fileManager.isExecutableFile(atPath: candidate) && launchCandidateFilter(candidate, fileManager)        {            return candidate        }         // 5) Interactive login shell lookup (captures nvm/fnm/mise paths from .zshrc/.bashrc)        if let shellHit = commandV(name, env["SHELL"], 2.0, fileManager),           fileManager.isExecutableFile(atPath: shellHit),           launchCandidateFilter(shellHit, fileManager)        {            return shellHit        }         // 5b) Alias fallback (login shell); only attempt after all standard lookups fail.        if let aliasHit = aliasResolver(name, env["SHELL"], 2.0, fileManager, home),           fileManager.isExecutableFile(atPath: aliasHit),           launchCandidateFilter(aliasHit, fileManager)        {            return aliasHit        }         // 6) Minimal fallback        let fallback = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"]        if let pathHit = self.find(            name,            in: fallback,            fileManager: fileManager,            launchCandidateFilter: launchCandidateFilter)        {            return pathHit        }         return nil    }     private static func find(        _ binary: String,        in paths: [String],        fileManager: FileManager,        launchCandidateFilter: (String, FileManager) -> Bool = { _, _ in true }) -> String?    {        for path in paths where !path.isEmpty {            let candidate = "\(path.hasSuffix("/") ? String(path.dropLast()) : path)/\(binary)"            if fileManager.isExecutableFile(atPath: candidate), launchCandidateFilter(candidate, fileManager) {                return candidate            }        }        return nil    }} public enum CodexLaunchPreflight {    struct GatekeeperAssessment {        let output: String        let exitStatus: Int32    }     public static func isLaunchCandidateAllowed(path: String, fileManager: FileManager = .default) -> Bool {        #if os(macOS)        self.isLaunchCandidateAllowed(            path: path,            fileManager: fileManager,            hasExtendedAttribute: self.hasExtendedAttribute,            spctlAssessment: { self.spctlAssessment(path: $0) },            appSignatureIsTrusted: self.isExpectedOpenAIAppSignature,            isMachOExecutable: self.isMachOExecutable)        #else        _ = path        _ = fileManager        return true        #endif    }     #if os(macOS)    // Keep each security boundary injectable so preflight tests never inspect or launch host binaries.    // swiftlint:disable:next function_parameter_count    static func isLaunchCandidateAllowed(        path: String,        fileManager: FileManager,        hasExtendedAttribute: (String, String) -> Bool,        spctlAssessment: (String) -> GatekeeperAssessment?,        appSignatureIsTrusted: (String) -> Bool,        isMachOExecutable: (String) -> Bool) -> Bool    {        let realPath = URL(fileURLWithPath: path).resolvingSymlinksInPath().path        let sourceAppBundlePath = self.containingAppBundlePath(for: path)        let resolvedAppBundlePath = self.containingAppBundlePath(for: realPath)        let appBundlePath: String?        if let sourceAppBundlePath {            let resolvedSourceBundle = URL(fileURLWithPath: sourceAppBundlePath).resolvingSymlinksInPath().path            guard resolvedAppBundlePath == resolvedSourceBundle else { return false }            appBundlePath = resolvedSourceBundle        } else {            appBundlePath = resolvedAppBundlePath        }        let appBundlePaths = [sourceAppBundlePath, appBundlePath].compactMap(\.self)        let pathsToCheck = [path, realPath] + appBundlePaths + self            .nativeCodexExecutableCandidates(                for: realPath,                fileManager: fileManager)         for candidate in Set(pathsToCheck) where hasExtendedAttribute(candidate, "com.apple.malware") {            return false        }         let hasQuarantine = Set(pathsToCheck).contains { hasExtendedAttribute($0, "com.apple.quarantine") }        if let appBundlePath {            guard appSignatureIsTrusted(appBundlePath),                  let assessment = spctlAssessment(appBundlePath),                  assessment.exitStatus == 0,                  self.isAcceptedAssessment(assessment.output, path: appBundlePath),                  !self.isExplicitlyBlockedAssessment(assessment.output, path: appBundlePath)            else {                return false            }            return true        }         guard let native = pathsToCheck.first(where: isMachOExecutable) else {            return !hasQuarantine        }         guard let assessment = spctlAssessment(native)        else {            return !hasQuarantine        }         return !self.isExplicitlyBlockedAssessment(assessment.output, path: native)    }     private static func containingAppBundlePath(for path: String) -> String? {        var candidate = URL(fileURLWithPath: path).standardizedFileURL        while candidate.path != "/" {            if candidate.pathExtension.caseInsensitiveCompare("app") == .orderedSame {                return candidate.path            }

Lines 1–500 of 1281.