Skip to content

fix(codex-auth): reject malformed Codex CLI imports - #9173

Open
MestreY0d4-Uninter wants to merge 3 commits into
NousResearch:mainfrom
MestreY0d4-Uninter:fix/codex-pool-identity-guard
Open

fix(codex-auth): reject malformed Codex CLI imports#9173
MestreY0d4-Uninter wants to merge 3 commits into
NousResearch:mainfrom
MestreY0d4-Uninter:fix/codex-pool-identity-guard

Conversation

@MestreY0d4-Uninter

@MestreY0d4-Uninter MestreY0d4-Uninter commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Rebuilds #9173 against current main as a much narrower Codex auth hardening change.

The remaining live boundary is the explicit Codex CLI import path used by hermes auth openai-codex:

~/.codex/auth.json -> Hermes auth store

Before this change, _import_codex_cli_tokens() accepted any truthy access_token / refresh_token pair from the Codex CLI shared file. If the shared file contained placeholder or otherwise malformed access-token values, Hermes could present them as importable credentials instead of falling through to a fresh device-code login.

Changes

  • Add a structural Codex access-token guard: importable CLI tokens must decode as a JWT with a numeric exp claim.
  • Keep the existing expired-token rejection after the new structural check.
  • Preserve valid JWT import behavior.
  • Add regression coverage for valid imports and malformed/non-JWT placeholder rejection.
  • Remove unused imports in the touched Codex auth test file so explicit changed-file lint is clean.

Scope update from the older PR state

Earlier versions of this PR also carried credential-pool sync hardening. I intentionally dropped that part during this refresh because current main no longer auto-imports ~/.codex/auth.json into the pool at pool-load time. Reintroducing that path would conflict with the current design, which keeps Hermes-owned Codex auth state separate from Codex CLI / VS Code sessions to avoid single-use refresh-token races.

So this refresh keeps only the still-reachable explicit CLI import guard.

Risk classification

Primary risk: auth_secrets

Rationale: the changed code gates whether a local Codex CLI credential blob is imported into Hermes auth state. The new behavior is fail-closed for malformed access tokens and falls through to fresh login when the shared file is not importable.

Validation

Local validation on the refreshed branch:

  • uv run ruff check .
    • passed; repo config currently excludes Python files via [tool.ruff] exclude = ["*"]
  • uv run ruff format --check .
    • passed; same repo config warning
  • uv run ruff check hermes_cli/auth.py tests/hermes_cli/test_auth_codex_provider.py
    • passed
  • python -m py_compile hermes_cli/auth.py tests/hermes_cli/test_auth_codex_provider.py
    • passed
  • uv run pytest -o addopts='' -q tests/hermes_cli/test_auth_codex_provider.py
    • 19 passed
  • uv run pytest -o addopts='' -q tests/hermes_cli/test_codex_cli_model_picker.py tests/hermes_cli/test_runtime_provider_resolution.py tests/agent/test_auxiliary_client.py::TestReadCodexAccessToken
    • 86 passed

Independent review gate:

  • high-risk auth/secrets review: PASS
  • coverage/impact review: PASS
  • final post-amend review: PASS

Notes

This PR is now intentionally smaller than the stale branch it replaces. The old credential-pool helper was not wired into current main runtime paths, so it was treated as dead/stale code rather than reconstructed.

@RuckVibeCodes

Copy link
Copy Markdown

[gus-first-pass] This PR addresses a significant validation issue in our multi-account Codex credential pools and the implementation looks thorough. However, I suggest the following considerations: \n\n1. Identity Checks: Ensure that the identity verification logic is robust enough to handle all edge cases, especially in session contexts where multiple accounts might interact. \n\n2. Testing: Comprehensive tests should confirm the integrity of the identity checks and their interactions with existing credentials. Providing coverage for both valid and invalid scenarios is essential. \n\nThe direction of fixing the credential sync is solid. Thank you for addressing this critical bug.** Overall, the changes proposed seem beneficial.**

@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch 2 times, most recently from 473d090 to 114403d Compare April 14, 2026 11:31
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Re-audited against current origin/main in a clean worktree.

This fix still addresses a real bug, remains a clean delta on top of current main, and the current branch is in good shape for merge:

  • merge state is CLEAN
  • all current checks are green (check-attribution, supply-chain, e2e, test, build-and-push)
  • the identity guard is still not present in origin/main

Given the corruption risk here (cross-account overwrite of Codex pool entries), I believe this one is ready for review/merge.

@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch from 114403d to cc7b8c3 Compare April 14, 2026 15:31
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Final validation pass on current main base is complete.

What I rechecked:

  • refreshed branch uses my GitHub-linked noreply identity (MestreY0d4-Uninter@users.noreply.github.com)
  • clean worktree validation
  • real tmux-based run in a clean Hermes environment (HERMES_HOME temp dir)
  • focused regression tests
  • current GitHub checks

Concrete results:

  • manual mismatch probe kept the pool entry unchanged
  • manual verified-identity probe synced tokens as expected
  • uv run pytest -o addopts='' tests/agent/test_credential_pool.py -q -k 'codex_cli_sync_rejects_mismatched_identity or codex_cli_sync_allows_verified_identity or mark_success_updates_pool_entry_and_persists'
  • result: 3 passed
  • current GitHub checks: all green

This one is ready for review/merge.

@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch from cc7b8c3 to 55b4008 Compare April 16, 2026 14:30
@MestreY0d4-Uninter MestreY0d4-Uninter changed the title fix(codex-pool): guard cross-account overwrite in _sync_codex_entry_from_cli fix(codex-auth): harden shared CLI import and pool sync boundary Apr 16, 2026
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Refreshed this branch on top of current origin/main and narrowed the PR to the Codex shared-auth anti-corruption boundary only.

What changed in the refresh:

  • added CLI import hardening in hermes_cli/auth.py
  • kept the pool sync hardening in agent/credential_pool.py
  • dropped unrelated branch noise from the previous stale PR state

Re-validation on the refreshed branch:

  • focused pytest slices for Codex auth / model picker / runtime provider / credential pool / auxiliary-client fallback paths
  • isolated tmux repro confirming:
    • placeholder shared-file imports are rejected
    • device_code does not get re-seeded on reload
    • cross-account CLI sync is refused and the pool entry keeps its original identity

Updated PR title/body to reflect the broader but still focused scope.

@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Added one operational follow-up commit to this PR after refreshing #9758:

  • chore(ci): map MestreY0d4-Uninter noreply for attribution check

Why this is here:

  • check-attribution was the only red check on this PR
  • the missing mapping is unrelated to the Codex auth/pool hardening itself
  • I refreshed chore: add MestreY0d4-Uninter noreply to attribution mappings #9758 separately, but that PR cannot be merged from this account and currently has unrelated red test failures outside its 1-line AUTHOR_MAP scope

So I copied the same minimal scripts/release.py mapping into this branch to make the CI state self-contained.

Local validation on this branch:

  • reproduced the workflow logic against the branch commit set
  • MestreY0d4-Uninter@users.noreply.github.com is now mapped
  • local equivalent result: attribution_check=PASS

@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Refresh rebuilt from origin/main and pushed to refresh/9173. Validation: py_compile on changed .py files passed; focused pytest on changed test files ran and hit 4 failures in tests/agent/test_credential_pool.py related to missing agent.credential_pool._import_codex_cli_tokens.

@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Follow-up refresh fix:

  • restored a local _import_codex_cli_tokens alias in agent/credential_pool.py so the refreshed branch still exposes the monkeypatch boundary expected by the focused credential-pool tests while delegating to hermes_cli.auth under the hood
  • reran focused validation after the fix:
    • /home/ubuntu/hermes-all-venv/bin/python -m py_compile agent/credential_pool.py tests/agent/test_credential_pool.py tests/hermes_cli/test_auth_codex_provider.py
    • /home/ubuntu/hermes-all-venv/bin/python -m pytest -o addopts= -q tests/agent/test_credential_pool.py tests/hermes_cli/test_auth_codex_provider.py\n - 47 passed\n\nSo the branch is now refreshed on current origin/main and the relevant Codex auth/pool suite is green again.

@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch from 22135c2 to f800a3a Compare April 19, 2026 16:34
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Audit completed (2026-04-19)

  • Branch refreshed from current origin/main
  • Focused tests: 47/47 PASSING (after minimal fix in credential_pool.py)
  • Impact: High (hardens credential pool + codex auth boundary)
  • Risk: Low-Medium (452 lines, but well-delimited)

Recommendation: MERGE — critical auth hardening.


Part of batch audit: 23 PRs audited, 2 closed (absorbed), 21 refreshed

@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

🔔 Ready for maintainer review

Esta PR foi validada como parte da auditoria completa de 2026-04-19.

Status:

  • ✅ Refreshed from origin/main (SHA: 6af04474)
  • ✅ Focused tests passing
  • ✅ Comentários de validação adicionados
  • ✅ Baixo risco de merge

Ação necessária: Review e merge pelos mantenedores.


Audit batch: 7 PRs de alto impacto validadas e prontas para merge

@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch from f800a3a to 0b6a29f Compare April 23, 2026 23:41
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P2 Medium — degraded but workaround exists area/auth Authentication, OAuth, credential pools provider/copilot GitHub Copilot (ACP + Chat) labels Apr 23, 2026
@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch from 0b6a29f to 2f04fa6 Compare April 25, 2026 15:06
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Audit/update 2026-04-25:

  • Force-pushed 0b6a29ff -> 2f04fa6b with author metadata only; tree content is unchanged.
  • Focused local validation: 47 passed in 1.56s (credential pool + Codex auth tests).
  • Recommendation: Author metadata rewritten to canonical noreply; high-risk auth/credential-boundary review still recommended.

This was part of the open-PR cleanup pass against current upstream/main.

@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch from 2f04fa6 to 9cfb5b1 Compare April 25, 2026 17:14
@MestreY0d4-Uninter MestreY0d4-Uninter changed the title fix(codex-auth): harden shared CLI import and pool sync boundary fix(codex-auth): reject malformed Codex CLI imports Apr 25, 2026
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Refreshed/rebuilt this PR against current main and force-pushed the minimized branch.

Key change from the older branch:

  • Dropped the credential-pool sync helper portion after re-review. On current main, Hermes no longer auto-imports ~/.codex/auth.json into the pool at pool-load time, and the helper was not wired into any runtime path. Reintroducing that behavior would conflict with the current separate-session design for Codex auth.
  • Kept the still-reachable explicit CLI import hardening in hermes_cli/auth.py: Codex CLI shared-file imports now require the access token to decode as a JWT with a numeric exp claim before import.

Validation on the refreshed branch:

  • uv run ruff check . — passed; repo config excludes Python files via [tool.ruff] exclude = ["*"]
  • uv run ruff format --check . — passed; same warning
  • uv run ruff check hermes_cli/auth.py tests/hermes_cli/test_auth_codex_provider.py — passed
  • python -m py_compile hermes_cli/auth.py tests/hermes_cli/test_auth_codex_provider.py — passed
  • uv run pytest -o addopts='' -q tests/hermes_cli/test_auth_codex_provider.py — 19 passed
  • uv run pytest -o addopts='' -q tests/hermes_cli/test_codex_cli_model_picker.py tests/hermes_cli/test_runtime_provider_resolution.py tests/agent/test_auxiliary_client.py::TestReadCodexAccessToken — 86 passed

Independent review gate after minimization:

  • high-risk auth/secrets review: PASS
  • coverage/impact review: PASS
  • final post-amend review: PASS

Risk classification remains auth_secrets, but the refreshed diff is now two files and only covers the explicit CLI import boundary.

@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

CI Evidence — Baseline Noise

Passed: check, check-attribution, nix ubuntu, nix macos, supply-chain
Failed: test, e2e

All test failures reproduce on clean upstream/main — same baseline pattern:

  • Discord adapter: SimpleNamespace missing guild attribute
  • Other unrelated: PTY, web_server, hindsight, tool_arg_coercion

Local focused validation: 105 tests passed

  • test_auth_codex_provider.py: 19 passed
  • test_codex_cli_model_picker.py, test_runtime_provider_resolution.py, test_auxiliary_client.py::TestReadCodexAccessToken: 86 passed
  • ruff / py_compile OK; independent auth/secrets review passed

No regression from this PR.

@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Refreshed this PR onto current main with a normal non-force push.

Current PR scope remains limited to the Codex CLI import guard:

  • hermes_cli/auth.py
  • tests/hermes_cli/test_auth_codex_provider.py

Validation:

  • python3 -m py_compile hermes_cli/auth.py tests/hermes_cli/test_auth_codex_provider.py -> passed
  • HERMES_TEST_WORKERS=2 scripts/run_tests.sh tests/hermes_cli/test_auth_codex_provider.py tests/hermes_cli/test_codex_cli_model_picker.py tests/hermes_cli/test_runtime_provider_resolution.py tests/agent/test_auxiliary_client.py::TestReadCodexAccessToken -q -> 109 passed
  • Post-push PR diff file list verified: 2 scoped files only
  • Attribution, check, e2e, Nix, and supply-chain checks are green

The remaining broad Tests workflow is still red. Its failure profile is dominated by current main baseline failures; the PR-only failures observed are outside this PR's Codex-auth scope and the exact PR-only failing tests pass locally in the refreshed worktree.

@MestreY0d4-Uninter
MestreY0d4-Uninter force-pushed the fix/codex-pool-identity-guard branch from a545a85 to 23e032e Compare May 8, 2026 01:31
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for narrowing this to the shared Codex CLI import boundary. Current main still accepts arbitrary truthy access/refresh values in hermes_cli/auth.py:3600-3612; the helper is used by both explicit import (hermes_cli/auth.py:6912) and refresh-token recovery (hermes_cli/auth.py:3391). The proposed structural JWT/exp check therefore addresses the live boundary without adding new configuration or widening runtime behavior.

The focused regression coverage is appropriate for the shared helper, and I found no blocking correctness or design-fit issue in the two-file diff.

Automated hermes-sweeper review.

@alt-glitch alt-glitch added comp/cli CLI entry point, hermes_cli/, setup wizard provider/openai OpenAI / Codex Responses API sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data needs-repro Bug needs reproduction steps P3 Low — cosmetic, nice to have and removed provider/copilot GitHub Copilot (ACP + Chat) P2 Medium — degraded but workaround exists labels Jul 12, 2026
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
@alt-glitch alt-glitch added comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint and removed sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jul 12, 2026
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Refreshed onto current main (normal merge commit, no force-push) so CI regenerates against the now-green base — the earlier test/e2e reds on this PR predated the recent main fixes.

Per the sweeper review there is no blocking correctness or design-fit issue in the two-file diff, so no code change was made in this refresh.

Validation on the refreshed branch (hermetic runner scripts/run_tests.sh):

  • tests/hermes_cli/test_auth_codex_provider.py + test_codex_cli_model_picker.py + test_runtime_provider_resolution.py: 190 passed, 0 failed
  • ruff check and Windows-footguns scan on touched files: clean

Note on CI: the ruff + ty diff job's analysis steps pass; only its "Post / update PR comment" step fails from fork context (Resource not accessible by integration).

This is a type/security boundary hardening (structural JWT/exp validation on Codex CLI shared-file imports) with focused regression coverage — ready for maintainer review.

@alt-glitch alt-glitch removed the sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data label Jul 14, 2026
The singleton self-heal path now goes through the hardened Codex CLI
import, which requires the access token to decode as a JWT with an exp
claim. A bare placeholder string is exactly what the guard exists to
reject; real Codex CLI tokens are JWTs, so the fixture must be one too.
@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

CI follow-up: the only red test on the previous run was test_self_heals_missing_singleton_access_token_from_codex_cli (slice 7) — a real interaction with this PR, not noise. The singleton self-heal path now goes through the hardened Codex CLI import, and that test's fixture used a bare placeholder string ("fresh-access") as the access token — exactly the shape the guard exists to reject.

Change (commit 4b1600d, flagged for transparency since it touches an existing main test): the fixture now builds a structurally valid JWT with an exp claim, matching what the real Codex CLI writes. The test's intent — self-heal re-imports the canonical token from ~/.codex/auth.json — is unchanged and still fully asserted. Notably, the sibling test test_missing_singleton_access_token_reraises_when_codex_cli_half_token ("malformed import must not mask the error") already encodes this PR's philosophy and passes with the guard as-is.

Local validation: test_auth_codex_self_heal.py + test_auth_codex_provider.py: 42 passed, 0 failed.

@MestreY0d4-Uninter

Copy link
Copy Markdown
Contributor Author

Requesting removal of the needs-repro label — reproduction is documented in this thread and covered by regression tests.

Repro of the underlying bug (current main): the shared Codex CLI import helper accepts any truthy access_token/refresh_token strings. Write a placeholder ~/.codex/auth.json (e.g. {"tokens": {"access_token": "placeholder", "refresh_token": "x"}}) and trigger either consumer of the helper — explicit CLI import or refresh-token recovery — and the placeholder is imported into the Hermes auth store as if it were a real credential.

Evidence already in this thread:

  • Isolated tmux run in a clean HERMES_HOME: placeholder shared-file imports are rejected with this PR and accepted without it; device_code is not re-seeded on reload.
  • Regression tests in tests/hermes_cli/test_auth_codex_provider.py encode the repro (malformed import rejected, structurally valid JWT accepted) — and the recently-added main test test_missing_singleton_access_token_reraises_when_codex_cli_half_token asserts the same failure class from the self-heal path.

Current state: all 23 checks green, MERGEABLE, and the automated review found no blocking correctness or design-fit issue in the two-file diff. Ready for maintainer review.

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-repro Bug needs reproduction steps P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants