Repository navigation
Description
needs_keychain() is true for Copilot, Gemini, Antigravity and Claude (src/agent.rs:273). The resulting profile grants read/write on ~/Library/Keychains (sandbox_profile.rs:466), unrestricted Mach-service lookup including securityd (:490), and execution of /usr/bin/security.
This is not host RCE and should not be described as one. The database stays encrypted and securityd arbitrates every request, so the grant does not hand over the user's secrets wholesale. What it does is put the sandboxed agent in the position of any local process asking securityd for an item — and per-item ACLs decide the rest.
An item comes out silently when its ACL allows all applications, explicitly trusts /usr/bin/security, trusts the requesting binary, or the user previously chose "Always Allow", and the item carries no user-presence requirement. Items gated on Touch ID or a password are not silently extractable. So the exposure is real but bounded by how each item was created, which is not something cplt controls or can see.
security find-generic-password -s SERVICE -a ACCOUNT -w
security find-internet-password -s host.example.com -a ACCOUNT -w
Anything retrieved can then be used directly, or exfiltrated over the permitted HTTPS egress. Non-exportable private keys stay non-exportable, but signing and decryption with them may still be permitted.
Fix
A scoped credential broker: retrieve the specific token the agent needs before entering the sandbox, and drop the Keychain grant entirely, rather than granting access to the whole Keychain service for the session.
#173 already does this for Copilot ("pre-extract the Copilot token so the Keychain grant can be dropped"). It has been open since 14 August as an auth cleanup; it is the remediation for this, and is worth re-prioritising on that basis. The remaining work is the same treatment for Gemini, Antigravity and Claude, and then removing needs_keychain() where nothing depends on it.
Worth confirming per agent before dropping a grant: some agents re-authenticate mid-session or refresh tokens, in which case a one-shot pre-extraction is not sufficient and the broker needs to stay reachable.
Activity
feat(sandbox): experimental keychain_substitute — drop the whole-Keyc…
feat(sandbox): experimental keychain_substitute — drop the whole-Keyc…
fix(agent): make the goose agent build, and close its persistence vector
fix(sandbox): drop the Keychain grant when the agent's credential is …
docs(sandbox): state why goose gets no Keychain substitute (navikt#293)
Starefossen commentedon Sep 22, 2026
Blocked on #277.
The drop mechanism landed in #257: the Keychain grant is dropped when the agent's credential is in the environment. Copilot still keeps the grant, because its
keychain_substitute_env_vars()entry is empty onmain(src/agent.rs) until the injected-token probe in #277 shows Copilot authenticates under a grant-dropped profile.Even with a positive probe, users authenticated through
ghwould keep the grant:credential_outside_keychainchecks only the parent environment, not the tokenextract_gh_token()pulls later (see the last comment on #277).test(sandbox): cover every branch of Copilot's gh-token substitute
fix(sandbox): let Copilot trade the Keychain for its GitHub token
test(sandbox): cover every branch of Copilot's gh-token substitute
fix(sandbox): let Copilot trade the Keychain for its GitHub token (na…
Starefossen commentedon Oct 15, 2026
Superseded by #695 and docs/copilot-auth-plan.md. Copilot's Keychain grant was dropped by default in #696, Claude's OAuth path landed in #700, and selected-account validation landed in #772.