Skip to content

fix(report): the finding cross-reference contract — the sanitizer keeps the finding-ref anchors (#711) - #876

Open
gadievron wants to merge 1 commit into
masterfrom
fix/711-finding-ref-links
Open

gadievron wants to merge 1 commit into
masterfrom
fix/711-finding-ref-links

Conversation

@gadievron

@gadievron gadievron commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

What

Closes #711 — #247-era producer/consumer disagreement: the Python CLI linkifies the model's #N finding references into <a href="#finding-N" class="finding-ref">#N</a> (and the prompt explicitly asks the model for them), both templates render the matching id="finding-N" anchors on the finding <details> — but the Go remediation policy allowed neither the class attribute nor any fragment href, so bluemonday dropped the whole anchor. The operator got plain text; the .finding-ref CSS styled nothing (PR #579's noted dead channel).

The falsifier (authored at filing): "a rendered HTML report whose remediation section contains a working link to a finding anchor."

The fix — the contract agreed, as narrowly as the old surface

The consumer (the Go policy): the class attribute on <a> accepts exactly the finding-ref value; the href accepts the finding fragments (#finding-N, digits, end-anchored) plus the pre-existing http(s) surface — the http(s) half of the pattern is deliberately prefix-shaped and case-insensitive, with a whitespace class matching validURL's TrimSpace, so bluemonday's own unchanged URL handling keeps owning the http(s) value exactly as before (an earlier fully-anchored pattern silently demoted uppercase schemes, whitespace-padded values, and apostrophe/quote/backtick-bearing URLs — real regressions, caught and closed in review with a 120-shape A/B: REGRESS=0, and the only newly accepted HREF VALUES in that corpus were the intended fragments — the full sanitized-output surface also gains three harmless class-related shapes the review rounds recorded (a class-only anchor with no href; duplicate class attributes — the browser uses the first; rel="noreferrer" on the fragment links), all enumerated, none a breakout). AllowRelativeURLs is required for a fragment to survive — and the href value pattern is what keeps that permission from admitting anything else: page2.html, ../x, //evil.com, #top, every off-shaped fragment, and javascript:/every other scheme all die.

The producer (the Python linkifier): extracted to the module-level _linkify_finding_refs (behavior-preserving — the 14-shape A/B: 0 diffs) + the numeric-character-reference lookbehind (&#39;, &#8217; — Python's own html.escape output, the common model-emitted shape — no longer linkify into misdirecting live links, the review's catch).

The drift pins

  • Go (internal/report/finding_ref_test.go): the anchor survives with href AND class (plus the rel="noreferrer" hardening); the hostile corpus dies (script URLs incl. entity-encoded, foreign classes, non-fragment relatives, protocol-relatives, off-shaped fragments, unknown schemes); the 8-shape old-surface regression corpus (uppercase, mixed-case, whitespace-padded, apostrophe/quote/backtick-bearing, unicode-whitespace-led http(s) URLs — each verified RED under the historical regressor pattern, 8/8); both templates' anchor ids Count-pinned (the reskin's two finding paths both carry them).
  • Python (tests/test_issue711_finding_ref_links.py): the exact contract shape; every-number linkification; the non-numeric shapes untouched; the entity lookbehind pin; the call-site pin (the handler routes through the module function — the C4 ledger's green row exposed that the suite's direct-call tests left the call site unpinned; a revert of the call-site hunk would leave them green while the report path regressed — pinned at the source now, verified load-bearing).

Evidence (runs/711)

  • RED gate at 9143061f..e372a2c: 6 nodes / 2 witnesses (absence-red at base — the all-new Python file; the red_waiver{new-test-file} route, its per-file fact verified: the file is absent at base); the behavioral half is Go-side (the pytest executor cannot run Go) — its linkage evidence is the C4 ledger.
  • C4 at the final head (the ledger: C4-mutants.json; the mutant patches: _mutant_href.patch / _mutant_import.patch in this bundle): 4/4 prod hunks covered behaviorally — the two cli.py hunks by hunk-reverts (failing=6 / failing=1 — the call-site row went green→behavioral when the pin landed; its kill is the call-site source pin, honestly a source pin not an end-to-end execution). The two types.go hunks are a compile-interdependent PAIR (each single revert is a Go compile error — an undefined/unused regexp import), so the revert route's confounding check refuses both seeds and the claim route refuses too ("a claim cannot ride on a compile error"); covered at PAIR level by prod-mutants in the pair's + ranges instead (the href-pattern mutant — in the policy-lines hunk's range — killed by 3 tests; the AllowRelativeURLs(false) mutant — in the policy hunk's behavioral range, the import hunk having no mutable behavioral content of its own — killed by 1). The pair-level attribution is stated as pair-level, not as two independent in-range demonstrations.
  • fable adversarial review: six rounds — r1 (with bluemonday pinned from the project's own go.sum: # proc-ok the executed B1 regression — 17-19 old-accepted http(s) shapes demoted; the D1 entity-linkification catch; the 116-shape hostile corpus clear); r2 (the B1 fix verified + the F1 unicode-whitespace residual + the two inert pins); r3 (the F2 pins fixed — the real historical regressor recovered from the reflog and overlaid, 7/8 RED — + the F3 tokenizer-truncation catch); r4 TERMINAL DRY (8/8 pins RED under the real mutant; the isolating control executed); r5 the post-boundary delta round at the final head — the C4 ledger's green call-site row exposed the unpinned-wiring class (the suite's direct-call tests left the handler's call site unguarded); the call-site source pin added, and its load-bearing-ness executed with the isolating control (the call-site hunk reverted alone → the pin fires 1-failed/5-passed, the direct-call tests stay green — the class confirmed real); TERMINAL DRY at 21686d38; r6 the CI-catch round — the Report CSS reproducibility job failed on the bare word "relative" in the policy comment (the tailwind content list includes types.go: the extractor generated .relative{position:relative} into regen.css and cmp diverged — the The HTML report's pipeline step rows render four fields and drop two diagnostic classes — the discovery/skip counters and the unknown-price disclosure #700 comment-token hazard, met a second time); the isolating control executed (the pre-fix types.go swapped in alone adds exactly that rule), the reword to "non-absolute" landed, and the regen re-verified BYTE-IDENTICAL to vendor (sha256 equal; the pre-PR baseline also identical — nothing in the whole PR delta emits a utility); TERMINAL DRY at e372a2c.
  • T8 standing-promises table (4 rows: 3 HIT + 1 disclosed MISS — the composed rendered document: the falsifier's "working link" is pinned part-by-part, not as one rendered-document assertion; the golden render fixtures carry no linkified sample), C2 anchor queries (consumed==total: 5/5, 2/2, 7/7, 0/0), A4 census (master unmoved at 9143061f; the open PRs fix(rust): the macro-body call scan runs on a tree-sitter token-tree walk — both regexes and the byte budget retired to the differential oracle (#708) #871/chore(deps): update dependency marked to v18.1.2 #872/chore(deps): update dependency openai to v3.42.0 #873/fix(prompts): the Untrusted choke-point — prompt-untrusted content refuses raw interpolation, unwrapped only at the fence single home (#709) #874/fix(dynamic-test): the recorded build-egress ruling — the falsifier's second disjunct, the accepted state pinned so it cannot drift (#710) #875 disjoint), reconciliation (5 rows); the sixth round is the CI-catch discharge above.

Parity

The legs' own terminal lines, pasted verbatim (the full logs in the bundle — base-parity-full.log, head-parity-settled.log [the WARM run], head-parity-settled-cold.log, head-parity-go.log, collect-diff.txt):

base (at 9143061f, the full core suite):

FAILED tests/patch/test_run_cve_batch.py::test_interrupt_then_resume - Assert...
FAILED tests/test_llm_sdk_contract_floor.py::test_installed_sdks_match_the_requirements_pins
FAILED tests/test_llm_sdk_contract_floor.py::test_anthropic_pin_tied_to_requirements
3 failed, 13280 passed, 38 skipped, 2 xfailed, 1 warning in 1657.80s (0:27:37)

head, warm (at the final head e372a2c, the same suite — the standing trio identity-matched; the +7 passing over base = the 6-test Python producer suite + the 1 parametrized no-hardcoded-paths guard item; the skips identical (38 = 38):

FAILED tests/patch/test_run_cve_batch.py::test_interrupt_then_resume - Assert...
FAILED tests/test_llm_sdk_contract_floor.py::test_installed_sdks_match_the_requirements_pins
FAILED tests/test_llm_sdk_contract_floor.py::test_anthropic_pin_tied_to_requirements
3 failed, 13287 passed, 38 skipped, 2 xfailed, 1 warning in 1619.85s (0:26:59)

the go module (at the head): head-parity-go.log ends EXIT:0 — all packages ok.

collect-diff: base 13,324 collected → head 13,331 — +7 added / 0 removed, exactly the new producer suite's 6 tests + the parametrized guard item (collect-diff.txt).

the cold-run disclosure: the head worktree's FIRST full run showed 3 failed, 13145 passed, 180 skipped — the +142 skips over base are the first-run provisioning class, fully decoded by the warm run above (38 = 38); the cold log retained as head-parity-settled-cold.log.

…ps the finding-ref anchors (#711)

The Python CLI linkifies the model's #N finding references into
<a href="#finding-N" class="finding-ref">#N</a> and both templates render
the matching id="finding-N" anchors on the finding <details>, but the Go
remediation policy allowed neither the class attribute nor any fragment
href, so bluemonday dropped the WHOLE anchor — the operator got plain text
and the .finding-ref rules styled nothing (PR #579's noted dead channel).

The producer/consumer contract, agreed and allowed now — as narrowly as
the old surface: the class attribute on <a> accepts EXACTLY the
finding-ref value (any other class dies); the href pattern accepts the
absolute http(s) links the policy always allowed plus the same-document
#finding-N fragments ONLY. AllowRelativeURLs is required for a fragment
to survive bluemonday's URL policy; the href VALUE PATTERN is what keeps
that permission from admitting any other relative URL — page2.html,
../x, //evil.com all fail the pattern and die, and javascript: and every
other scheme still die at the pattern too. Pinned: the anchor survives
with href AND class (plus the referrer hardening); the hostile shapes
die (the script URLs, the foreign classes, the non-fragment relatives,
the protocol-relatives, the off-shaped fragments, the unknown schemes);
the absolute http(s) links keep their noreferrer/target hardening; both
templates carry the finding anchor ids.

The Python producer half extracted to a module-level
_linkify_finding_refs (behavior-preserving) and pinned: the exact shapes
the Go patterns admit (a drifted producer shape would silently strip at
the render boundary).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant