Three sandbox settings interactions that silently remove a control
Documentation Type
Incorrect/outdated documentation
Documentation Location
_No response_
Section/Topic
Custom proxy configuration · Mask credentials · Protect credentials
Current Documentation
Three places, quoted from the page:
Custom proxy configuration
For organizations requiring advanced network security, you can implement a custom proxy to: Decrypt and inspect HTTPS traffic · Apply custom filtering rules · Log all network requests · Integrate with existing security infrastructure
Mask credentials
Set network.tlsTerminate so the proxy terminates TLS itself. Without it, masking fails without exposing anything: the command still sees only the sentinel, but the sentinel reaches the server unchanged and authentication fails. Claude Code reports this misconfiguration at startup.
Protect credentials
The setting affects sandboxed Bash commands only.
What's Wrong or Missing?
Not a security report — a related item was already closed as Informative on the
grounds that these are operator-configuration properties, and that reasoning
applies here too. This is a documentation gap: three behaviours we measured are
not stated on the page, and each one silently removes a control the reader
believes is active.
Measured on Claude Code 2.1.220, Ubuntu 24.04 on WSL2. Method, raw per-run data
and reproduction steps: https://github.com/yejunkim2000/agentfence
---
1. A custom proxy replaces the built-in one — the allowlist goes with it
The section reads as stricter control. In practice, setting httpProxyPort
removes the built-in domain enforcement entirely.
With network.strictAllowlist: true and the destination not inallowedDomains:
| | request reaches an unlisted host |
|---|---|
| no custom proxy | 0/29 |
| httpProxyPort set | 5/5 |
2. Credential mask does not substitute when a custom proxy is configured
Same cause, separate consequence, and easy to miss because the failure is silent
in the documented-correct configuration.
Requests observed at the proxy (plain HTTP, credential in an Authorization
header):
| configuration | real value arrives | sentinel arrives |
|---|---|---|
| no credential config (baseline) | 5/5 | 0/5 |
| mask + injectHosts, no tlsTerminate | 0/8 | 8/8 |
| mask + injectHosts + tlsTerminate | 0/11 | 11/11 |
With tlsTerminate set — the configuration the page tells you to use — there is
no startup warning and no substitution. The sentinel reaches the destination
and authentication fails. Nothing leaks, but mask exists to keep tools working
while hiding the secret, and that half stops working.
3. sandbox.credentials also restricts the built-in file tools
We measured the opposite of "sandboxed Bash commands only" for built-in Read.
This is the safer direction, but the sentence leads readers to add a second rule
they may not need, or to assume a gap that isn't there.
Asking the agent for a non-secret field inside a protected credentials file
(bypassPermissions, n=30 per arm):
| | value appears in output |
|---|---|
| no protection | 18/30 |
| credentials.files[].mode: "deny" | 0/30 |
---
Smaller notes
- The dependency list is
bubblewrapandsocat. Installing only
bubblewrap produces the same "dependencies are missing" failure — the hint
in the error message is right, but the prose above it mentions the packages
separately and it costs a round of debugging.
- Private and loopback destinations are refused even when present in
allowedDomains (0/10 in our runs). This looks deliberate, but it is not
documented, and it is the first thing anyone building a local test harness
will hit.
Suggested Improvement
- State that a custom proxy replaces the built-in proxy, so
allowedDomains, strictAllowlist and allowManagedDomainsOnly are no
longer enforced and must be implemented by the custom proxy itself.
- Note that
maskrequires the built-in proxy and is therefore incompatible
with httpProxyPort — ideally warn at startup, as the missing-tlsTerminate
case already does.
- Either broaden the "sandboxed Bash commands only" sentence, or state
precisely which tools are covered. The current phrasing understates the
protection.
Impact
High - Prevents users from using a feature
Additional Context
Why "High": item 2 is literally a feature that stops working — withhttpProxyPort set, mask never substitutes, so the credential-masking
feature cannot be used at all. Items 1 and 3 are milder: 1 removes a control
the reader believes is active, 3 understates an existing protection.
Reproduction. All numbers come from a harness that runs the real CLI and
observes outcomes rather than reading responses. Shortest reproduction is about
15 minutes:
https://github.com/yejunkim2000/agentfence → QUICKSTART.md
Raw per-run files are committed alongside the documented numbers, and a checker
fails the selftest if a table drifts from the data.
Method notes that matter for reading the tables:
- Denominators are runs where the build script actually executed. A run where
the agent never ran the script says nothing about the network.
- Every arm carries an inside control (can the agent still read a file inside
the workspace?) so "the rule blocked it" is distinguishable from "the rule
broke the work". Inside controls were full marks in every arm above.
- Canaries are per-run random values in fake credential files. No real
credentials were used or read.
Related. A separate item — the sandbox failing open when bubblewrap /socat are missing, with the warning only on stderr and no trace in--output-format stream-json — was reported through the security program and
closed as Informative, on the reasoning that the preconditions are operator
configuration. We agree with that call, which is why this is filed as a docs
issue instead. Worth noting that the mask + missing-tlsTerminate warning in
item 2 uses the same channel:
⚠ sandbox.credentials mask entries (DEPLOY_TOKEN) are configured but TLS
termination is unavailable — sandboxed commands see only a sentinel value
stdout shows nothing and the exit code is 0 in both cases.