Summary
The local gateway binds to 127.0.0.1, but POST /v1/responses (and related native proxy routes) do not require admin authentication. Combined with native passthrough that can substitute the ChatGPT access token from ~/.codex/auth.json, any process running as the same user can consume the user's ChatGPT/Codex subscription and stored third-party provider credentials without presenting the OpenCodex admin token.
/api/* is protected by requireAdmin, but the inference surface is not.
Why this is urgent
On a shared machine, compromised browser extension host process, malicious npm script, or other same-user malware, this turns OpenCodex into an unauthenticated local credential proxy:
- Call
POST http://127.0.0.1:8765/v1/responses with a native model slug.
- If the request carries a local/placeholder bearer (or the gateway treats it as such), native passthrough can inject the real ChatGPT token from
~/.codex/auth.json.
- For imported/configured third-party models, the gateway resolves provider keys / subscription tokens server-side and forwards upstream.
This bypasses the admin boundary that the dashboard and /api/* otherwise enforce.
Evidence in current source
1. Admin auth only gates /api/*
In src_v2/server/gateway.ts, requireAdmin is applied to /api/ routes, then /v1/responses is handled without an equivalent check:
if (url.pathname.startsWith("/api/") && !this.requireAdmin(req, res)) {
return;
}
// ... realtime / dashboard / images ...
if (req.method === "POST" && (url.pathname === "/v1/responses" || url.pathname === "/responses")) {
// no requireAdmin() here
2. Native passthrough can upgrade local bearer to ChatGPT token
In src_v2/server/webrtc_proxy.ts, copyNativeRequestHeaders() reads ~/.codex/auth.json and replaces a local/placeholder Authorization header with the native ChatGPT access token for native-session requests.
3. Related weak admin bootstrap
GET / and GET /dashboard call issueAdminCookie(res) unconditionally, so visiting the dashboard sets the admin cookie without an authentication challenge. That weakens the local admin boundary further for browser-capable local attackers.
Suggested fix
Minimum hardening:
- Require admin/capability auth on all privileged routes, including:
POST /v1/responses
/responses/compact
/v1/images/*
- realtime / live / backend-api proxy paths that can use native ChatGPT credentials
- Do not auto-issue the admin cookie on unauthenticated dashboard GETs. Require an explicit local unlock / pairing step first.
- Fail closed on native passthrough: never substitute
auth.json tokens for unauthenticated or placeholder callers; only forward credentials already legitimately presented by Codex, or require a dedicated native capability.
- Add regression tests asserting unauthenticated
/v1/responses returns 401 and never contacts upstream with ChatGPT credentials.
Optional follow-ups:
- Reject browser
Origin / Sec-Fetch-Site on mutation endpoints (similar to capability-isolated local routers).
- Keep admin token out of plaintext
experimental_bearer_token in config.toml where practical; prefer a file reference with 0600 permissions.
Impact
- Confidentiality: ChatGPT session token and third-party keys can be exercised by any same-user local client.
- Integrity / availability: attacker can burn subscription quota, trigger provider spend, and drive model calls without user interaction.
- Scope: local attacker / malware with same OS user privileges; not remote-by-default because the gateway listens on loopback. Still severe on developer machines.
Environment
Observed in public source for OpenCodex v1.2.0 (src_v2/server/gateway.ts, src_v2/server/webrtc_proxy.ts).
Happy to adjust the report if maintainers prefer a private security channel for coordinated disclosure.
Summary
The local gateway binds to
127.0.0.1, butPOST /v1/responses(and related native proxy routes) do not require admin authentication. Combined with native passthrough that can substitute the ChatGPT access token from~/.codex/auth.json, any process running as the same user can consume the user's ChatGPT/Codex subscription and stored third-party provider credentials without presenting the OpenCodex admin token./api/*is protected byrequireAdmin, but the inference surface is not.Why this is urgent
On a shared machine, compromised browser extension host process, malicious npm script, or other same-user malware, this turns OpenCodex into an unauthenticated local credential proxy:
POST http://127.0.0.1:8765/v1/responseswith a native model slug.~/.codex/auth.json.This bypasses the admin boundary that the dashboard and
/api/*otherwise enforce.Evidence in current source
1. Admin auth only gates
/api/*In
src_v2/server/gateway.ts,requireAdminis applied to/api/routes, then/v1/responsesis handled without an equivalent check:2. Native passthrough can upgrade local bearer to ChatGPT token
In
src_v2/server/webrtc_proxy.ts,copyNativeRequestHeaders()reads~/.codex/auth.jsonand replaces a local/placeholder Authorization header with the native ChatGPT access token for native-session requests.3. Related weak admin bootstrap
GET /andGET /dashboardcallissueAdminCookie(res)unconditionally, so visiting the dashboard sets the admin cookie without an authentication challenge. That weakens the local admin boundary further for browser-capable local attackers.Suggested fix
Minimum hardening:
POST /v1/responses/responses/compact/v1/images/*auth.jsontokens for unauthenticated or placeholder callers; only forward credentials already legitimately presented by Codex, or require a dedicated native capability./v1/responsesreturns401and never contacts upstream with ChatGPT credentials.Optional follow-ups:
Origin/Sec-Fetch-Siteon mutation endpoints (similar to capability-isolated local routers).experimental_bearer_tokeninconfig.tomlwhere practical; prefer a file reference with0600permissions.Impact
Environment
Observed in public source for OpenCodex
v1.2.0(src_v2/server/gateway.ts,src_v2/server/webrtc_proxy.ts).Happy to adjust the report if maintainers prefer a private security channel for coordinated disclosure.