fix: fail closed on interactive Keychain use in darwin workers - #823
Conversation
Greptile SummaryAdds a fail-closed macOS Keychain boundary for non-interactive workers.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/darwin-keychain-boundary.js | Implements atomic shim publication, interactive-flag blocking, opt-out handling, and PATH mutation for macOS workers. |
| src/agent/agent-task-executor.js | Applies the Keychain boundary when constructing non-isolated agent worker environments. |
| src/claude-task-runner.js | Applies the boundary in the task-runner environment path and cleans Claude settings overlays if setup fails. |
| src/worktree-tooling-env.js | Prefers the exact uppercase PATH key so worktree tools retain precedence over the managed shim. |
| tests/unit/darwin-keychain-boundary.test.js | Covers shim behavior, publication safety, PATH edge cases, opt-out behavior, and worker-grandchild integration. |
| tests/unit/claude-task-runner-worktree-env.test.js | Verifies settings-overlay cleanup when Keychain boundary installation fails. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Build local worker environment] --> B{macOS and no opt-out?}
B -- No --> C[Preserve inherited environment]
B -- Yes --> D[Install or refresh managed security shim]
D --> E[Prepend shim to PATH]
E --> F[Prepend worktree tool directories]
F --> G[Spawn worker]
G --> H{PATH-resolved security invocation}
H -- Interactive --> I[Block with diagnostic and exit 1]
H -- Non-interactive --> J[Exec /usr/bin/security]
Reviews (5): Last reviewed commit: "fix(agent): preserve exact worktree PATH..." | Re-trigger Greptile
tomdps
left a comment
There was a problem hiding this comment.
The reported #704 root cause is real, but this boundary is not ready to merge. Please address these concrete issues:
-
src/darwin-keychain-boundary.js:27: Darwin environment keys are case-sensitive.pathKeyForEnv()can selectPathwhen bothPathandPATHexist, leaving the actualPATHused by descendants unchanged and allowingsecurity -ito bypass the shim. On Darwin, update the exactPATHkey and add a both-keys regression case. -
src/darwin-keychain-boundary.js:98-102: publish the executable atomically.writeFileSync()truncates the live shim, so concurrent installers or a failed refresh can expose an empty/partialsecurityexecutable to existing workers. Write and chmod a same-directory temporary file, then rename it over the live path; clean the temporary file on failure. -
src/darwin-keychain-boundary.js:135-146: preserve inherited empty PATH components and distinguish unset PATH from explicitly empty PATH. Empty POSIX PATH segments mean the current directory; filter only duplicate shim-directory entries before prepending the shim. -
tests/unit/darwin-keychain-boundary.test.js:217-218: the production-wiring cases take only the non-Darwin branch in required Ubuntu unit CI, while the required macOS install matrix does not run this test. Inject/stub the platform boundary in both production builders and exercise a PATH-resolvedsecurity -igrandchild using the returned environment, including real worktree tool-bin ordering. The test must fail if either productionapplyDarwinKeychainBoundaryToEnvcall is removed.
Please also split tests/e2e/helpers/e2e-harness.js cleanup retry into a separate PR; it is reasonable but unrelated to the Keychain boundary squash.
After those updates, rerun the required gates and request re-review.
Local/worktree workers are spawned with the inherited host environment, so worker descendants (e.g. `claude doctor` probing Keychain writes through `security -i`) reach the logged-in user's GUI Keychain session and launch SecurityAgent dialogs from a supposedly non-interactive cluster. On darwin, worker spawn envs now get a managed shim directory prepended to PATH whose `security` wrapper fails closed on interactive invocations (`-i`, `-p`, or no arguments) with a deterministic diagnostic that points at Docker isolation or explicit credential configuration, and execs /usr/bin/security for every other subcommand so provider authentication (e.g. `security find-generic-password`) keeps working. Docker isolation never reaches buildSpawnEnv and non-darwin platforms are untouched; set ZEROSHOT_ALLOW_INTERACTIVE_KEYCHAIN=1 to opt out. Fixes the-open-engine#704
e046fb3 to
c892e4a
Compare
|
@tomdps Thanks for the concrete review. I rebased the change onto current
Validation after the rebase:
Could you please re-review when convenient? |
tomdps
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 936d2eb after the contributor updates and two follow-up reviewer-fix commits. The #704 root cause is confirmed, every prior finding is resolved, the full current diff and direct spawn/PATH/cleanup integrations are clean, regression coverage exercises both production builders, unrelated cleanup is absent, authorship is preserved, and all applicable required gates are green. Approved for squash merge at this unchanged head.
|
🎉 This PR is included in version 6.10.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Problem
Non-interactive local/worktree worker clusters on macOS can pop GUI Keychain password prompts (SecurityAgent).
buildSpawnEnvspawns workers with the inherited host environment, andtask-lib/watcher.jspasses it through to every worker grandchild, so a descendant likeclaude doctorprobing Keychain writes viasecurity -ireaches the logged-in user's GUI Keychain session.Fixes #704
Fix (fail closed, option 1 from the issue)
On darwin only, worker spawn envs now get a managed shim directory (
~/.zeroshot/keychain-shim) prepended to the exactPATHkey. Itssecuritywrapper:-i,-p(implies interactive), bundled global flags like-qi, or a zero-argument implicit interactive session — with a deterministic diagnostic naming the non-interactive worker boundary and pointing at Docker isolation or explicit credential configuration;execs/usr/bin/security "$@"for every other subcommand, so provider authentication such assecurity find-generic-passwordkeeps working (flag scanning stops at the first subcommand token, and exit codes pass through).ZEROSHOT_ALLOW_INTERACTIVE_KEYCHAIN=1opts out. If the shim cannot be installed, the spawn fails rather than silently re-exposing the interactive Keychain session.The managed executable is refreshed through a same-directory temporary file, chmodded before an atomic rename over the live path, and cleaned up on publication failure. PATH handling preserves inherited empty components (the POSIX current-directory entry), distinguishes unset PATH from an explicitly empty PATH, and removes only duplicate shim entries.
Scope guarantees:
buildSpawnEnv, so containerized runs are untouched.Tests
tests/unit/darwin-keychain-boundary.test.jsnow has 15 cases covering interactive blocking, non-interactive pass-through and exit propagation, opt-out behavior, exactPATHcasing whenPathalso exists, empty/unset PATH semantics, idempotency, atomic publication failure, and non-darwin behavior.security -igrandchild must hit the shim rather than a logging fallback executable.Notes
A PATH shim intercepts PATH-resolved lookups; descendants invoking
/usr/bin/securityby absolute path bypass it. The reported probe resolves via PATH, so the defect in the issue is covered; this is a guardrail for well-behaved tools, not a sandbox against adversarial code.