Skip to content

fix(reborn): fingerprint credential setup in the auth gate-record key (#6299 follow-up) - #6304

Closed
ilblackdragon wants to merge 1 commit into
mainfrom
fix/auth-gate-setup-fingerprint
Closed

ilblackdragon wants to merge 1 commit into
mainfrom
fix/auth-gate-setup-fingerprint

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

Follow-up to a MEDIUM IronLoop finding that landed on main when #6299 was squash-merged before the fix was pushed (the fix was left on the deleted integration branch).

The bug (now on main)

stable_auth_gate_id (ironclaw_host_runtime/src/production.rs) fingerprints capability, required_secrets, and per-requirement provider:requester_extension:provider_scopes — but omits RuntimeCredentialAuthRequirement.setup. Since GateRecord::Auth is write-once and retained, changing a capability's setup (ManualToken → OAuth/Pairing, or an OAuth setup-scope change) derives the SAME auth-{sha256} key as the old flow, collides with the stale record, and the runner reloads the obsolete requirements — presenting the wrong auth flow on resume.

Fix

Include a deterministic setup fingerprint in the per-requirement key via auth_setup_fingerprint (exhaustive match — a new setup variant fails the build rather than silently sharing a key; OAuth setup scopes are sorted for order independence).

Test

stable_auth_gate_id_distinguishes_credential_setup asserts ManualToken vs OAuth, and OAuth setups with different setup scopes, derive distinct keys while an identical requirement stays stable. Verified it fails before the fix. clippy (test-support,libsql) clean.

🤖 Generated with Claude Code

…#6299 IronLoop)

`stable_auth_gate_id` fingerprinted only `capability`, `required_secrets`, and
per-requirement `provider:requester_extension:provider_scopes` — it omitted
`RuntimeCredentialAuthRequirement.setup`. Since `GateRecord::Auth` is write-once
and retained, changing a capability's setup (ManualToken → OAuth/Pairing, or an
OAuth setup-scope change) derived the SAME `auth-{sha256}` key as the old flow
and collided with the stale record; the runner then reloaded the obsolete
requirements and presented the wrong auth flow on resume.

Include a deterministic `setup` fingerprint in the per-requirement key via
`auth_setup_fingerprint` (exhaustive match — a new setup variant fails the build
rather than silently sharing a key; OAuth setup scopes sorted for order
independence).

Regression: `stable_auth_gate_id_distinguishes_credential_setup` asserts
ManualToken vs OAuth, and OAuth setups with different setup scopes, derive
distinct keys while an identical requirement stays stable — verified it fails
before this fix. clippy (test-support,libsql) clean on ironclaw_host_runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ironloopai

ironloopai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: bd360381c5f63a6db2bc03fbd57e146bbcb1da89
Result: 1 blocking finding across 1 reviewer.
Next: Address the blocking findings, push fixes, then re-run the relevant reviewer.
Updated: 2026-07-20T03:40:47.269Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Completed Changes requested 1 blocking finding / 0 notes 2026-07-20T03:40:47.259Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Changes requested; 1 blocking finding; Focused review of 1 Rust file (+78/-2) found one remaining gate-key collision in the new OAuth setup fingerprint.
Recent activity
Time Reviewer State Detail
2026-07-20T03:37:40.052Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head bd36038.
2026-07-20T03:37:40.052Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-20T03:37:40.539Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-20T03:37:43.387Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (merge_ref) at 86850b1.
2026-07-20T03:40:47.259Z ironloop/common-reviewer (reviewer) Result captured Changes requested; 1 blocking finding.
2026-07-20T03:40:47.259Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent>
Run metadata

Admission: webhook accepted the request and IronLoop persisted reviewer state before this projection.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6304 July 20, 2026 03:37 Destroyed
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved authentication flow tracking so changes to credential setup—such as switching authentication methods or OAuth scopes—are recognized correctly.
    • Prevented updated authentication requirements from reusing stale persisted authentication flows.
    • Preserved consistent behavior when authentication requirements remain unchanged.

Walkthrough

stable_auth_gate_id now fingerprints credential auth setup, including canonicalized OAuth scopes, so setup changes produce distinct persisted gate keys. A unit test covers setup-kind differences, scope differences, and deterministic equality.

Changes

Auth gate fingerprinting

Layer / File(s) Summary
Setup-aware gate IDs
crates/ironclaw_host_runtime/src/production.rs
stable_auth_gate_id incorporates an exhaustive RuntimeCredentialAccountSetup fingerprint, OAuth scopes are sorted deterministically, and tests verify distinct identifiers for changed setups and stable identifiers for identical requirements.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: serrrfirat, henrypark133, think-in-universe

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the bug, fix, and test, but it does not follow the required template sections or checklist items. Reformat it to the repo template with Summary, Change Type, Linked Issue, Validation, Security Impact, Reborn checklist, Database Impact, Blast Radius, Rollback Plan, and Review Follow-Through.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional-commit style and clearly matches the auth gate-record fingerprint fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 20, 2026
@ilblackdragon

Copy link
Copy Markdown
Member Author

Closing in favor of #6303 (@ilblackdragon), which lands the same fix — setup folded into the auth gate-record fingerprint via an exhaustive stable_setup_token match with sorted OAuth scopes — and is more complete (updates the surrounding call sites/tests too). I opened this independently after spotting the same IronLoop finding merge into main with #6299; #6303 covers it. Deferring to the author's PR.

@ilblackdragon
ilblackdragon deleted the fix/auth-gate-setup-fingerprint branch July 20, 2026 03:39

@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 updates the stable_auth_gate_id fingerprint generation to include the credential auth setup, resolving issue #6299 where different auth flows could collide on the same gate-record key. It also introduces a helper function auth_setup_fingerprint and corresponding unit tests. The review feedback highlights potential delimiter collision vulnerabilities in both the main fingerprinting logic and the helper function, recommending the use of an injective length-prefixed encoding to guarantee unique and secure identifiers.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 2130 to 2136
format!(
"credential={}:{}:{}",
"credential={}:{}:{}:{}",
requirement.provider.as_str(),
requirement.requester_extension.as_str(),
scopes.join(",")
scopes.join(","),
auth_setup_fingerprint(&requirement.setup),
)

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.

security-high high

There is a potential delimiter collision vulnerability here. Since both provider_scopes and OAuth scopes can contain colons (:), joining them with commas and then formatting with colons can lead to identical fingerprints for different requirements. To eliminate the risk of separator-collision attacks or accidental collisions, use an injective length-prefixed encoding rather than simple concatenation with a delimiter.

            [
                requirement.provider.as_str(),
                requirement.requester_extension.as_str(),
                &scopes.join(","),
                &auth_setup_fingerprint(&requirement.setup),
            ]
            .iter()
            .map(|s| format!("{}:{}", s.len(), s))
            .collect::<String>()
References
  1. When generating deterministic identifiers or hashes from multiple string components, use an injective length-prefixed encoding rather than simple concatenation with a delimiter to eliminate the risk of separator-collision attacks or accidental collisions.

Comment on lines +2159 to +2163
Setup::OAuth { scopes } => {
let mut scopes = scopes.clone();
scopes.sort();
format!("oauth:{}", scopes.join(","))
}

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.

security-high high

To prevent delimiter collisions when OAuth scopes contain colons, use an injective length-prefixed encoding instead of joining with delimiters or using debug formatting. This ensures that the scopes are serialized with clear, unambiguous boundaries.

Suggested change
Setup::OAuth { scopes } => {
let mut scopes = scopes.clone();
scopes.sort();
format!("oauth:{}", scopes.join(","))
}
Setup::OAuth { scopes } => {
let mut scopes = scopes.clone();
scopes.sort();
let mut parts = vec!["oauth".to_string()];
parts.extend(scopes);
parts.iter().map(|s| format!("{}:{}", s.len(), s)).collect::<String>()
}
References
  1. When generating deterministic identifiers or hashes from multiple string components, use an injective length-prefixed encoding rather than simple concatenation with a delimiter to eliminate the risk of separator-collision attacks or accidental collisions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_host_runtime/src/production.rs`:
- Around line 2130-2136: Update the fingerprint construction in the format block
and auth_setup_fingerprint so every provider, requester, scope element, and
setup component is structurally unambiguous. Replace bare colon/comma
concatenation with bounded serialization or equivalent length/escaping-aware
encoding, preserving scope and array boundaries so distinct inputs always
produce distinct gate-record keys.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0345f702-438a-47a4-8540-49ad28f89157

📥 Commits

Reviewing files that changed from the base of the PR and between 67a2cee and bd36038.

📒 Files selected for processing (1)
  • crates/ironclaw_host_runtime/src/production.rs

Comment on lines 2130 to 2136
format!(
"credential={}:{}:{}",
"credential={}:{}:{}:{}",
requirement.provider.as_str(),
requirement.requester_extension.as_str(),
scopes.join(",")
scopes.join(","),
auth_setup_fingerprint(&requirement.setup),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Key collision via ambiguous delimiters in fingerprint.

Using bare : and , for concatenation creates collisions between adjacent fields, defeating the PR's core objective to guarantee distinct gate-record keys.

For example:

  1. Scope boundary collision: provider_scopes=["read:oauth"] + setup=ManualToken produces ...:read:oauth:manual_token, which identically collides with provider_scopes=["read"] + setup=OAuth(["manual_token"]).
  2. Array boundary collision: scopes=["a,b", "c"] collides with scopes=["a", "b,c"] because both join to a,b,c.

Use unambiguous bounding (e.g., brackets [{}], or JSON serialization) instead of bare delimiters to preserve the domain structure.

🛡️ Proposed fix to unambiguously bound the fingerprint parts
             format!(
-                "credential={}:{}:{}:{}",
+                "credential={}:{}:[{}]-[{}]",
                 requirement.provider.as_str(),
                 requirement.requester_extension.as_str(),
-                scopes.join(","),
+                scopes.join(" "),
                 auth_setup_fingerprint(&requirement.setup),
             )

And in auth_setup_fingerprint (lines 2159-2163):

         Setup::OAuth { scopes } => {
             let mut scopes = scopes.clone();
             scopes.sort();
-            format!("oauth:{}", scopes.join(","))
+            format!("oauth:[{}]", scopes.join(" "))
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
format!(
"credential={}:{}:{}",
"credential={}:{}:{}:{}",
requirement.provider.as_str(),
requirement.requester_extension.as_str(),
scopes.join(",")
scopes.join(","),
auth_setup_fingerprint(&requirement.setup),
)
format!(
"credential={}:{}:[{}]-[{}]",
requirement.provider.as_str(),
requirement.requester_extension.as_str(),
scopes.join(" "),
auth_setup_fingerprint(&requirement.setup),
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_host_runtime/src/production.rs` around lines 2130 - 2136,
Update the fingerprint construction in the format block and
auth_setup_fingerprint so every provider, requester, scope element, and setup
component is structurally unambiguous. Replace bare colon/comma concatenation
with bounded serialization or equivalent length/escaping-aware encoding,
preserving scope and array boundaries so distinct inputs always produce distinct
gate-record keys.

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

❌ IronLoop Review: reviewer

Review at a glance

Verdict Blocking Notes Inline Head
❌ Changes requested 1 0 1 bd360381c5f6

Head: bd360381c5f63a6db2bc03fbd57e146bbcb1da89
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

Focused review of 1 Rust file (+78/-2) found one remaining gate-key collision in the new OAuth setup fingerprint.

Findings

Blocking: 1 / Notes: 0

Blocking findings

1. ❌ [MEDIUM] Encode OAuth scope vectors unambiguously

Location: crates/ironclaw_host_runtime/src/production.rs:2162
Joining scope strings with commas is not injective: OAuth { scopes: vec!["a,b"] } and OAuth { scopes: vec!["a", "b"] } both fingerprint as oauth:a,b. Setup scopes are a Vec<String> without delimiter validation, so this configuration change retains the same write-once auth gate key and can reload the old GateRecord::Auth requirements. Use a canonical structured or length-prefixed encoding and add a regression assertion for this pair.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloopai review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloopai review when the fix may affect multiple areas.

Setup::OAuth { scopes } => {
let mut scopes = scopes.clone();
scopes.sort();
format!("oauth:{}", scopes.join(","))

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.

Joining scope strings with commas is ambiguous: OAuth { scopes: vec!["a,b"] } and OAuth { scopes: vec!["a", "b"] } both yield oauth:a,b. That leaves a setup-only change on the same write-once gate key, so the stale auth record can still be reused. Please use an unambiguous structured or length-prefixed encoding and cover this collision.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 86.2% (319539 / 370706 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 370706 lines now vs 320188 at floor capture (+50518 lines, +15.78%) — material change (>5%)

⚠️ 2 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts

Reborn integration-tier coverage

Line coverage (Reborn crates): 86.2% — 319539 / 370706 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_runtime_policy 33.84% 89 / 263
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_observability 61.54% 16 / 26
ironclaw_authorization 62.46% 604 / 967
ironclaw_dispatcher 62.88% 83 / 132
ironclaw_mcp 64.89% 595 / 917
ironclaw_triggers 65.44% 2142 / 3273
ironclaw_filesystem 67.78% 3957 / 5838
ironclaw_channel_host 68.65% 219 / 319
ironclaw_memory 69.2% 773 / 1117
ironclaw_reborn_migration 71.64% 1551 / 2165
ironclaw_trust 72.88% 661 / 907
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.67% 958 / 1283
ironclaw_extractors 74.72% 538 / 720
ironclaw_capabilities 75.72% 2096 / 2768
ironclaw_projects 76.48% 400 / 523
ironclaw_reborn_cli 77% 10247 / 13307
ironclaw_llm 78.36% 20306 / 25915
ironclaw_product_context 78.57% 11 / 14
ironclaw_telegram_extension 80.18% 4842 / 6039
ironclaw_wasm_product_adapters 80.36% 1448 / 1802
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_first_party_extensions 81.06% 5965 / 7359
ironclaw_memory_native 81.17% 3195 / 3936
ironclaw_events 81.95% 1594 / 1945
ironclaw_network 82.98% 673 / 811
ironclaw_reborn_identity 83.59% 433 / 518
ironclaw_processes 83.76% 939 / 1121
ironclaw_secrets 83.79% 2548 / 3041
ironclaw_wasm 84.44% 1069 / 1266
ironclaw_auth 84.81% 3233 / 3812
ironclaw_product_workflow 84.91% 11031 / 12992
ironclaw_reborn_config 85.2% 2055 / 2412
ironclaw_run_state 85.61% 458 / 535
ironclaw_channel_delivery 85.79% 1383 / 1612
ironclaw_common 86.13% 1714 / 1990
ironclaw_turns 86.7% 14703 / 16958
ironclaw_threads 86.93% 4708 / 5416
ironclaw_slack_v2_adapter 87.3% 1491 / 1708
ironclaw_skills 87.58% 4470 / 5104
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_product_adapters 88.1% 3384 / 3841
ironclaw_reborn_traces 88.2% 11946 / 13544
ironclaw_hooks 88.35% 10075 / 11404
ironclaw_host_api 88.65% 4389 / 4951
ironclaw_host_runtime 88.7% 18098 / 20403
ironclaw_reborn_openai_compat 89.21% 3778 / 4235
ironclaw_webui 89.33% 7700 / 8620
ironclaw_extensions 89.38% 2971 / 3324
ironclaw_runner 89.65% 17454 / 19469
ironclaw_telegram_v2_adapter 89.7% 2717 / 3029
ironclaw_reborn_composition 90.08% 74853 / 83093
ironclaw_approvals 90.18% 1598 / 1772
ironclaw_conversations 90.39% 3123 / 3455
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_resources 91.65% 4476 / 4884
ironclaw_loop_host 92.28% 15997 / 17336
ironclaw_attachments 93.06% 630 / 677
ironclaw_agent_loop 94.95% 9416 / 9917
ironclaw_safety 95.09% 3682 / 3872
ironclaw_outbound 95.52% 3451 / 3613
ironclaw_first_party_extension_ports 95.62% 3672 / 3840

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (3 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6304 — bd360381 Deployed Jul 20, 2026 by railway-app[bot]
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.

1 participant