# PRD 0029: Provider auth credentials through egress - **Status:** Active - **Author:** didericis-codex - **Created:** 2026-05-29 - **Issue:** #109 ## Summary Allow provider bottles to inject host credentials into the egress sidecar without exposing them to the agent. Codex uses `agent_provider.forward_host_credentials` for ChatGPT/device-login access tokens. Claude uses `agent_provider.auth_token` to name the host env var holding its OAuth token, which egress injects on `api.anthropic.com` requests. ## Problem Codex bottles can reach OpenAI hosts after they are added to egress, but requests to Codex's ChatGPT-backed API endpoints still fail with HTTP 403 when the egress route is unauthenticated. The egress proxy strips agent-originated `Authorization` headers and only re-injects auth for routes that declare an egress-owned token. Bare `api.openai.com` or `chatgpt.com` routes therefore forward Codex requests without the ChatGPT bearer token. Copying the host `~/.codex/auth.json` into the agent would solve auth mode detection but would also put access and refresh material inside the agent sandbox. That cuts against bot-bottle's credential minimization model: provider credentials should live in the sidecar boundary when possible, not in the agent. ## Goals / Success Criteria - A Codex bottle with host ChatGPT auth can call Codex's `api.openai.com` and `chatgpt.com` endpoints through egress. - Host credential forwarding happens only when the bottle declares `agent_provider.forward_host_credentials: true`. - The agent container does not receive `OPENAI_API_KEY`, `CODEX_ACCESS_TOKEN`, or real `tokens.access_token` / `tokens.refresh_token` values. - The agent container receives only a dummy Codex `auth.json` that preserves the host auth-mode shape, keeps the selected ChatGPT account id, and replaces credential values with placeholders. - Egress route files remain non-secret: they contain only host/path/auth slot metadata, never token values. - Missing, API-key, malformed, or expired host Codex auth fails launch with a clear operator-facing message. - Existing Claude OAuth placeholder behavior remains unchanged. ## Non-goals - Refreshing Codex tokens in the sidecar. The first cut reads the host's current access token at launch; operators can restart after host Codex refreshes auth. - Copying host `~/.codex/auth.json` credentials into the agent. - Allowing arbitrary host credential forwarding beyond the two providers covered here (Codex ChatGPT/device-login and Claude OAuth). - Hot-applying new authenticated Codex routes to an existing running sidecar. The current hot-apply path cannot safely populate new token env slots in an already-running container. ## Scope ### In scope - Add `agent_provider.forward_host_credentials` to the bottle manifest schema, defaulting to `false`. - Support the flag for `agent_provider.template: codex`. - Add `agent_provider.auth_token` to the bottle manifest schema. - Support the field for `agent_provider.template: claude`: the named host env var is forwarded only into the egress sidecar as the Bearer token for `api.anthropic.com`, and a placeholder `CLAUDE_CODE_OAUTH_TOKEN` is set in the agent so the Claude Code CLI starts without a real credential. - Remove the `claude_code_oauth` egress route role, which previously required operators to declare the OAuth route manually. The provisioner now injects it from `auth_token`. - Read host Codex auth from `$CODEX_HOME/auth.json` when `CODEX_HOME` is set, otherwise from `~/.codex/auth.json`. - Extract only `tokens.access_token` for egress injection. - Generate a dummy agent-side `auth.json` from the host auth file's mode and key shape, without copying real token values. - Validate that host auth is not API-key mode and the access token is present, JWT-shaped, and not expired. - Add or upgrade `api.openai.com` and `chatgpt.com` egress routes to inject that access token via a shared `EGRESS_TOKEN_N` sidecar env slot. - Pass the extracted token only into the sidecar compose/run environment, alongside other egress token values. ### Out of scope - Sidecar-owned refresh using `tokens.refresh_token`. - Sharing full Codex auth state with the agent. - Supporting host credential forwarding for non-Codex providers. ## Design ### Manifest Extend `agent_provider`: ```yaml agent_provider: template: codex forward_host_credentials: true ``` The field defaults to `false`. If set on a non-Codex provider, manifest validation should reject it until that provider has a concrete, credential-minimizing implementation. ### Host auth extraction At prepare/launch time, when the flag is enabled for Codex: 1. Resolve the host Codex home directory from `$CODEX_HOME`, falling back to `~/.codex`. 2. Parse `auth.json`. 3. Require user/device auth mode rather than API-key auth. 4. Require a non-empty `tokens.access_token`. 5. Parse the JWT payload enough to require an `exp` claim in the future. 6. Return only the access token value to the launch path. Errors should name the missing or invalid condition and point the operator at `codex login --device-auth`, without printing token values. ### Egress route When forwarding host Codex credentials, the effective egress route table should contain authenticated `api.openai.com` and `chatgpt.com` routes. If the bottle already declares either host as a bare-pass route, upgrade it in the effective route table rather than requiring a duplicate manifest entry. If the bottle already declares an authenticated route for either host, fail rather than guessing whether to override operator-provided auth, unless that route already uses the synthetic Codex host credential token reference. The rendered route should look like any other egress-owned auth route: ```yaml routes: - host: "api.openai.com" auth_scheme: "Bearer" token_env: "EGRESS_TOKEN_N" - host: "chatgpt.com" auth_scheme: "Bearer" token_env: "EGRESS_TOKEN_N" ``` The access token value is supplied through the sidecar process environment for that `EGRESS_TOKEN_N` slot. It must not be written to `routes.yaml`, compose files, env files, logs, or user-facing output. ### Data flow ```mermaid flowchart LR H["Host ~/.codex/auth.json"] --> L["bot-bottle launch"] L -->|access token only| S["egress sidecar env"] L -->|dummy auth.json only| A A["Codex agent"] -->|HTTPS via proxy, auth stripped| E["egress"] E -->|Bearer injected from env| C["api.openai.com / chatgpt.com"] ``` ## Implementation chunks 1. **PRD first.** Land this document as the first commit on the feature branch. 2. **Manifest schema.** Add `forward_host_credentials`, validation, and unit tests. 3. **Host Codex auth reader.** Add a small stdlib-only helper for parsing and validating host Codex auth without printing values. 4. **Effective egress route.** Add/upgrade the Codex API routes when the flag is enabled, and add tests for bare route upgrade, missing-route insertion, and authenticated-route conflict. 5. **Agent auth marker.** Provision a dummy Codex `auth.json` into the agent home so Codex selects the host's user/device auth branch while real credentials stay in egress. 6. **Launch wiring.** Pass the host access token into the egress sidecar env for Docker and smolmachines without exposing it to the agent. 7. **Docs and tests.** Update README examples and run the unit suite. ## Open questions - Should a later version support sidecar refresh using the host refresh token, or should restart-on-expiry remain the policy? - Should telemetry hosts such as `ab.chatgpt.com` stay blocked by default even when Codex ChatGPT auth is enabled? ## References - Gitea issue #109: Codex ChatGPT auth should inject host access token via egress. - PRD 0017: Egress-proxy — universal MITM with path filtering + auth injection. - PRD 0026: Agent provider templates.