Files
bot-bottle/docs/prds/0029-codex-host-credentials-egress.md
didericis-codex eb6bace84f
test / unit (pull_request) Successful in 35s
test / integration (pull_request) Successful in 42s
complete(prd): mark PRD 0029 active
2026-06-02 03:00:47 +00:00

7.8 KiB

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:

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:

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

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.