Skip to content

[codex] Verify auth gate blocked exits - #4232

Merged
serrrfirat merged 2 commits into
reborn-integrationfrom
codex/auth-gate-evidence-pr4231
May 29, 2026
Merged

serrrfirat merged 2 commits into
reborn-integrationfrom
codex/auth-gate-evidence-pr4231

Conversation

@serrrfirat

@serrrfirat serrrfirat commented May 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • verify Reborn auth blocked exits against a durable same-run BeforeBlock checkpoint
  • allow validated auth blocked exits to persist as BlockedAuth instead of falling into RecoveryRequired
  • keep approval/resource/dependent-run blocked evidence fail-closed until their pending gate/process evidence exists

Tests

  • cargo fmt --all
  • cargo test -p ironclaw_reborn loop_exit_applier
  • cargo test -p ironclaw_reborn
  • git diff --check

Base is now reborn-integration; this PR no longer depends on #4231.

@github-actions github-actions Bot added size: S 10-49 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels May 29, 2026

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request implements verification for auth-blocked checkpoints in verify_blocked_evidence by retrieving and validating the checkpoint from the store. It also adds comprehensive unit tests and test helpers to support this functionality. The reviewer suggests using an exhaustive match statement over LoopBlockedKind instead of an inequality check to leverage compiler-enforced safety for future enum variants.

Comment on lines +273 to +281
if request.blocked.kind != LoopBlockedKind::Auth {
// A BeforeBlock checkpoint alone is not sufficient for approval,
// resource, or dependent-run gates: #3424 requires a durable
// pending gate/process ref for those block types. Auth gates use
// the blocked turn state itself as the product-visible pending ref,
// so verifying the pre-block checkpoint is enough to let the
// applier persist that state.
return Ok(false);
}

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.

medium

To ensure safety and maintainability, prefer using an exhaustive match statement over LoopBlockedKind instead of an inequality check. This forces a compile-time error if new variants are added to the enum in the future, preventing silent failures or unhandled cases.

        match request.blocked.kind {
            LoopBlockedKind::Auth => {}
            LoopBlockedKind::Approval
            | LoopBlockedKind::Resource
            | LoopBlockedKind::AwaitDependentRun => {
                // A BeforeBlock checkpoint alone is not sufficient for approval,
                // resource, or dependent-run gates: #3424 requires a durable
                // pending gate/process ref for those block types. Auth gates use
                // the blocked turn state itself as the product-visible pending ref,
                // so verifying the pre-block checkpoint is enough to let the
                // applier persist that state.
                return Ok(false);
            }
        }
References
  1. Prefer exhaustive enum matching over runtime property-based checks for classifying variants when the set is small and known, as it leverages compiler-enforced safety and prevents loosening type constraints.

@serrrfirat
serrrfirat force-pushed the codex/auth-gate-evidence-pr4231 branch from e7ba8a0 to eceb8b1 Compare May 29, 2026 12:16
@serrrfirat
serrrfirat changed the base branch from codex/issue-4176-product-auth-runtime to reborn-integration May 29, 2026 12:16
@serrrfirat
serrrfirat force-pushed the codex/auth-gate-evidence-pr4231 branch from eceb8b1 to 0795317 Compare May 29, 2026 12:21
@abbyshekit

Copy link
Copy Markdown
Contributor

Review — verify auth gate blocked exits

Combined pass (Claude Code + Codex 5.5 gpt-5.5, branch diff vs origin/reborn-integration). Sound, fail-closed, well-scoped change; the new tests genuinely drive the production verify_blocked_evidence path (not tautological). One protocol gap, flagged independently by both reviewers.

Medium — Auth blocked-evidence isn't bound to the actual gate

crates/ironclaw_reborn/src/loop_exit_applier.rs:302-303 — for LoopBlockedKind::Auth the check is checkpoint.kind == BeforeBlock && checkpoint.state_ref == blocked.state_ref. But BeforeBlock carries no gate-kind discriminator, and gate_ref is never cross-checked against the checkpoint. A buggy/compromised driver can reuse a legitimate Approval/Resource/dependent-run BeforeBlock checkpoint from the same run, label the exit Auth, and it validates as BlockedAuth — routing resolution through the auth path instead of the durable gate evidence the other kinds keep fail-closed. Within the driver-trust model this is low-likelihood, but the Auth carve-out gets weaker evidence than its peers. Bind gate identity into the checkpoint record (so the kind can be asserted), or verify durable auth-gate metadata / gate_ref against the checkpoint.

Low

crates/ironclaw_reborn/src/loop_exit_applier.rs:286 — the trailing _ => return Ok(false) correctly fails unknown LoopBlockedKind variants closed (the enum is #[non_exhaustive] in a sibling crate), but a future variant silently lands in the fail-closed bucket rather than forcing a compile-time decision. A comment noting that would help.

What's good

Auth carve-out maps correctly to BlockedAuth via BlockedReason::status(); lookup is strictly same-run scoped (no cross-run replay); Approval/Resource/AwaitDependentRun stay fail-closed; mirrors the existing verify_final_checkpoint pattern with an added state_ref binding.

Coverage gaps

Auth with a wrong-kind checkpoint at the same checkpoint_id (should return false); Auth with an unparseable gate_ref (verified evidence but to_blocked_reason fails → UnverifiedBlockedEvidence); a positive end-to-end apply() asserting Approval/Resource/AwaitDependentRun still land in RecoveryRequired.

Codex 5.5 independently flagged the same gate-binding gap as P2.

…heckpoints (#4232)

Closes the Medium finding from the combined Claude/Codex review on PR #4232:
Auth blocked-evidence was not bound to the gate that triggered it. A rogue
driver could reuse a legitimate Approval/Resource BeforeBlock checkpoint from
the same run, label the exit Auth, and it would validate as BlockedAuth.

Fix: thread gate_ref through the checkpoint pipeline so that BeforeBlock records
carry the gate identity. verify_blocked_evidence now requires the checkpoint's
gate_ref to match the blocked exit's gate_ref in addition to kind and state_ref.

Changes:
- PutLoopCheckpointRequest / LoopCheckpointRecord: add gate_ref Option field
  (serde default = None for backward-compatible deserialization of legacy records)
- LoopCheckpointRequest (host API): add gate_ref Option field (serde default)
- CheckpointStage: add write_before_block(gate_ref) variant; write() delegates
  to a shared write_with_gate_ref() helper so existing callers are unchanged
- gates.rs: BeforeBlock checkpoint calls use write_before_block so the gate ref
  is stored alongside the checkpoint
- port_adapters.rs: propagate gate_ref from LoopCheckpointRequest to PutLoopCheckpointRequest
- loop_exit_applier.rs:
  - add gate_ref cross-check in verify_blocked_evidence (Medium fix)
  - add comment on wildcard arm explaining intentional fail-closed for
    #[non_exhaustive] LoopBlockedKind variants (Low fix)
- Tests: update auth evidence tests to supply matching gate_ref; add
  thread_checkpoint_evidence_rejects_auth_blocked_checkpoint_gate_mismatch
@github-actions github-actions Bot added size: M 50-199 changed lines and removed size: S 10-49 changed lines labels May 29, 2026
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Addressed the combined review findings in a45df3aca.

Fixed

Severity Finding Fix
Medium Auth blocked-evidence not bound to gate — BeforeBlock checkpoint carries no gate-kind discriminator; rogue driver could reuse Approval/Resource checkpoint labeled as Auth Thread gate_ref through LoopCheckpointRequest → PutLoopCheckpointRequest → LoopCheckpointRecord. CheckpointStage::write_before_block stores the gate ref in BeforeBlock checkpoints. verify_blocked_evidence now cross-checks checkpoint.gate_ref == Some(&blocked.gate_ref)
Low _ => return Ok(false) wildcard arm on LoopBlockedKind needs comment Added comment explaining wildcard is required by #[non_exhaustive] in sibling crate, intentionally fail-closed for unknown variants

Tests

  • Updated auth_blocked_exit_with_durable_checkpoint_maps_to_blocked_auth to supply matching gate_ref
  • Updated thread_checkpoint_evidence_verifies_auth_blocked_checkpoint to supply matching gate_ref
  • Added thread_checkpoint_evidence_rejects_auth_blocked_checkpoint_gate_mismatch — verifies cross-gate checkpoint reuse is rejected

Validation

cargo fmt --check     ✓
cargo clippy          ✓ (no errors)  
cargo test -p ironclaw_turns -p ironclaw_agent_loop -p ironclaw_reborn -p ironclaw_hooks  ✓
git diff --check      ✓

@serrrfirat
serrrfirat merged commit 4ddb259 into reborn-integration May 29, 2026
22 checks passed
@serrrfirat
serrrfirat deleted the codex/auth-gate-evidence-pr4231 branch May 29, 2026 22:18
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
* Verify auth gate blocked exits with durable checkpoints

* fix(reborn): address combined review — bind gate_ref to BeforeBlock checkpoints (nearai#4232)

Closes the Medium finding from the combined Claude/Codex review on PR nearai#4232:
Auth blocked-evidence was not bound to the gate that triggered it. A rogue
driver could reuse a legitimate Approval/Resource BeforeBlock checkpoint from
the same run, label the exit Auth, and it would validate as BlockedAuth.

Fix: thread gate_ref through the checkpoint pipeline so that BeforeBlock records
carry the gate identity. verify_blocked_evidence now requires the checkpoint's
gate_ref to match the blocked exit's gate_ref in addition to kind and state_ref.

Changes:
- PutLoopCheckpointRequest / LoopCheckpointRecord: add gate_ref Option field
  (serde default = None for backward-compatible deserialization of legacy records)
- LoopCheckpointRequest (host API): add gate_ref Option field (serde default)
- CheckpointStage: add write_before_block(gate_ref) variant; write() delegates
  to a shared write_with_gate_ref() helper so existing callers are unchanged
- gates.rs: BeforeBlock checkpoint calls use write_before_block so the gate ref
  is stored alongside the checkpoint
- port_adapters.rs: propagate gate_ref from LoopCheckpointRequest to PutLoopCheckpointRequest
- loop_exit_applier.rs:
  - add gate_ref cross-check in verify_blocked_evidence (Medium fix)
  - add comment on wildcard arm explaining intentional fail-closed for
    #[non_exhaustive] LoopBlockedKind variants (Low fix)
- Tests: update auth evidence tests to supply matching gate_ref; add
  thread_checkpoint_evidence_rejects_auth_blocked_checkpoint_gate_mismatch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants