You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Sandbox can read Zero's own OAuth and provider credential stores #675
Sandboxed commands use a read-all/write-restricted filesystem posture, but the default credential deny list only covers ~/.aws, ~/.config/gcloud, ~/.azure, and the GOOGLE_APPLICATION_CREDENTIALS target. Zero's own credential-bearing files remain readable.
This exposes:
the default plaintext OAuth store at ~/.config/zero/oauth-tokens.json
credentials.json when plaintext provider-key storage is selected
both credentials.enc and its adjacent .secret AES key
user config.json, which can contain inline apiKey, authHeaderValue, and secret customHeaders
Relevant code
internal/sandbox/profile.go: permissionProfileReadRoots starts with the filesystem root; credentialDenyReadPaths does not include Zero files.
internal/oauth/store.go: the default storage case is the plaintext file backend.
internal/credstore/credstore.go: encrypted storage uses credentials.enc with securefile.NewCrypter(path + ".secret").
internal/config/types.go: provider profiles can persist inline keys and credential-bearing headers.
Observed at commit 6fc1220.
Impact
Environment scrubbing does not protect credentials stored on disk. Reading both the encrypted provider store and its co-located key is equivalent to reading plaintext.
Suggested fix
Add exact default deny-read entries for user config.json, OAuth/MCP token stores, credentials.json, credentials.enc, and its .secret.
Include overridden token-store locations.
Add platform integration tests that attempt sandboxed reads of each store.
Fixed by #801, merged today. Verified against current main rather than inferred from the diff, driving credentialDenyReadPathsForEnvironment with the whole store materialised, since the builder filters to paths that exist:
The fix denies the directory rather than the individual files, which matters for the reason this issue exists. An earlier revision denied config.json alone, which is the one file in there that deliberately holds no secrets, while credentials.enc and its adjacent .secret key stayed readable together. Denying the directory also covers nested paths and files nobody enumerated, mcp-oauth-tokens.json among them.
Two things this does NOT cover, both raised on the PR and both still open, so they should not be read as closed here: ~/.ssh, ~/.gnupg, ~/.git-credentials and ~/.config/git/credentials are all still readable by a sandboxed command. Worth its own issue if someone wants that boundary closed too.
Also unchanged on Windows, where the deny list is skipped by design. #808 approaches that side differently, by giving the sandbox its own identity so the credential store is unreachable by construction rather than by enumeration.
Split out of here as #815, with the probe output and the git-config wrinkle written up. Flagging it on the PR thread too since @gnanam1990 raised these as the non-blocking remainder.
Activity
feat(sandbox): add Windows sandbox principals
Vasanthdev2004 commentedon Jul 27, 2026
Fixed by #801, merged today. Verified against current
mainrather than inferred from the diff, drivingcredentialDenyReadPathsForEnvironmentwith the whole store materialised, since the builder filters to paths that exist:That is every file this issue named.
The fix denies the directory rather than the individual files, which matters for the reason this issue exists. An earlier revision denied
config.jsonalone, which is the one file in there that deliberately holds no secrets, whilecredentials.encand its adjacent.secretkey stayed readable together. Denying the directory also covers nested paths and files nobody enumerated,mcp-oauth-tokens.jsonamong them.Two things this does NOT cover, both raised on the PR and both still open, so they should not be read as closed here:
~/.ssh,~/.gnupg,~/.git-credentialsand~/.config/git/credentialsare all still readable by a sandboxed command. Worth its own issue if someone wants that boundary closed too.Also unchanged on Windows, where the deny list is skipped by design. #808 approaches that side differently, by giving the sandbox its own identity so the credential store is unreachable by construction rather than by enumeration.
Vasanthdev2004 commentedon Jul 27, 2026
Split out of here as #815, with the probe output and the git-config wrinkle written up. Flagging it on the PR thread too since @gnanam1990 raised these as the non-blocking remainder.
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals
feat(sandbox): add Windows sandbox principals