Sandbox launcher leaks proxy credentials (HTTP(S)_PROXY basic-auth) via bwrap --setenv argv pairs, world-readable in /proc/<pid>/cmdline
Summary
When the host environment contains an HTTP(S)_PROXY URL with inline basic-auth (http://user:pass@proxyhost:port), Claude Code's sandbox network-proxy passthrough parses the credentials out of the URL and re-emits them as bubblewrap --setenv NAME VALUE argv pairs so gcloud inside the sandbox authenticates to the proxy. bwrap argv is visible in /proc/<pid>/cmdline, which is world-readable on hosts without hidepid= mount options. On shared multi-user Linux hosts, every local user can read the proxy password for the lifetime of every sandboxed process.
Observed in the wild: 19 bwrap --new-session --die-with-parent ... --setenv CLOUDSDK_PROXY_PASSWORD <value> ... launcher processes alive over ~29 hours on a shared dev host (including orphaned launchers whose parent claude had exited — they persist until manually killed).
Affected
Claude Code sandbox runtime binary app/versions/2.1.299 (Bun-compiled ELF, dynamically linked). needs-check: other versions.
Evidence (value-free)
- Emitter site 1 — byte offset
0xcf4c21f:f("CLOUDSDK_PROXY_USERNAME",s.user), f("CLOUDSDK_PROXY_PASSWORD",s.pass)— the bwrap--setenv NAME VALUEpair builder;s.user/s.passare parsed from the host proxy URL's inline basic-auth. - Emitter site 2 — byte offset
0xd1f1198: a.push(\CLOUDSDK_PROXY_PASSWORD=${n}\) — a second variant emitsNAME=VALUEas a single flat argv token (still argv-visible). - The launcher's own scrubber allowlist at byte offset
0xce07230lists"CLOUDSDK_PROXY_PASSWORD"— the launcher classifies it as sensitive yet still places it in argv. - Count-only scanner on
/proc/[0-9]*/cmdline:{"procs_with_secret_argv": 19, "names": ["CLOUDSDK_PROXY_PASSWORD"]}before cleanup,0after killing the launcher trees. - The launcher resolves
bwrapvia PATH (no/usr/bin/bwrapstring in the binary) and does not use--clearenv, so parent-env inheritance applies.
Reproduction (no values)
- On a multi-user Linux host without
hidepid, exportHTTPS_PROXY=http://<user>:<pass>@<proxyhost>:<port>(placeholder creds). - Start Claude Code (2.1.299) with sandbox enabled; run any sandboxed Bash command.
- As any local user:
grep -l CLOUDSDK_PROXY_PASSWORD /proc/[0-9]*/cmdline→ the bwrap launcher pid appears; itscmdlinecontains the actual password.
Impact
- Local information disclosure: any local user can read the proxy password (and username) from world-readable
/proc/<pid>/cmdlinefor the lifetime of each sandboxed process; orphaned launchers extend this indefinitely. - Credential class: egress-proxy basic-auth; blast radius depends on what the proxy permits.
- No evidence the value is written to disk or logs; scope is argv exposure.
Suggested fix
- Pass secrets by environment inheritance, never argv. The bwrap invocation does not use
--clearenv, so secrets placed in the execve environment of the bwrap process reach the sandboxed shell identically (/proc/<pid>/environis owner-only, unlikecmdline). Concretely: drop theCLOUDSDK_PROXY_PASSWORD(and preferablyCLOUDSDK_PROXY_USERNAME)--setenvpairs at both emission sites (0xcf4c21f, 0xd1f1198) and set the variables in the child environment instead. If--clearenvis ever added, use fd-passing (--file 0/--env-file) for secrets. - Pre-exec guard: refuse to launch when any bwrap argv token is secret-shaped (flat
*(PASSWORD|TOKEN|SECRET|_KEY)=tokens, or--setenv NAME VALUEwith a secret-shaped NAME whose VALUE is not an allowlisted non-secret such assafe.directory). - Optionally, honor the existing scrubber allowlist (0xce07230) as the denylist for argv emission.
Disclosure notes
- No credential values are included anywhere in this report.
- Affected processes were killed and the credential rotated on the affected host before filing.
- A deployable operator-side mitigation exists (PATH-first
bwrapwrapper that moves secret-shaped--setenvpairs into the execve environment; effective because the launcher resolvesbwrapvia PATH) — happy to share the shim if useful.