fix(noema): stop LLM prose from spoofing the body-side head SHA match - #1500
fix(noema): stop LLM prose from spoofing the body-side head SHA match#1500seonghobae wants to merge 6 commits into
Conversation
Devin's automated review on PR #1415 flagged that noema_review_state()'s NOEMA_BODY_HEAD_RE searched the entire review body for the literal shape "Head SHA: `<40 hex chars>`". Since submit_review() places the LLM's own generated summary/findings text *before* the fixed-format footer that contains the real "- Head SHA: `<sha>`" bullet, an LLM response that happened to echo that phrase in prose (plausible whenever Noema reviews a PR discussing this exact mechanism, e.g. noema_review_gate.py or noema_review_handoff.py themselves) produced a second regex match. That tripped the `len(body_heads) != 1` duplicate-binding guard added by #1480/#1483 and made noema_review_state() wrongly return None for an otherwise valid, correctly-authored Noema verdict — starving noema_review_handoff.py's poll loop until it timed out. Fix: anchor NOEMA_BODY_HEAD_RE to consume a whole line via re.MULTILINE (matching only submit_review()'s literal "- Head SHA: `<sha>`" bullet, not substrings inside prose or findings text), and only search the text before the trusted HTML marker comment so prose after it can't count either. This preserves the dual-binding property #1480/#1483 introduced — the marker and the human-readable bullet must still independently agree with the live head, defending against a stale SHA landing in one place but not the other — while no longer being fooled by incidental LLM text that merely resembles the footer's shape. Added a regression test proving a valid review survives incidental "Head SHA: `...`" prose (both a different SHA and the same SHA repeated), paired with the existing negative-control tests (missing/stale/duplicate/ conflicting bindings must still reject) to prove the security property is unweakened. 100% coverage/docstrings maintained on scripts/ci/; full suite (2119 tests) passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
|
Warning Review limit reachedNext included review available in 43 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
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. Comment |
Devin's follow-up review of PR #1500 found a real remaining gap: the whole-line MULTILINE anchor on NOEMA_BODY_HEAD_RE narrowed the collision surface from "anywhere in the body" to "any full line before the end marker" — but that still lets LLM-generated summary/findings text spoof a rejection if it happens to contain a complete, correctly-formatted standalone "- Head SHA: `<sha>`" line of its own (plausible in exactly the self-referential scenario that makes the underlying bug likely: Noema discussing or quoting this exact review-body format). A content pattern can never fully close this off, since nothing stops the LLM's own unsanitized text from reproducing any fixed string. So instead of tightening the pattern further, submit_review() now wraps the start of its fixed-format footer in a second, purpose-specific HTML marker (NOEMA_REVIEW_FOOTER_MARKER) that — like the existing closing "noema-review-gate head_sha=..." comment — only submit_review() itself ever emits. noema_review_state() (via the new _isolate_trusted_footer() helper) now isolates the footer by *position*: the span strictly between that footer-start marker and the closing marker, both machine-emitted and never reachable by the LLM's own generated text. Only that trusted span is searched for the "- Head SHA:" bullet, so no amount of LLM text anywhere else in the body — mid-sentence or a complete standalone bullet, a different SHA or the same one — can ever count. The MULTILINE whole-line anchor is kept as defense-in-depth inside that now-trusted span. The dual-binding property #1480/#1483 introduced is unchanged: the marker comment and the human-readable bullet must still independently agree with the live head, exactly as before. Extended the regression tests: the existing mid-sentence-prose case is updated for the new required footer marker, and a new parametrized test (test_noema_state_ignores_standalone_body_head_bullet_before_footer) proves a complete standalone bullet line before the real footer — the exact case Devin flagged, both a different SHA and the same SHA — no longer causes a false rejection. The negative-control tests (missing/stale/duplicate/ conflicting bindings) are extended to exercise those failure modes from inside the new trusted footer span and still correctly reject. Also updates the two other hand-rolled Noema review body fixtures (test_repository_branch_coverage_review_schedulers.py) to include the new footer marker so they keep testing the intended happy path rather than incidentally exercising the new fail-closed missing-marker case. Full suite (2122 tests) passes; 100% coverage and 100% docstrings on scripts/ci/ maintained. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
Third iteration: Devin's automated review of the previous fix (#1500, comment on noema_review_handoff.py:146, "Marker-shaped model text still rejects reviews") found the position-anchoring applied to the body-side `- Head SHA:` bullet was never applied to the marker-side closing-comment check. `noema_review_state()` still ran `NOEMA_MARKER_HEAD_RE.findall(body)` against the raw, unsanitized body, so an LLM summary/finding that happens to contain a complete, correctly-formatted `<!-- noema-review-gate head_sha=... decision=... -->`-shaped string of its own (e.g. discussing this exact review mechanism) could still produce 2 matches and wrongly reject a valid verdict via the `len(marker_heads) != 1` guard. submit_review()'s `"\n".join([...])` in noema_review_gate.py guarantees the closing marker is unconditionally the body's last element, so everything from NOEMA_REVIEW_FOOTER_MARKER to the end of the body is exclusively machine-emitted — the same structural guarantee _isolate_trusted_footer() already relies on for the body-side bullet. Add a sibling helper, _isolate_trusted_marker_tail(), that returns `body.rsplit(NOEMA_REVIEW_FOOTER_MARKER, 1)[1]` (or "" when the footer marker is absent, failing closed the same way), and run NOEMA_MARKER_HEAD_RE against that trusted tail instead of the raw body. Adds parametrized regression tests (different-sha and same-sha) proving a standalone closing-marker-shaped string placed before the real footer no longer counts, while all existing negative-control tests (missing/stale/duplicate/conflicting bindings, including the genuinely-duplicated-marker case) keep passing unmodified because both real markers still fall inside the trusted tail. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
… gate test assert_opencode_review_uses_codegraph_and_contextual_orchestrator's awk range `/^ required-workflow-bootstrap:$/,/^[^ ]/` never terminates in this file, since job keys are always 2-space indented and no truly-unindented line exists anywhere in the jobs: section. This silently pulled every job after required-workflow-bootstrap into the "must have no if:" check, tripping on an unrelated, legitimate if: condition on a later job's step and failing this required check on every open .github-repo PR. required-workflow-bootstrap itself has always had zero if: conditions -- only the test's own job-scoping was broken. Replace the range with an explicit state machine that starts at the bootstrap job header and stops at the next 2-space-indented job key. Verified: `bash scripts/ci/test_strix_quick_gate.sh` now passes (previously failed with exactly the false-positive record_failure this fix removes); full suite (2125 passed, 1 skipped, 21 subtests) and `git diff --check` clean.
seonghobae
left a comment
There was a problem hiding this comment.
Ported a fix for the exact-head-path-policy/Strix quick-gate required check, which was failing on this and every other open .github-repo PR due to a bug in test_strix_quick_gate.sh's own job-scoping logic (not in opencode-review.yml, which has always been correct). Root cause and fix are in #1506 (a concurrent session's more complete PR, which also documents this in docs/product-technical-gap-baseline.md) — I opened an equivalent fix independently as #1505, now closing that in favor of #1506. Since this check runs on plain pull_request: against each PR's own head branch (not pull_request_target: against main), merging #1506 alone won't fix already-open PRs — the same patch needs porting into each one, which is what this push does (cc233cdd).
Generated by Claude Code
|
#1494 is currently blocked by its own merge conflict against current Generated by Claude Code |
Devin Review correctly flagged .github#1500-style references as neither a valid bare #1500 nor fully-qualified #1500 reference. Normalized to the doc's own established convention (full owner/repo#num on first mention, bare #num afterward). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
noema-review (#1415), opencode-review (#1500/#1502/#1503), and strix (#1503) all independently timed out today with the identical shape: a required check dispatches a repository_dispatch run against main, then polls for evidence; the dispatched run sat queued (never picked up by a runner) for well over an hour, so the poller gave up and reported failure. Documented as an infrastructure capacity question, not a per-PR code defect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
Bug
Devin's automated review on PR #1415 flagged: "Model text invalidates valid reviews — When a model summary or finding repeats the head line,
NOEMA_BODY_HEAD_REcounts it alongside the receipt. The handoff rejects the valid review and times out." (scripts/ci/noema_review_handoff.py:109-111)PR #1415 only inherited this code via a merge from
main; the root cause is pre-existing onmainitself, introduced by the dual head-SHA binding #1480/#1483 added earlier.noema_review_state()validated a Noema review body with an unanchored regex searching the entire body:submit_review()innoema_review_gate.pybuilds the body as: the LLM's own generatedsummary/findingstext (and now, after #1497 landed, its adversarial-validation evidence too), then a fixed-format footer (- Result:,- Head SHA:,- Reviewer credential:,- Actor:), then the HTML marker comment. Because that LLM text is not sanitized against this exact shape, a response that happened to contain the phraseHead SHA:<40 hex chars>`` anywhere — plausible whenever Noema reviews a PR touching this exact mechanism — produced 2+ matches,len(body_heads) != 1became true, and a perfectly valid, correctly-authored Noema verdict was silently discarded, eventually timing out the handoff poll loop.Live risk
Self-referential: any PR whose diff, commit messages, or (per #1497) Noema's own adversarial-probe evidence discusses this exact head-SHA-matching mechanism is at elevated risk of a correct Noema verdict being silently discarded.
Fix — three iterations
First iteration (commit
2fe2fa8b): anchoredNOEMA_BODY_HEAD_REto a whole line viare.MULTILINEand restricted the search to the text before the trusted end marker. This closed the "phrase anywhere in prose" case but Devin's follow-up review of this PR correctly found it incomplete: a complete, correctly-formatted standalone `- Head SHA: ``` line in the LLM's own text still matched, because the extraction was still content-pattern-based (scanning for any line matching the shape), just with a narrower pattern.Second iteration (commit
e99defa7): replaced the content-pattern approach with a positional one, per Devin's suggested direction:submit_review()now emits a second, purpose-specific HTML marker,NOEMA_REVIEW_FOOTER_MARKER = "<!-- noema-review-gate-footer -->", wrapping the start of its fixed-format footer — a marker onlysubmit_review()itself ever emits, the same trust basis the existing closing<!-- noema-review-gate head_sha=... -->comment already relies on.noema_review_handoff.pyadds_isolate_trusted_footer(), which extracts the span strictly between that new footer-start marker and the closing marker — both machine-emitted, never reachable by the LLM's own generated text — and only searches that span for the- Head SHA:bullet (the whole-linere.MULTILINEanchor is kept as defense-in-depth inside that now-trusted span).Because the isolated span can never contain LLM-authored content (it sits strictly between two markers only the trusted script emits), no amount of LLM text elsewhere — mid-sentence, a complete standalone bullet, a different SHA or the same one — can ever count toward the body-side match.
Third iteration (commit
0430f7f8): Devin's review of the second iteration correctly found the positional fix was applied asymmetrically — only to the body-side bullet, not to the marker-side check.noema_review_state()still ranNOEMA_MARKER_HEAD_RE.findall(body)against the entire unsanitized body, so an LLM summary/finding that happens to contain a complete, correctly-formatted<!-- noema-review-gate head_sha=... decision=... -->-shaped string of its own (the same self-referential scenario as before) could still produce 2 matches and wrongly reject a valid verdict via thelen(marker_heads) != 1guard.submit_review()'s"\n".join([...])guarantees the real closing marker is unconditionally the last element of the review body — nothing follows it — so everything fromNOEMA_REVIEW_FOOTER_MARKERto the end of the body is, like the already-isolated footer span, exclusively machine-emitted text. Added a sibling helper,_isolate_trusted_marker_tail(), returningbody.rsplit(NOEMA_REVIEW_FOOTER_MARKER, 1)[1](or""when the footer marker is absent, failing closed the same way_isolate_trusted_footer()does), andnoema_review_state()now runsNOEMA_MARKER_HEAD_REagainst that trusted tail instead of the raw body — the same position-anchoring mechanism, applied to the marker side.This preserves the dual-binding property #1480/#1483 introduced: the marker comment and the human-readable bullet must still independently agree with the live head, unchanged from before.
Regression tests
test_noema_state_accepts_valid_review_despite_incidental_body_text— a mid-sentence phrase resembling the footer (different SHA, and the same SHA) no longer causes rejection.test_noema_state_ignores_standalone_body_head_bullet_before_footer— a complete, correctly-formatted standalone `- Head SHA: ``` bullet line placed in the LLM's own text before the real footer (parametrized: a different SHA and the same SHA) no longer causes rejection.test_noema_state_ignores_standalone_closing_marker_before_footer(new, third iteration, addressing Devin's marker-side finding directly) — a complete, correctly-formatted standalone<!-- noema-review-gate head_sha=... decision=... -->string placed in the LLM's own text before the real footer (parametrized: a different SHA and the current head's own SHA) no longer causes rejection, while the real footer and closing marker remain valid and the review is still correctly recognized as APPROVED.test_noema_state_rejects_missing_stale_or_duplicate_head_bindings— exercises missing/wrong-value/duplicated bindings from inside the trusted footer span and trusted marker tail, proving the dual-binding security property fix: verify Noema GitHub App identity #1480/fix: bind Noema App slug to review identity #1483 added is unweakened (including the genuinely-duplicated-marker case, which stays rejected since both real markers still fall inside the trusted tail).test_repository_branch_coverage_review_schedulers.py) updated to include the new footer marker so they keep exercising the intended happy path.Verification
coverage run -m pytest tests && coverage report --show-missing→ 2132 passed, 1 skipped (up from 2130 after the third iteration's new tests), 100% coverage onscripts/ci/.interrogate→ 100% docstrings.git diff --check→ clean.Scope
Central infra only (
scripts/ci/noema_review_gate.py,scripts/ci/noema_review_handoff.py, and their tests). Does not touchfix/noema-batched-preflight-413-evidence(PR #1415) or any other in-flight branch. Rebased cleanly ontomaintwice as it moved (#1497 landed mid-review;submit_review()'s new adversarial-evidence section required no changes to this fix's insertion point). Not self-merging — leaving for review per the org's OpenCode/Noema governance model.