Skip to content

Security: /v1/responses is unauthenticated and can silently use ChatGPT auth.json #30

Description

@ahimawari

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:

  1. Call POST http://127.0.0.1:8765/v1/responses with a native model slug.
  2. 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.
  3. 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:

  1. 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
  2. Do not auto-issue the admin cookie on unauthenticated dashboard GETs. Require an explicit local unlock / pairing step first.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions