Skip to content

fix(codex): bridge gateway approvals safely - #66925

Open
h4ck200711 wants to merge 2 commits into
NousResearch:mainfrom
h4ck200711:fix/codex-gateway-approval-hardened
Open

fix(codex): bridge gateway approvals safely#66925
h4ck200711 wants to merge 2 commits into
NousResearch:mainfrom
h4ck200711:fix/codex-gateway-approval-hardened

Conversation

@h4ck200711

Copy link
Copy Markdown

Summary

Bridge Codex app-server exec and file-change approval requests into Hermes' existing gateway approval queue when a live gateway session is available.

This preserves CLI approval behavior, keeps cron/non-interactive contexts fail-closed, and reuses the shared timeout, interrupt, cleanup, heartbeat, and approval-hook machinery.

Problem

run_codex_app_server_turn() currently only resolves the CLI approval callback. Gateway sessions therefore have no callback for Codex item/commandExecution/requestApproval and item/fileChange/requestApproval requests, so they are silently declined even though the user is present on Telegram, Discord, or Desktop.

This is the remaining interactive-gateway case discussed in #26530 and the draft approach in #27636. The merged YOLO/mode-off behavior does not provide an interactive approval bridge for manual or smart mode.

Implementation

  • Prefer the existing CLI callback when one is installed.
  • Otherwise, expose a callback only when the active session has a registered gateway notifier.
  • Send requests through prompt_gateway_approval(), which:
    • applies hardline, sudo-stdin, and approvals.deny floors before any bypass;
    • honors mode-off, YOLO, permanent/session approval, and smart approval;
    • force-redacts command and description before crossing the gateway boundary;
    • delegates queueing, timeout, interruption, cleanup, and hooks to _await_gateway_decision();
    • fails closed on missing notifier, notification failure, timeout, or unresolved decisions.
  • Apply unconditional command-policy floors before Codex's auto_approve_exec and auto_approve_apply_patch shortcuts.

Safety properties

  • Hardline commands cannot be approved through YOLO/mode-off auto-routing.
  • sudo -S guessing and user-defined deny rules remain unconditional.
  • Gateway payload redaction uses force=True, independent of security.redact_secrets.
  • No notifier means no interactive callback; requests remain fail-closed unless the existing explicit bypass routing applies.
  • Permanent approval remains disabled for Codex exec and apply-patch requests.

Tests

410 passed, 1 deselected
ruff: passed
git diff --check: passed

The deselected test is TestDetectDangerousRm::test_nonrecursive_verification_artifact_cleanup_is_not_dangerous, which also fails on clean main on macOS because /tmp resolves through /private/tmp; it is unrelated to this change.

New regression coverage includes:

  • Gateway queue approval and decision mapping
  • Forced secret redaction
  • Smart auto-approval without prompting
  • Hardline denial
  • Missing-notifier fail-closed behavior
  • Hardline denial before Codex exec auto-approval
  • Safe apply-patch auto-approval without a callback

Related work

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery codex provider/openai OpenAI / Codex Responses API P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state needs-decision Awaiting maintainer decision before any implementation labels Jul 18, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to open #25889 and #27636. This current-main bridge adds policy-floor and forced-redaction coverage, so it is competing/superset work rather than a duplicate; a maintainer should select or consolidate the implementation.

@teknium1 teknium1 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.

Thanks for addressing a real gateway gap. Current origin/main only passes the CLI approval callback into CodexAppServerSession at agent/codex_runtime.py:643-650, while missing callbacks decline Codex exec and file-change requests at agent/transports/codex_app_server_session.py:875-937.

Problems

  • tools/approval.py:2137-2140 returns a gateway "session" choice without calling approve_session(). The existing gateway path persists that choice at current-main tools/approval.py:2804-2805; without the same persistence, every later Codex approval will prompt again.
  • tests/run_agent/test_codex_app_server_integration.py:86-175 tests the resolver directly, not the run_codex_app_server_turn() construction path that must carry gateway session context and notifier registration.

Suggested changes

  • Persist a resolved "session" choice under codex_app_server; keep permanent approval disabled.
  • Add a lifecycle-level test from gateway notifier registration through a Codex server approval request and response.

Automated hermes-sweeper review.

Comment thread tools/approval.py
if decision.get("notify_failed") or not decision.get("resolved"):
return "deny"

choice = decision.get("choice") or "deny"

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.

A session decision is returned but never persisted. The normal gateway approval path calls approve_session(session_key, pattern_key) before returning success; please do the same here so one session approval covers subsequent Codex requests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed — prompt_gateway_approval() now calls approve_session() (and approve_permanent()/save_permanent_allowlist() for the true-permanent branch) after a resolved session/always choice, matching the exact persistence convention every other approval call site in this module already uses. Added a lifecycle test that drives a real run_conversation() turn so codex_runtime constructs an actual CodexAppServerSession with the gateway-resolved callback wired in exactly as production does, and asserts a second exec-approval request for the same pattern does NOT re-prompt the notifier, proving persistence through the real construction path rather than just the resolver in isolation. Pushed in 78657ed28.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 19, 2026
@h4ck200711

h4ck200711 commented Jul 19, 2026

Copy link
Copy Markdown
Author

Addressed both points from the hermes-sweeper review:

Persistence bug (real, confirmed): prompt_gateway_approval() checked is_approved(session_key, pattern_key) before prompting but never called approve_session() after a resolved session/always choice — so every subsequent Codex exec/apply_patch request in the same session re-prompted the user despite them having already approved it for the session. Fixed to match the identical persistence convention already used at every other approval call site in this file.

Test coverage gap: added test_gateway_session_approval_persists_through_full_turn_construction, which drives a real run_conversation() turn so codex_runtime constructs an actual CodexAppServerSession with the gateway-resolved approval_callback wired in exactly as production does (not a hand-built stand-in), simulates a mid-turn Codex exec-approval request through that stored callback, and asserts a second request for the same pattern does NOT re-prompt the notifier.

Test results: tests/run_agent/test_codex_app_server_integration.py 33/33 pass; tests/tools/test_approval.py 311/312 pass (the 1 failure is the same pre-existing macOS /tmp vs /private/tmp quirk already called out in this PR's description as failing on clean main too — unrelated to this change).

Pushed to this branch in 78657ed28.

Additional corroboration: I also carry an independently-written approval bridge for this exact same use case (Codex exec/apply_patch → gateway approval UI) on a separate deployment, predating this PR. Auditing it against this review's finding turned up the identical class of bug — it checks a notifier and awaits a decision, but never persisted a resolved approval either. Fixed there with the same approach (is_approved() short-circuit + approve_session()/approve_permanent() after resolution) and live-tested: 3 sequential approval requests for the same command → only the first one actually prompted, the other two correctly auto-approved from persisted state. Consistent, reproducible evidence that this class of bug is real in practice, not just a theoretical gap caught by static review.

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Four PRs address the missing Codex app-server approval path in gateway sessions: #25889 adds a transport fallback with a separate wait loop, #27636 adds a runtime callback bridge with another separate wait loop, #43217 is a closed shared-waiter implementation mixed with Telegram cache-path changes, and #66925 combines the runtime bridge with the shared waiter, policy floors, forced redaction, and session-choice persistence.

Related pull requests

Duplicates

#25889 and #27636 substantially duplicate the same Codex-to-gateway bridge through different wiring points; #43217 is an earlier closed implementation of the shared core, while #66925 consolidates that work with additional policy and redaction safeguards.

Suggested consolidation

Keep #66925 open with a salvage path: retain its shared-waiter bridge, policy floors, forced redaction, session persistence, and lifecycle coverage while maintainers validate the updated head. Close #25889 as superseded by #66925 with the author's stated consent, close #27636 as a duplicate of #66925 despite its keep_open verdict because #66925's diff implements the verdict's frozen-bypass and shared-interrupt requirements, and keep #43217 closed as a historical reference.

Cross-PR triage: Reviewed 4 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 63 kB of PR diffs, 6 kB of issue/PR text, 11 kB of discussion (15 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

h4ck200711 added 2 commits August 6, 2026 19:38
Address hermes-sweeper review on NousResearch#66925:

- prompt_gateway_approval() checked is_approved(session_key, pattern_key)
  before prompting, but never called approve_session() after a "session"
  (or demoted "always") choice was resolved — so every subsequent Codex
  exec/apply_patch request in the same session re-prompted the user
  despite them having already picked "approve for this session". Fixed to
  match the identical persistence convention already used by every other
  approval call site in this module.
- Added a lifecycle-level test that drives a real run_conversation() turn
  so codex_runtime constructs an actual CodexAppServerSession with the
  gateway-resolved approval_callback wired in exactly as production does,
  simulates a mid-turn Codex exec-approval request through that stored
  callback, and asserts a second request for the same pattern does NOT
  re-prompt the notifier (proving the persistence fix through the real
  construction path, not just the resolver in isolation).

313 tests in tests/tools/test_approval.py +
tests/run_agent/test_codex_app_server_integration.py: 312 passed, 1
pre-existing/unrelated macOS /tmp-vs-/private/tmp failure (already
documented in the PR description as failing on clean main too).
@h4ck200711
h4ck200711 force-pushed the fix/codex-gateway-approval-hardened branch from 78657ed to 4062a9d Compare August 6, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants