fix(macos): consolidate exec approval evaluation

This commit is contained in:
Peter Steinberger
2026-02-21 19:30:26 +01:00
parent 9fc6c8b713
commit e371da38aa
7 changed files with 464 additions and 478 deletions

View File

@@ -350,21 +350,7 @@ enum ExecApprovalsPromptPresenter {
@MainActor
private enum ExecHostExecutor {
private struct ExecApprovalContext {
let command: [String]
let displayCommand: String
let trimmedAgent: String?
let approvals: ExecApprovalsResolved
let security: ExecSecurity
let ask: ExecAsk
let autoAllowSkills: Bool
let env: [String: String]?
let resolution: ExecCommandResolution?
let allowlistResolutions: [ExecCommandResolution]
let allowlistMatches: [ExecAllowlistEntry]
let allowlistSatisfied: Bool
let skillAllow: Bool
}
private typealias ExecApprovalContext = ExecApprovalEvaluation
static func handle(_ request: ExecHostRequest) async -> ExecHostResponse {
let command = request.command.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
@@ -395,7 +381,7 @@ private enum ExecHostExecutor {
if ExecApprovalHelpers.requiresAsk(
ask: context.ask,
security: context.security,
allowlistMatch: context.allowlistSatisfied ? context.allowlistMatches.first : nil,
allowlistMatch: context.allowlistMatch,
skillAllow: context.skillAllow),
approvalDecision == nil
{
@@ -406,7 +392,7 @@ private enum ExecHostExecutor {
host: "node",
security: context.security.rawValue,
ask: context.ask.rawValue,
agentId: context.trimmedAgent,
agentId: context.agentId,
resolvedPath: context.resolution?.resolvedPath,
sessionKey: request.sessionKey))
@@ -447,7 +433,7 @@ private enum ExecHostExecutor {
? context.allowlistResolutions[idx].resolvedPath
: nil
ExecApprovalsStore.recordAllowlistUse(
agentId: context.trimmedAgent,
agentId: context.agentId,
pattern: match.pattern,
command: context.displayCommand,
resolvedPath: resolvedPath)
@@ -466,49 +452,12 @@ private enum ExecHostExecutor {
}
private static func buildContext(request: ExecHostRequest, command: [String]) async -> ExecApprovalContext {
let displayCommand = ExecCommandFormatter.displayString(
for: command,
rawCommand: request.rawCommand)
let agentId = request.agentId?.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedAgent = (agentId?.isEmpty == false) ? agentId : nil
let approvals = ExecApprovalsStore.resolve(agentId: trimmedAgent)
let security = approvals.agent.security
let ask = approvals.agent.ask
let autoAllowSkills = approvals.agent.autoAllowSkills
let env = self.sanitizedEnv(request.env)
let allowlistResolutions = ExecCommandResolution.resolveForAllowlist(
await ExecApprovalEvaluator.evaluate(
command: command,
rawCommand: request.rawCommand,
cwd: request.cwd,
env: env)
let resolution = allowlistResolutions.first
let allowlistMatches = security == .allowlist
? ExecAllowlistMatcher.matchAll(entries: approvals.allowlist, resolutions: allowlistResolutions)
: []
let allowlistSatisfied = security == .allowlist &&
!allowlistResolutions.isEmpty &&
allowlistMatches.count == allowlistResolutions.count
let skillAllow: Bool
if autoAllowSkills, !allowlistResolutions.isEmpty {
let bins = await SkillBinsCache.shared.currentBins()
skillAllow = allowlistResolutions.allSatisfy { bins.contains($0.executableName) }
} else {
skillAllow = false
}
return ExecApprovalContext(
command: command,
displayCommand: displayCommand,
trimmedAgent: trimmedAgent,
approvals: approvals,
security: security,
ask: ask,
autoAllowSkills: autoAllowSkills,
env: env,
resolution: resolution,
allowlistResolutions: allowlistResolutions,
allowlistMatches: allowlistMatches,
allowlistSatisfied: allowlistSatisfied,
skillAllow: skillAllow)
envOverrides: request.env,
agentId: request.agentId)
}
private static func persistAllowlistEntry(
@@ -525,7 +474,7 @@ private enum ExecHostExecutor {
continue
}
if seenPatterns.insert(pattern).inserted {
ExecApprovalsStore.addAllowlistEntry(agentId: context.trimmedAgent, pattern: pattern)
ExecApprovalsStore.addAllowlistEntry(agentId: context.agentId, pattern: pattern)
}
}
}
@@ -586,10 +535,6 @@ private enum ExecHostExecutor {
payload: payload,
error: nil)
}
private static func sanitizedEnv(_ overrides: [String: String]?) -> [String: String] {
HostEnvSanitizer.sanitize(overrides: overrides)
}
}
private final class ExecApprovalsSocketServer: @unchecked Sendable {