177 lines
6.5 KiB
Markdown
177 lines
6.5 KiB
Markdown
# 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 `~/.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 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`, `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 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`.
|
|
- 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 `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 `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 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"]
|
|
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. **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.
|