Sandbox launcher leaks proxy credentials (HTTP(S)_PROXY basic-auth) via bwrap --setenv argv pairs, world-readable in /proc/<pid>/cmdline

Status Open
Reported on v2.1.299
Maintainer reply None cached
Activity 0 comments · opened Oct 14, 2026

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)

  1. Emitter site 1 — byte offset 0xcf4c21f: f("CLOUDSDK_PROXY_USERNAME",s.user), f("CLOUDSDK_PROXY_PASSWORD",s.pass) — the bwrap --setenv NAME VALUE pair builder; s.user/s.pass are parsed from the host proxy URL's inline basic-auth.
  2. Emitter site 2 — byte offset 0xd1f1198: a.push(\CLOUDSDK_PROXY_PASSWORD=${n}\) — a second variant emits NAME=VALUE as a single flat argv token (still argv-visible).
  3. The launcher's own scrubber allowlist at byte offset 0xce07230 lists "CLOUDSDK_PROXY_PASSWORD" — the launcher classifies it as sensitive yet still places it in argv.
  4. Count-only scanner on /proc/[0-9]*/cmdline: {"procs_with_secret_argv": 19, "names": ["CLOUDSDK_PROXY_PASSWORD"]} before cleanup, 0 after killing the launcher trees.
  5. The launcher resolves bwrap via PATH (no /usr/bin/bwrap string in the binary) and does not use --clearenv, so parent-env inheritance applies.

Reproduction (no values)

  1. On a multi-user Linux host without hidepid, export HTTPS_PROXY=http://<user>:<pass>@<proxyhost>:<port> (placeholder creds).
  2. Start Claude Code (2.1.299) with sandbox enabled; run any sandboxed Bash command.
  3. As any local user: grep -l CLOUDSDK_PROXY_PASSWORD /proc/[0-9]*/cmdline → the bwrap launcher pid appears; its cmdline contains the actual password.

Impact

  • Local information disclosure: any local user can read the proxy password (and username) from world-readable /proc/<pid>/cmdline for 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

  1. 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>/environ is owner-only, unlike cmdline). Concretely: drop the CLOUDSDK_PROXY_PASSWORD (and preferably CLOUDSDK_PROXY_USERNAME) --setenv pairs at both emission sites (0xcf4c21f, 0xd1f1198) and set the variables in the child environment instead. If --clearenv is ever added, use fd-passing (--file 0 / --env-file) for secrets.
  2. Pre-exec guard: refuse to launch when any bwrap argv token is secret-shaped (flat *(PASSWORD|TOKEN|SECRET|_KEY)= tokens, or --setenv NAME VALUE with a secret-shaped NAME whose VALUE is not an allowlisted non-secret such as safe.directory).
  3. 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 bwrap wrapper that moves secret-shaped --setenv pairs into the execve environment; effective because the launcher resolves bwrap via PATH) — happy to share the shim if useful.

View original on GitHub ↗