# OAuth token exposure to claude inside the bottle Research into whether `CLAUDE_CODE_OAUTH_TOKEN` — as currently forwarded into each claude-bottle container — is reachable by claude itself, what (if any) deeper-than-prompt mechanisms could hide it, and whether routing claude through an auth-injecting proxy is viable. ## Summary Yes, claude can read `CLAUDE_CODE_OAUTH_TOKEN` trivially today. There is no Linux primitive for "this env var exists in my process but I cannot read it"; the only credible boundary is to put the credential in a *different* process that claude cannot read. Default Docker enforces that boundary at the kernel level (a non-root process cannot read `/proc//environ`), so a root-owned auth-injecting reverse proxy listening on `127.0.0.1` is a realistic design. Claude Code's `ANTHROPIC_BASE_URL` officially supports this routing pattern with bearer auth, with documented caveats around SSE, header passthrough, and out-of-band outbound traffic (telemetry, npm, etc.) that does not route through `ANTHROPIC_BASE_URL` at all. ## How the token reaches claude today 1. `claude_bottle/cli/start.py` (around line 237–238) — host's `CLAUDE_BOTTLE_OAUTH_TOKEN` is exported into the launcher process as `CLAUDE_CODE_OAUTH_TOKEN`, then forwarded with `docker run -e CLAUDE_CODE_OAUTH_TOKEN` (no `=value`, so the value never lands on argv — good). 2. `claude_bottle/cli/start.py` (around line 318–325) — claude is launched via `docker exec -it claude …`, which inherits the container PID 1's env, including the token. 3. claude runs as `node` (UID 1000) with `--dangerously-skip-permissions`. Its Bash tool can run `printenv CLAUDE_CODE_OAUTH_TOKEN`, `cat /proc/self/environ`, `node -e 'console.log(process.env)'`, etc. and capture the value into the conversation. A prompt-injection vector — a poisoned skill, a malicious string in a file claude reads, or a hijacked MCP server — can extract the token and exfiltrate it through any allowed outbound channel. The `local-vs-remote-agent-execution.md` note already flags static long-lived tokens as the biggest credential risk; this is exactly that risk, present in the local topology today. ## Hiding env vars "at a deeper level" Linux has no primitive to mark an individual env var as unreadable to the process that holds it. Once a var is in a process's `environ`, the process and its descendants have full access. The deeper-level lever is process boundary, not env-var ACL: put the credential in a *different* process that claude cannot read. Default Docker enforces this for you. The kernel's `ptrace_may_access` check rejects `/proc//environ` reads when the caller's UID/GID don't match the target's and the caller lacks `CAP_SYS_PTRACE` or `CAP_PERFMON`. A `node`-uid claude process attempting to read a root-owned proxy's environ gets `EACCES`. Escape hatches are explicit and not used by claude-bottle: `--cap-add=SYS_PTRACE`, `--cap-add=PERFMON`, `--privileged`. Yama `ptrace_scope` is irrelevant here because it only relaxes the *same-UID* relationship check; the cross-UID UID-match requirement still blocks the read. The `apiKeyHelper` setting in claude-code is **not** a boundary. The helper is invoked by claude's own process, so claude can just call it via Bash and capture stdout. Same trust domain. The only credible designs: - **Header-injecting reverse proxy** — claude points at a localhost URL; proxy holds the credential; proxy adds `Authorization: Bearer` and forwards. (See next section.) - **Network namespace + outbound proxy** — claude runs with `--network none` and a unix-socket proxy that holds the credential and enforces an egress allowlist. Anthropic's secure-deployment docs describe this pattern; the existing research note on remote agents recommends adding it locally first as the highest-leverage change. - **Don't ship the OAuth token at all** — fall back to per-session login or short-lived tokens. Operationally heavier, and the long-lived OAuth token is the chosen design here precisely because it's portable across hosts (Keychain on macOS, file on Linux). ## Proxy auth: viable, with caveats Pattern: - Run a small reverse proxy as **root** inside the container, listening on `127.0.0.1:N` (or a root-owned unix socket with `SO_PEERCRED` checks). - Set `ANTHROPIC_BASE_URL=http://127.0.0.1:N` (or the socket path) in claude's env. Claude as `node` only sees the URL, not the token. - The proxy injects `Authorization: Bearer $TOKEN` and forwards to `https://api.anthropic.com`. - Token lives only in the root proxy's env; node-uid claude cannot read `/proc//environ` (kernel-enforced). `ANTHROPIC_BASE_URL` is documented as routing for proxies/gateways, not just Bedrock/Vertex, and works alongside bearer auth. Confirmed gotchas: - **SSE streaming**: proxy must not buffer responses (nginx `proxy_buffering off`, or use a streaming-aware proxy). Claude Code uses SSE only — no websockets. - **Forward `anthropic-version`, `anthropic-beta`, and `X-Claude-Code-Session-Id` untouched** — stripping them breaks tool use / extended thinking / session aggregation. - **GitHub issue [#36998](https://github.com/anthropics/claude-code/issues/36998)**: interactive mode historically bypassed `ANTHROPIC_BASE_URL` for some startup calls (auth validation / org lookup), connecting directly to `api.anthropic.com`. Marked closed but verify with `tcpdump` or `strace -e connect` against the pinned 2.1.126 build before trusting the isolation. - **Tool search** (`ENABLE_TOOL_SEARCH`): disabled by default when `ANTHROPIC_BASE_URL` is non-Anthropic; re-enable explicitly if needed. - **Out-of-band outbound traffic** is the weak link. None of these route through `ANTHROPIC_BASE_URL`: - `statsig.anthropic.com` — telemetry (disable: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, `DISABLE_TELEMETRY=1`) - Sentry error reporting (disable: `DISABLE_ERROR_REPORTING=1`) - `registry.npmjs.org`, `github.com`, `release-assets.githubusercontent.com` — MCP installs and autoupdater - `pypi.org`, `bun.sh` — if the Bash tool installs Python or Bun packages during a session A hijacked claude could exfiltrate the captured token (or any other data) through these channels even with the proxy in place. Pair the proxy with an explicit egress allowlist (iptables / Docker network policy) for the full benefit. - **Token refresh**: `claude setup-token` issues a ~1-year token with no client-side refresh, so a static proxy value is fine. - **No request signing / anti-replay** on the Messages API; header rewriting is safe. - **`--bare` mode** does not read `CLAUDE_CODE_OAUTH_TOKEN` at all (only `ANTHROPIC_API_KEY`). Not relevant to the interactive flow claude-bottle ships, but worth noting if `--bare` is ever wired in. ## Recommended path forward In priority order: 1. **`--network none` + a localhost (or unix-socket) auth-injecting proxy** that holds the token. Highest-leverage change: credential isolation **and** egress containment in one pass. Aligns with the recommendation already in `local-vs-remote-agent-execution.md`. 2. Layer in `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` plus an explicit egress allowlist (api.anthropic.com only, plus the per-agent set of MCP / git / package-registry hosts) so a hijacked claude can't exfiltrate through statsig / Sentry / npm. The current `docker run -e CLAUDE_CODE_OAUTH_TOKEN` pattern is fine for argv hygiene on the host, but inside the container the token is fully exposed to claude. The proxy pattern moves it across a real kernel-enforced boundary. ## Sources - [Authentication — Claude Code Docs](https://code.claude.com/docs/en/authentication) - [LLM gateway configuration — Claude Code Docs](https://code.claude.com/docs/en/llm-gateway) - [Claude Code Environment Variables](https://code.claude.com/docs/en/env-vars) - [GitHub issue #36998 — Interactive mode ignores ANTHROPIC_BASE_URL](https://github.com/anthropics/claude-code/issues/36998) - [GitHub issue #11587 — Auth conflict: CLAUDE_CODE_OAUTH_TOKEN and apiKeyHelper](https://github.com/anthropics/claude-code/issues/11587) - [proc_pid_environ(5) Linux manual page](https://man7.org/linux/man-pages/man5/proc_pid_environ.5.html) - [Documenting ptrace access mode checking — LWN.net](https://lwn.net/Articles/692203/) - [StepSecurity — Claude Code Action outbound network analysis](https://www.stepsecurity.io/blog/anthropics-claude-code-action-security-how-to-secure-claude-code-in-github-actions-with-harden-runner) - [Manage API key environment variables — Claude Help Center](https://support.claude.com/en/articles/12304248-manage-api-key-environment-variables-in-claude-code)