Claude Code 2.1.221: mask credential files before sandboxed commands read them
Quick answer
Claude Code 2.1.221 adds mode: "mask" for sandbox credential files on Linux and WSL2. A sandboxed command reads a sentinel copy instead of the real credential. When the command sends an allowed request, the sandbox proxy can substitute the real value on egress. On macOS, file masking falls back to deny, so the command cannot read the credential file.
Do not treat this as a transparent toggle. Pin 2.1.221 or newer, put the rule in a trusted settings scope, use a disposable credential, narrow injectHosts, enable the TLS-terminating proxy required for credential substitution, and make extraction failures stop the canary. The rollout passes only when the command never sees or logs the real value, the approved request authenticates, and every unapproved path fails.
At publication time, Anthropic's general sandbox guide still said file entries supported only deny, while the tagged 2.1.221 release and packaged settings schema exposed file masking. Treat that documentation lag as a reason to verify the exact installed build and effective schema before production rollout.
Who this is for
This guide is for developers and platform teams that let Claude Code run gh, npm, cloud CLIs, or internal API clients inside the Bash sandbox. It is most useful when a command needs authenticated access but the agent should never receive the underlying token.
Credential masking complements the strict network allowlist checklist. The allowlist controls where a sandboxed command can connect; masking controls whether the command holds the real credential. Keep the broader sandbox and worktree regression checks for filesystem and session boundaries.
What changed, and what did not
Before 2.1.221, Claude Code documented two practical credential paths: deny a credential file entirely, or mask an environment variable and let the proxy inject its value. The new release extends the sentinel-and-proxy pattern to files on Linux and WSL2.
| Mode | What the command reads | Can an approved request authenticate? | Boundary |
|---|---|---|---|
File deny
|
Read fails | No | Works as a hard block |
Whole-file mask
|
One sentinel copy | Yes, after proxy substitution | Best for a file containing one secret |
Structured file mask
|
File syntax with captured secret spans replaced | Yes, after proxy substitution | Requires an extract regex |
macOS file mask
|
Read is denied | No | Currently degrades to deny
|
The packaged 2.1.221 schema describes extract as a global regular expression whose capture group 1 identifies each credential value. Without extract, the entire file is replaced by one sentinel. It also exposes onExtractNoMatch, maskDuplicates, and injectHosts controls.
Masking does not make an allowed host safe, inspect the meaning of a request, restrict MCP tools, or protect Claude's built-in file tools. It applies to sandboxed commands. Continue to use least-privilege tokens, repository rules, deterministic tests, and human approval for consequential actions.
A fail-closed rollout workflow
1. Freeze the runtime and platform
Record the exact Claude Code version, install channel, OS, WSL version if applicable, and settings sources. Restart long-running sessions after upgrading. Do not infer the active version from a package lock or another terminal.
2. Start with a disposable credential file
Create a token that can access only a harmless canary endpoint and can be revoked immediately. Use one exact file path, not a directory. Never test with a developer's GitHub, npm, cloud, or production credential.
3. Choose whole-file or structured masking
Use whole-file masking only when the file contains a single credential and the client accepts that shape. For .netrc, JSON, YAML, or key-value files, use extract so the client still sees valid syntax. Capture only the secret in group 1.
Set onExtractNoMatch to error for the canary. The packaged schema says the default warn behavior can leave an unmatched file readable inside the sandbox. A changed file format must block startup, not silently expose the original file. Use maskDuplicates only for long, high-entropy values because replacing short repeated text can corrupt unrelated fields.
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"network": {
"tlsTerminate": {},
"allowedDomains": ["api.example.test"]
},
"credentials": {
"files": [
{
"path": "~/.config/example/credentials",
"mode": "mask",
"extract": "token\\s*=\\s*([A-Za-z0-9_-]+)",
"onExtractNoMatch": "error",
"maskDuplicates": true,
"injectHosts": ["api.example.test"]
}
]
}
}
}
Replace the example host and pattern with your controlled canary. Keep credential-injection rules in user, managed, or explicit CLI settings rather than a repository-controlled file.
4. Bind substitution to narrow egress
Every injectHosts entry must also be reachable through network.allowedDomains. Prefer the exact API hostname rather than a broad wildcard. Credential substitution requires the proxy to see request contents, so configure network.tlsTerminate; without it, the sentinel should reach the server unchanged and authentication should fail.
5. Run six negative and positive tests
Use a sink you control and preserve only redacted evidence:
- Reading the file inside the sandbox shows valid structure and a sentinel, never the disposable token.
- Logs, traces, process arguments, and error messages contain no disposable token.
- A request to the exact allowed host authenticates through proxy substitution.
- The same request to a different host does not receive the real token.
- Change the file so
extractmatches nothing; sandbox setup must stop. - Repeat on every supported execution surface. macOS must deny the file instead of pretending masking works.
6. Roll out with a deny fallback
Start with one non-production workflow. If the build, platform, TLS proxy, schema, or redacted evidence cannot be proven, switch the file entry to deny and route the authenticated action through a narrow MCP/custom tool or an external credential-injecting proxy. The untrusted repository sandbox checklist remains the outer boundary.
Eight rollout gates
| Gate | Required evidence |
|---|---|
| Version | The launching process reports 2.1.221 or newer |
| Platform | Linux or WSL2 is used for file masking; macOS is treated as deny |
| Settings trust | The injection rule is not controlled by repository content |
| Sentinel | The command never reads or logs the real credential |
| Extraction | Group 1 captures only the secret; no-match stops the run |
| Egress |
injectHosts is narrower than or equal to allowed domains |
| TLS | Proxy substitution works only on the intended TLS-terminated path |
| Fallback | Revocation and file deny or external-tool fallback are tested |
Common mistakes
Testing with a real account token. A canary is supposed to be safe when a log, regex, or proxy rule is wrong.
Leaving no-match behavior at a fail-open default. Credential file formats change. Make an unmatched extraction block the rollout.
Using a broad allowed domain. Masking limits who holds the token, not what an authenticated request can do. Keep both the host and token scope narrow.
Assuming macOS parity. Version 2.1.221 explicitly falls back to deny on macOS. An authentication failure there is expected, not proof of substitution.
FAQ
Does masking prevent Claude Code from ever seeing the credential?
It protects the real value from sandboxed Bash commands when the configured file, proxy, and host path behave as expected. It does not cover built-in tools, MCP servers, other unsandboxed processes, or credentials stored somewhere you did not list.
Should I replace file deny rules immediately?
No. Keep deny when the command does not need the credential. Use mask only for an authenticated workflow that passes the sentinel, extraction, egress, TLS, logging, and rollback gates.
Top comments (0)