Debugging a live codex smolmachines bottle surfaced three independent
failures past the sign-in screen; fix each so forward_host_credentials
works end to end:
- codex_auth: dummy access/id tokens now inherit the *real* host token's
exp instead of now+1h. Codex (0.135) refreshes when its local token's
JWT exp lapses; with a placeholder refresh_token that refresh fails and
drops to the sign-in screen. Aligning exp tracks the real token's life.
- prepare: set CODEX_CA_CERTIFICATE to the agent CA bundle for codex
bottles. Codex is rustls and ignores the system store / NODE_EXTRA_CA_
CERTS; it reads CODEX_CA_CERTIFICATE (fallback SSL_CERT_FILE) for custom
roots across HTTPS + wss, so it must be pointed at the egress MITM CA or
injection can't work without tls_passthrough.
- pipelock: auto tls_passthrough the Codex API hosts when
forward_host_credentials is on. Egress injects the bearer before
pipelock, whose header DLP then flags the JWT ("request header contains
secret") and the retry storm trips its 429. passthrough host-gates the
CONNECT but skips decrypt+rescan of egress-owned auth. The auto-added
routes aren't in bottle.egress.routes, so the hosts are added explicitly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.1 KiB
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 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.comandchatgpt.comendpoints 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 realtokens.access_token/tokens.refresh_tokenvalues. - The agent container receives only a dummy Codex
auth.jsonthat 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.jsoncredentials into the agent. - Allowing arbitrary host credential forwarding. This PRD covers Codex ChatGPT/device-login credentials only.
- 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_credentialsto the bottle manifest schema, defaulting tofalse. - Support the flag for
agent_provider.template: codex. - Read host Codex auth from
$CODEX_HOME/auth.jsonwhenCODEX_HOMEis set, otherwise from~/.codex/auth.json. - Extract only
tokens.access_tokenfor egress injection. - Generate a dummy agent-side
auth.jsonfrom 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.comandchatgpt.comegress routes to inject that access token via a sharedEGRESS_TOKEN_Nsidecar 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:
- Resolve the host Codex home directory from
$CODEX_HOME, falling back to~/.codex. - Parse
auth.json. - Require user/device auth mode rather than API-key auth.
- Require a non-empty
tokens.access_token. - Parse the JWT payload enough to require an
expclaim in the future. - 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
- PRD first. Land this document as the first commit on the feature branch.
- Manifest schema. Add
forward_host_credentials, validation, and unit tests. - Host Codex auth reader. Add a small stdlib-only helper for parsing and validating host Codex auth without printing values.
- 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.
- Agent auth marker. Provision a dummy Codex
auth.jsoninto the agent home so Codex selects the host's user/device auth branch while real credentials stay in egress. - Launch wiring. Pass the host access token into the egress sidecar env for Docker and smolmachines without exposing it to the agent.
- 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.comstay 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.