Related articles
- Coding Agent Security Checklist 2026: the isolation model every operator needs
- Beware: Claude Code background subagents can leak secrets — build the isolation model
- Beware: Oh-My-Pi subagents can write into your protected parent checkout
You trust your coding agent’s sandbox. You run it with a permission layer, you read the docs that promise spawned commands can’t reach your secrets, and you assume “read-all” just means the agent can read your repo. This week that assumption broke — twice, in two different popular agents, in the same way. The sandbox was not actually isolating the agent’s own credentials from the commands it ran. Provider API keys, dynamically named OAuth client secrets, MCP OAuth tokens, and a daemon bearer-token file were all reachable by shell commands spawned inside the sandbox. No exploit needed. No attacker needed. The agent’s own tooling was the leak.
This is not a theoretical audit. It is a coordinated cluster of merged-and-open security PRs with reproductions, root causes, and regression tests. The blast radius is every developer who runs these agents with real keys on a machine that also runs other people’s code, CI, or shared processes.
What happened
The clearest example is Gitlawb/zero, where three linked pull requests (#681, #682, #685) landed as a single security review stack fixing issues #675, #676, and #677. Read them in order and the shape of the bug is unmistakable.
-
PR #681 — deny reads of Zero credential stores: The sandbox scrubbed credential environment variables but its read-deny profile only covered cloud-provider paths. Under a “read-all” posture, a spawned command could still read Zero’s own configuration, credential, encrypted-secret, and OAuth token stores from disk. The root-cause note is blunt: the agent’s own credential stores were simply not in the deny list. The fix extends deny-read to the full
~/.config/zerodirectory, OAuth and MCP token paths, and their.secretvariants. -
PR #682 — scrub dynamic credential env vars: The env scrubber knew only a static secret list plus the built-in provider catalog. But custom provider profiles can name arbitrary credential variables through
apiKeyEnv, and OAuth provider names create client-secret variables dynamically (ZERO_OAUTH_*_CLIENT_SECRET). Neither category was in the fixed set, so sandboxed commands inherited those credentials even though built-in secrets were removed. The fix passes every resolved provider profile’sapiKeyEnvinto the sandbox engine and scrubs it across Linux, macOS, and Windows command plans. -
PR #685 — protect daemon token file: The sandbox scrubbed the inline
ZERO_DAEMON_REMOTE_TOKENvalue but left its file-pointer alternative (ZERO_DAEMON_REMOTE_TOKEN_FILE) in the child environment. Under a read-all workspace posture, a command could read that pointer and then the bearer-token file outside the workspace. The pointer is now scrubbed on every platform, and its target is added to the credential deny-read profile. A follow-up commit also denies writes to DenyRead files on macOS, closing a truncate/overwrite bypass under a writable root.
The pattern across all three: the sandbox was built to protect cloud provider secrets, but the agent’s own identity material — the keys that let the agent itself authenticate — was treated as if it lived outside the threat model. It didn’t. A command the agent spawned could read it.
The same bug, different project
It is not isolated to one repo. On the same day, Goose (aaif-goose/goose, 51K stars) merged a set of hardening PRs discovered by the Project Loupe security effort:
- PR #10481 — confine app file operations: App resource loading, provider-generated saves, and deletion now flow through a shared path builder that rejects traversal, absolute paths, multi-component names, cross-platform separators, and Windows drive prefixes before any filesystem operation. Before the fix, app names were not contained to the apps data directory.
- PR #10482 — secure large response spill files: Large tool responses — which can contain command output, file contents, credentials, or database results — were being written through predictable, non-collision-safe temp paths. The fix creates spill files with collision-safe random names and keeps them owner-only on Unix, preventing other local users from reading them under a permissive umask and preventing symlink/prediction collisions.
Neither Goose fix is “the credentials leak” verbatim, but they come from the same class of failure: the agent’s runtime was writing or handling sensitive material in places that a spawned process, a sibling user, or a traversal could reach. When two independent agents close the same shape of hole in the same week, it stops being a one-off and becomes an ecosystem pattern.
Who is affected
- Anyone running Gitlawb/zero with real provider keys, OAuth providers, or the daemon remote token. If you spawned a command (a build, a test runner, an linter, a shell) under a read-all posture before these fixes, that command could read your agent’s credential stores and env vars. On a shared or multi-tenant machine, that is a direct secret-exposure path.
- Anyone running Goose with apps enabled or large tool responses on a shared host. Predictable temp paths and uncontained app names are local-info-leak and traversal vectors.
- Teams that treat “the agent is sandboxed” as a security boundary. The most dangerous assumption here is that the agent’s sandbox protects the agent’s own identity. These PRs prove it didn’t, by default, for the agent’s own token files and dynamic secrets.
- Windows users specifically. Multiple fixes explicitly note that Windows filesystem deny-read remains subject to an ACL-model limitation (tracked separately), meaning the credential deny-read coverage is weaker on Windows than on Linux/macOS even after the fix.
Mitigations
You cannot assume the agent protects itself. Close the gap from your side:
- Run agents on single-user, non-shared hosts where practical. The Goose spill-file and temp-umask issues are local-disclosure vectors — they matter most when other users or processes share the box. A dedicated dev container or VM shrinks the threat surface regardless of agent bugs.
- Scope credentials narrowly and rotate. If a spawned command can read
apiKeyEnvor an OAuth client secret, the blast radius is the full permission of that key. Use provider-scoped keys, short-lived tokens, and rotate after any run that executed untrusted code. - Don’t run untrusted code under the same agent identity you use for trusted work. The fixes close default leaks, but “read-all” plus a cleverly named env var or path is a cat-and-mouse game. Separate the profile (and the keys) you use to run unknown repositories from the profile with deploy/MCP tokens.
- Pin to the fixed versions and verify. For Gitlawb/zero, ensure you are on a build that includes PRs #681/#682/#685 (fixing #675/#676/#677). For Goose, pull #10481/#10482. If you can’t upgrade immediately, set an explicit
AllowReadopt-out posture and avoidapiKeyEnvdynamic providers until you do. - Audit spawned-command output. Anything a shell command prints — logs, error text, env dumps — may now have contained a secret. Treat historical CI logs and local command output from before the fix as potentially leaked.
The ecosystem pattern
Step back and the lesson is bigger than two repos. Every coding agent maintains a second set of credentials beyond the cloud provider keys: its own OAuth tokens, MCP OAuth tokens, daemon bearer tokens, and dynamically generated client secrets. The security story of 2026 is that agents built elaborate sandboxes to protect third-party secrets while leaving their own identity material on the table. The fix is unglamorous — deny-read the agent’s config dir, scrub every dynamic env var, randomize every spill path — but it is the difference between “sandboxed” and “actually contained.”
If you want the operator-level mental model for keeping secrets out of spawned commands, subagent output, and temp files, start from the Coding Agent Security Checklist 2026. The checklist turns this week’s three PRs and two Goose fixes into a repeatable pre-flight you can run before every agent session.
The takeaway: trust the agent’s sandbox for cloud secrets at your peril, and never trust it for the agent’s own. Read the deny list. Scrub the env. Rotate the keys.