diff --git a/docs/prds/0029-codex-host-credentials-egress.md b/docs/prds/0029-codex-host-credentials-egress.md new file mode 100644 index 0000000..a26bb35 --- /dev/null +++ b/docs/prds/0029-codex-host-credentials-egress.md @@ -0,0 +1,169 @@ +# PRD 0029: Codex host credentials through egress + +- **Status:** Draft +- **Author:** didericis-codex +- **Created:** 2026-05-29 +- **Issue:** #109 + +## Summary + +Allow Codex bottles to use a host-authorized ChatGPT/device-login +access token by forwarding it only into the egress sidecar, gated by an +explicit `agent_provider.forward_host_credentials` manifest flag. + +## Problem + +Codex bottles can reach `chatgpt.com` after the host is added to egress, +but requests to `chatgpt.com/backend-api/codex/...` still fail with +HTTP 403. The egress proxy strips agent-originated `Authorization` +headers and only re-injects auth for routes that declare an egress-owned +token. A bare `chatgpt.com` route therefore forwards Codex requests +without the ChatGPT bearer token. + +Copying `~/.codex/auth.json` into the agent would solve auth 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 + `chatgpt.com/backend-api/codex/...` 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`, `tokens.access_token`, `tokens.refresh_token`, + or `auth.json`. +- Egress route files remain non-secret: they contain only host/path/auth + slot metadata, never token values. +- Missing, non-ChatGPT, 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` into the agent. +- Allowing arbitrary host credential forwarding. This PRD covers Codex + ChatGPT/device-login credentials only. +- Hot-applying a new authenticated `chatgpt.com` route 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`. +- Read host Codex auth from `$CODEX_HOME/auth.json` when `CODEX_HOME` is + set, otherwise from `~/.codex/auth.json`. +- Extract only `tokens.access_token`. +- Validate that `auth_mode` is `chatgpt` and the access token is present, + JWT-shaped, and not expired. +- Add or upgrade a `chatgpt.com` egress route to inject that access token + via an `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 `auth_mode == "chatgpt"`. +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 an authenticated `chatgpt.com` route. If the bottle +already declares `chatgpt.com` 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 `chatgpt.com` route, +fail rather than guessing whether to override operator-provided auth. + +The rendered route should look like any other egress-owned auth route: + +```yaml +routes: + - 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"] + A["Codex agent"] -->|HTTPS via proxy, auth stripped| E["egress"] + E -->|Bearer injected from env| C["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 `chatgpt.com` route when + the flag is enabled, and add tests for bare route upgrade, + missing-route insertion, and authenticated-route conflict. +5. **Launch wiring.** Pass the host access token into the egress sidecar + env for Docker and smolmachines without exposing it to the agent. +6. **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.