This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/magpie.git
The following commit(s) were added to refs/heads/main by this push:
new dd107da feat(agent-isolation): protect secret env vars via
sandbox.credentials (#591)
dd107da is described below
commit dd107dafc3e71f1382bb923784ece83109e5ce88
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Jun 27 15:55:05 2026 -0400
feat(agent-isolation): protect secret env vars via sandbox.credentials
(#591)
* feat(agent-isolation): protect secret env vars via sandbox.credentials
Add a `sandbox.credentials.envVars` block (claude-code 2.1.187+) to
the dogfooded `.claude/settings.json`, with `mode: "deny"` for the
secret environment variables a sandboxed command could otherwise
read.
This closes a real gap: `sandbox.filesystem.denyRead` and the
`permissions.deny[Read(...)]` rules are filesystem-only and do NOT
stop a sandboxed Bash command from reading `$ANTHROPIC_API_KEY`,
`$GH_TOKEN`, or dumping `env`. `sandbox.credentials.envVars` is the
only layer that covers environment variables.
`mode: "deny"` unsets the variable for sandboxed commands only — the
unsandboxed agent process keeps its own auth, and sandbox-bypassed
commands (e.g. `gh`, which authenticates via ~/.config/gh) are
unaffected. Credential FILES are already covered by
`denyRead: ["~/"]`, so only `envVars` is added.
The annotated copy in docs/setup/secure-agent-setup.md is updated to
match, documenting the env-var gap and the deny-vs-mask choice.
Generated-by: Claude Code (Opus 4.8)
* fix(sandbox-lint): mirror sandbox.credentials into expected.json baseline
The `lint .claude/settings.json against baseline` CI check (sandbox-lint,
threat-model mitigation M.29) requires every change to the live sandbox
config to be mirrored in tools/sandbox-lint/expected.json. The previous
commit added `$.sandbox.credentials` to .claude/settings.json but not to
the baseline, so the check failed with:
$.sandbox.credentials: extra in settings (not in expected)
Mirror the same `credentials.envVars` block into the baseline. `uv run
sandbox-lint` now passes.
Generated-by: Claude Code (Opus 4.8)
---
.claude/settings.json | 14 ++++++++++++++
docs/setup/secure-agent-setup.md | 25 +++++++++++++++++++++++++
tools/sandbox-lint/expected.json | 14 ++++++++++++++
3 files changed, 53 insertions(+)
diff --git a/.claude/settings.json b/.claude/settings.json
index 39ae34e..6dd439f 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -46,6 +46,20 @@
"*.crates.io"
],
"enableWeakerNetworkIsolation": true
+ },
+ "credentials": {
+ "envVars": [
+ { "name": "ANTHROPIC_API_KEY", "mode": "deny" },
+ { "name": "ANTHROPIC_AUTH_TOKEN", "mode": "deny" },
+ { "name": "CLAUDE_CODE_OAUTH_TOKEN", "mode": "deny" },
+ { "name": "GH_TOKEN", "mode": "deny" },
+ { "name": "GITHUB_TOKEN", "mode": "deny" },
+ { "name": "AWS_ACCESS_KEY_ID", "mode": "deny" },
+ { "name": "AWS_SECRET_ACCESS_KEY", "mode": "deny" },
+ { "name": "AWS_SESSION_TOKEN", "mode": "deny" },
+ { "name": "NPM_TOKEN", "mode": "deny" },
+ { "name": "TWINE_PASSWORD", "mode": "deny" }
+ ]
}
},
"permissions": {
diff --git a/docs/setup/secure-agent-setup.md b/docs/setup/secure-agent-setup.md
index dc29d7e..177c89b 100644
--- a/docs/setup/secure-agent-setup.md
+++ b/docs/setup/secure-agent-setup.md
@@ -427,6 +427,31 @@ below, annotated.
// data-exfiltration vector through the trustd service." No-op
// outside the sandbox (e.g. CI). macOS-only.
"enableWeakerNetworkIsolation": true
+ },
+ // `sandbox.credentials` (claude-code 2.1.187+) is the only layer
+ // that protects secret ENVIRONMENT VARIABLES — `denyRead` and the
+ // `permissions.deny[Read(...)]` rules are filesystem-only and do
+ // NOT stop a sandboxed command from reading `$ANTHROPIC_API_KEY`
+ // or dumping `env`. `mode: "deny"` unsets the var for sandboxed
+ // commands only; the unsandboxed agent process keeps its own auth,
+ // and sandbox-bypassed commands (e.g. `gh`, which reads
+ // ~/.config/gh) are unaffected. Credential FILES are already
+ // covered by `denyRead: ["~/"]`, so only `envVars` is listed here.
+ // (`mode: "mask"` + `injectHosts` is the alternative: keep the var
+ // usable only for connections to named hosts — not needed here.)
+ "credentials": {
+ "envVars": [
+ { "name": "ANTHROPIC_API_KEY", "mode": "deny" },
+ { "name": "ANTHROPIC_AUTH_TOKEN", "mode": "deny" },
+ { "name": "CLAUDE_CODE_OAUTH_TOKEN", "mode": "deny" },
+ { "name": "GH_TOKEN", "mode": "deny" },
+ { "name": "GITHUB_TOKEN", "mode": "deny" },
+ { "name": "AWS_ACCESS_KEY_ID", "mode": "deny" },
+ { "name": "AWS_SECRET_ACCESS_KEY", "mode": "deny" },
+ { "name": "AWS_SESSION_TOKEN", "mode": "deny" },
+ { "name": "NPM_TOKEN", "mode": "deny" },
+ { "name": "TWINE_PASSWORD", "mode": "deny" }
+ ]
}
},
"permissions": {
diff --git a/tools/sandbox-lint/expected.json b/tools/sandbox-lint/expected.json
index 39ae34e..6dd439f 100644
--- a/tools/sandbox-lint/expected.json
+++ b/tools/sandbox-lint/expected.json
@@ -46,6 +46,20 @@
"*.crates.io"
],
"enableWeakerNetworkIsolation": true
+ },
+ "credentials": {
+ "envVars": [
+ { "name": "ANTHROPIC_API_KEY", "mode": "deny" },
+ { "name": "ANTHROPIC_AUTH_TOKEN", "mode": "deny" },
+ { "name": "CLAUDE_CODE_OAUTH_TOKEN", "mode": "deny" },
+ { "name": "GH_TOKEN", "mode": "deny" },
+ { "name": "GITHUB_TOKEN", "mode": "deny" },
+ { "name": "AWS_ACCESS_KEY_ID", "mode": "deny" },
+ { "name": "AWS_SECRET_ACCESS_KEY", "mode": "deny" },
+ { "name": "AWS_SESSION_TOKEN", "mode": "deny" },
+ { "name": "NPM_TOKEN", "mode": "deny" },
+ { "name": "TWINE_PASSWORD", "mode": "deny" }
+ ]
}
},
"permissions": {