Skip to content

feat(proxy): add authenticated loopback Codex OAuth adapter - #92750

Open
elkimek wants to merge 3 commits into
NousResearch:mainfrom
elkimek:zofka/codex-proxy-complete
Open

elkimek wants to merge 3 commits into
NousResearch:mainfrom
elkimek:zofka/codex-proxy-complete

Conversation

@elkimek

@elkimek elkimek commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Direct combined replacement for #91199. The first commit preserves Chris Munn’s original Codex OAuth adapter authorship; the following commits add cross-platform downstream-authority hardening.

  • add a loopback OpenAI Codex OAuth proxy backed by Hermes credential-pool entries
  • preserve raw Responses API request bytes, query strings, streaming, retry rotation, and adapter-owned identity headers
  • pin the Codex upstream to the trusted OpenAI base URL
  • require a separate downstream bearer for /health and /v1/* before any credential-pool lookup or upstream request
  • load that bearer from a bounded owner-only regular file
  • enforce current-user ownership and mode 0600 on POSIX
  • enforce current-user-or-SYSTEM ownership and a current-user/SYSTEM-only DACL on Windows; reject null or permissive DACLs
  • reject symlinks, file swaps, wrong ownership, insecure permissions, empty/multiline/oversized tokens, and client identity-header spoofing
  • compare downstream credentials in constant time and never forward them upstream
  • enforce the same authentication boundary through CLI and programmatic server paths

Contributor lineage

  • #91199 by Chris Munn (@BELGARATHbb) is the direct feature origin; his commit remains first with original authorship.
  • #62297 by @darkyy92 is the closest earlier native-Responses and owner-only per-client-authority precedent.
  • #62510 by Dillon Townsel (@dtownsel) provided downstream-bearer precedent; his contribution is credited in the hardening commit.
  • #92409 by @phucnguyenquang overlaps the Codex-proxy/inbound-auth delivery surface.
  • #54877 by @blazing-mj is adjacent shared-pool/Cloudflare-header work with a different Chat-Completions translation design.

This PR is intended as the single hardened delivery object; maintainers can retire the overlapping PRs if this version is accepted.

Security boundary

Loopback limits network reachability, but it does not prove caller authority on a multi-user host. Missing or incorrect downstream credentials return 401 proxy_auth_failed before Hermes reads pool availability, resolves an OAuth bearer, or contacts OpenAI.

On Windows, the already-open file descriptor is inspected with pywin32. Only the current user and SYSTEM may own or receive allowed access. This preserves the existing symlink, swap, regular-file, and content-bound checks while closing the cross-user confused-deputy path.

Verification

Fresh on the rebased branch:

  • scripts/run_tests.sh tests/hermes_cli/test_proxy.py tests/hermes_cli/test_proxy_codex.py34 passed, 2 native-Windows tests skipped locally and selected for the Windows CI lane
  • Ruff check and format check — passed
  • Python bytecode compilation — passed
  • Windows-footgun scan across 1,005 files — passed
  • exact base-vs-head Ruff/Ty diagnostic diff — no branch code-diagnostic delta
  • full English + Chinese Docusaurus production build — passed
  • git diff --check — passed
  • sabotage proof — disabling the downstream-auth guard makes the adversarial regression return HTTP 200 instead of 401
  • live Codex canary from the hardened implementation:
    • unauthenticated /health — HTTP 401 proxy_auth_failed
    • authenticated /health — HTTP 200, upstream authenticated
    • authenticated /v1/responses — HTTP 200, response.completed, exact marker returned

Hosted exact-head CI status is recorded in the PR checks.

No credential value, credential file, or production-service configuration is included.

@elkimek
elkimek force-pushed the zofka/codex-proxy-complete branch from e3eef10 to 8c44278 Compare August 23, 2026 06:25
@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard provider/openai OpenAI / Codex Responses API area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Aug 23, 2026
@elkimek
elkimek force-pushed the zofka/codex-proxy-complete branch 3 times, most recently from f33e1d1 to 02a3cc4 Compare August 23, 2026 07:04

@andrexibiza andrexibiza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking review (GitHub will not permit this identity to set REQUEST_CHANGES without explicit repository review access).

I re-reviewed this as the direct replacement for #91199, including the exact transport/auth hardening rather than inheriting the older verdict. The new downstream-authority half fixes the stop-the-line issue I raised on #91199 for POSIX: /health and /v1/* authenticate before pool inspection or credential resolution, the comparison is constant-time, the client bearer is stripped rather than forwarded, Codex-owned identity headers override client spellings, the upstream is pinned exactly, raw query/body/SSE handling is preserved, and the cancellation/prepare/stream cleanup tests cover the right failure directions.

There is still one cross-platform authority blocker on exact head 02a3cc41376720234605424f1c4dbf749d25d58c:

Windows does not enforce the owner-only token-file boundary

_read_client_auth_token() makes the token file an authorization credential specifically because loopback is only machine-local on a multi-user host. But the ownership/permission checks are entirely inside if os.name != "nt".

On Windows the function currently proves only:

  • the path was a regular file at lstat;
  • it did not resolve through an observed symlink;
  • the opened file has the same (st_dev, st_ino);
  • the contents are bounded/nonempty/single-value.

It never proves who owns the file or who can read it. A perfectly ordinary regular file under a shared location such as C:\Users\Public\codex-proxy.token, or any token file whose inherited DACL grants BUILTIN\Users / Everyone read access, is accepted. Another local OS user can then read the bearer, connect to the loopback listener, and spend the profile owner's ChatGPT/Codex subscription. That recreates the exact confused-deputy boundary this replacement exists to close, just on Windows instead of POSIX.

The tests encode the gap: the 0644 -> reject / 0600 -> accept assertion is skipped on Windows, and there is no Windows DACL/owner witness. The docs call the bearer “owner-only” generally while only explaining POSIX mode enforcement.

Please make the authority invariant cross-platform before landing. Any of these shapes is defensible:

  • inspect the Windows file owner/DACL and reject token files readable by unrelated principals (at minimum broad Users/Everyone read); or
  • provision/validate a token in an OS-owned private location with equivalent DACL proof; or
  • explicitly fail closed / mark this Codex proxy unsupported on Windows until that proof exists.

Add a real Windows regression where a broadly-readable regular token file is refused and a correctly private token file is accepted. The load path must still retain the current symlink/swap/bounds checks.

Repository topology / credit

The consolidation direction is right and contributor preservation is real: the first commit feb202d23844e32a5cd4ddf06d45d7a6451c66b5 retains Chris Munn's author identity from #91199, and the hardening commit credits Dillon Townsel from #62510. Please also make the older authority lineage explicit when retiring competing delivery objects:

  • #62297 by @darkyy92 is the closest earlier native-Responses + owner-only per-client-key implementation for multi-user hosts; superseded as delivery, but directly relevant authority provenance.
  • #62510 by @dtownsel is downstream-bearer precedent with older token-file/refresh defects; its useful auth shape is carried here and credit is already preserved in the hardening commit.
  • #92409 by @phucnguyenquang is overlapping Codex-proxy/inbound-auth work and should not remain a second delivery owner if this lands.
  • #54877 by @blazing-mj is older shared-pool/Cloudflare-header work with a Chat-Completions translation choice; adjacent/superseded rather than a byte-level duplicate.
  • #91199 remains the direct feature origin and should close as superseded by this combined replacement once the hardened object lands.

Exact-object evidence

Current upstream main is 4553e71993dbeb21449f1ef4d6fad069adb20915; the actual merge base is 32a8a7031e8248b05db8142b0d7349616fda71fa, so this head is 2 commits ahead / 3 behind. Those three main commits only touch update/update-receipt tests and do not overlap this proxy diff, but rebase before final acceptance.

Exact-head Docker 32624589313 and Nix 32624589264 are green. CI 32624589728 is cancelled, not green: Detect affected areas was cancelled and the Python, OS-specific, lint, supply-chain, docs, and other substantive lanes were consequently skipped. The aggregate All required checks pass job succeeding after that cancellation is not evidence that those skipped lanes ran. After the Windows authority repair/rebase, require fresh exact-head CI in addition to Docker/Nix.

Once the Windows token-file authority is real and the resulting exact object has complete hosted execution, the rest of this combined replacement looks like the correct canonical Codex proxy shape.

@elkimek
elkimek force-pushed the zofka/codex-proxy-complete branch from 02a3cc4 to 59124ba Compare August 23, 2026 08:39
@elkimek

elkimek commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the Windows authority blocker in final rebased head 326eadd135f80faa0d8d9642916bbfd8b0e5826d. The tree is identical to 57e87dca23cd639e80cbca12bc69fa78c93fd907; only the Windows-security commit body was expanded to generate a clean CI synchronize event after GitHub refused a direct rerun. The opened token-file descriptor is verified with pywin32: owner must be the current user or SYSTEM, null DACLs are rejected, and every nonzero allow ACE must belong to the current user or SYSTEM. Native Windows regressions create a protected private DACL that is accepted and an Everyone-readable DACL that is refused; the existing symlink/swap/bounds checks remain in the same load path. The branch is rebased onto upstream main 503d863fcd2cbfc0be5a6d6c536fae2e98aa4204 (0 behind / 3 ahead). Local evidence: 34 proxy tests passed; the previously flaky unrelated Telegram e2e also passes locally; Ruff/format/compile/diff checks, Windows-footgun scan, exact lint diff, and multilingual docs build passed. The content-equivalent bfba30… head already passed native Windows and all substantive hosted lanes. Hosted exact-head CI is rerunning.

@elkimek
elkimek force-pushed the zofka/codex-proxy-complete branch from 59124ba to bfba30a Compare August 23, 2026 08:48
Chris Munn and others added 2 commits August 23, 2026 10:54
Require an owner-only regular token file before loopback clients can spend the profile-owned Codex subscription. Enforce the same boundary in CLI and programmatic server startup, compare bearer credentials in constant time, and prove unauthorized callers never resolve or contact the upstream.

Co-authored-by: Dillon Townsel <dillontownsel@gmail.com>
@elkimek
elkimek force-pushed the zofka/codex-proxy-complete branch from bfba30a to 57e87dc Compare August 23, 2026 08:55
Verify the opened token-file descriptor is owned by the current user or SYSTEM and reject null or permissive DACLs. Add native-Windows regressions for private and Everyone-readable files while preserving the existing symlink, file-swap, and content-bound checks.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants