A credential proxy keeps an agent's real token on the host: it puts the token into each request as it leaves the sandbox, and masks any real token in what comes back. Many of them can be set to allow only a fixed list of destinations. Their bugs this autumn were elsewhere:
- Docker Sandboxes, CVE-2026-105570: the check that decided whether to mask a token response compared hostnames case-sensitively, routing did not. A case variant of the endpoint returned real access and refresh tokens to the sandbox.
- Docker Sandboxes, CVE-2026-101998, in its own words: "could fail open while masking credentials in protected proxy responses". A body read returned data and an error together, and the unmasked bytes went through.
- microsandbox, 28 September: a hostname allowlist bypass "via mismatched HTTP Host/:authority", affecting "especially deny-by-default configurations" (GHSA-3mpp-52gc-935r). And on 9 October: "Host/SNI check fails open on malformed HTTP/1 request lines" (GHSA-gj24-29j2-3x78).
None of these came from the allowlist: no entry was missing, and none was wrong. Each is a place where the proxy met something it did not expect and let it pass.
Three decisions, one default
A proxy decides three things, and each needs the same answer for the unknown: refuse.
What may go out. Listed hosts, methods and exact paths; everything else is a named refusal in the log. This is the part everyone has.
What a request is. The CONNECT target, the TLS server name, the Host
header and whatever the proxy routes on must all be one name, read one way. Every normaliser is a second
parser, and three of the four bugs above are one name read two ways. Either canonicalise once and use only
the result, or refuse anything not already canonical. Anthropic's new
srt-proxy pins it: "the Host header and the TLS server name must name the CONNECT target".
What comes back. Masking is code, and code has error paths. A read error, an unknown flow, an unparseable body: if the default there is forward, the proxy fails open exactly where it matters most.
What d0 does
d0
is a private tool of mine, a productivity tool for my own agentic coding, not a product: it starts Claude
Code, Codex or Antigravity in a throwaway Firecracker microVM, hands them a task and brings their work back
as a branch. Its proxy takes the strict end of each decision:
- One host per CLI, a few exact methods and paths, nothing else.
- The TLS server name and the
Hostheader must equal that host byte for byte. No lowercasing, no trimmed dot: an odd spelling is refused, not repaired. - The refresh token never leaves the host. No token endpoint is on the list, so there is no token response to mask. The safest masking code is the code you do not need.
- A response whose status line or headers carry the real token is not passed on.
if r.Host != host && r.Host != host+":443" || r.URL.IsAbs() || r.URL.RawPath != "" ||
r.URL.EscapedPath() != r.URL.Path || !maskedPathAllowed(credentials, r.Method, r.URL.Path) {
status, reason = http.StatusForbidden, "path-refused"
}
One of the three CLIs d0 runs, Antigravity, keeps its token inside the VM. Google lets its unmodified CLI be run by one's own tools as long as no credential is "extracted, read, or passed to the external tool" (Google staff, October 2026), and masking would have to read it. So d0 only passes Antigravity's traffic through, and every such run says so. Whether that setup is fine, I asked in Google's forum; an answer is still open.
The price
Strict costs something, and it shows. On 3 November I wanted OpenAI's Daybreak, a per-request switch for
security work, in a
d0 run.
Codex refused before sending anything: "Daybreak requires the OpenAI model provider". d0 routes Codex
through its own provider, so that the proxy sees every request. A looser proxy might have let the feature
work. This one made the conflict visible on the first try, which is the point.
That only works at my scale: one user, a few CLIs, Linux. A product for every agent and every company would drown in refusals.
Four questions for any credential proxy
- What happens to a request it does not recognise?
- Do SNI,
Hostand the routed name have to be the same string? - What does the masking code do on a read error?
- Does the sandbox ever talk to a token endpoint at all?
Written with the help of AI; the thinking and the final say are mine.