Skip to content

fix(audit): un-mask findings hidden by detector-ID collisions + clearer B2 message#48

Merged
ProtocolWarden merged 1 commit into
mainfrom
fix/pattern-collision-masking
Jun 18, 2026
Merged

fix(audit): un-mask findings hidden by detector-ID collisions + clearer B2 message#48
ProtocolWarden merged 1 commit into
mainfrom
fix/pattern-collision-masking

Conversation

@ProtocolWarden

Copy link
Copy Markdown
Owner

Problem

Two detector families ship the same IDs: builtin readme (R1=README
missing, R2=H1 mismatch) and builtin reconcile (R1=.console line-budget,
R2=scrub-target leak), and a repo's custom plugin can add a third R1/R2.
Assembled into one list, run_audit/_run_adapters did
result.patterns[id] = entry — so a later, count-0 instance silently
overwrote an earlier instance that found something, while total_findings
(summed independently) still counted it.

Net effect: a phantom findingtotal_findings: 1 with every visible
pattern at count: 0 and an empty findings(). In the field this masked a
real boundary-leak
(a reconcile R2 scrub-target hit) behind a custom
plugin's count-0 R2. A security detector firing into a black hole.

Fix

  • AuditResult.add_pattern(code, entry) — merge on ID collision instead of
    overwrite: sum count, dedupe-concat samples, take max severity, flag
    collision + record collision_sources. Both registration sites (detectors
    and tool adapters) route through it.
  • Zero pass/fail blast radius. total_findings is still computed by the
    pre-storage sum in run_audit, so --fail-on-findings behaves byte-for-byte
    identically. The change only makes already-counted findings visible
    repos that were silently red become diagnosably red; no green repo flips.
  • detect_b2 now distinguishes a content-less artifact (provided +
    parsed but zero forbidden_names — a secret/data fix) from not-provided
    (a config fix). Previously both got the generic "not provided" message, which
    sent operators hunting for a missing file that was actually present-but-empty.

Tests

add_pattern first-write / collision-merge-no-mask / max-severity+dedupe, and
the B2 content-less message. Full suite: 1123 passed, 1 skipped; ruff +
doctor clean.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

🤖 Generated with Claude Code

…er B2

Two detector families ship the same IDs (builtin readme R1/R2 vs reconcile
R1/R2; a repo's custom plugin may add a third). run_audit/_run_adapters did
`result.patterns[id] = entry`, so a later count-0 instance silently overwrote
an earlier instance that found something, while total_findings (summed
separately) still counted it — a phantom finding invisible in the patterns map
and findings(). It masked a real boundary-leak (R2 scrub-target) in a consumer.

- AuditResult.add_pattern(): merge on collision (sum count, dedupe samples,
  max severity, flag `collision`/`collision_sources`); route both detector and
  adapter registration through it. total_findings is unchanged (computed by
  pre-storage sum), so --fail-on-findings behavior is byte-identical — the fix
  only makes already-counted findings visible.
- detect_b2: distinguish a content-less artifact (provided + parsed, zero
  forbidden_names — a secret/data fix) from not-provided (a config fix).

Tests: collision merge/no-mask/severity/dedupe; B2 content-less message.
Full suite 1123 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ProtocolWarden ProtocolWarden merged commit d6ba8ab into main Jun 18, 2026
7 checks passed
@ProtocolWarden ProtocolWarden deleted the fix/pattern-collision-masking branch June 18, 2026 20:55
ProtocolWarden added a commit that referenced this pull request Jun 22, 2026
…warning) (#50)

#48 un-masked the COUNT when two detectors share an id, but the displayed finding
still showed only the first-registered detector's title — so a `.console/task.md`
violation surfaced as "[R2] README first H1 does not match repo name" and silently
misdirected a consumer (the reviewer escalated it as ci_misconfigured_check; the
goal lane stalled for hours chasing a phantom README issue).

This makes the collision visible at both points:

- audit_kit/result.py: `collision_note(pattern)` — a marker rendered for any merged
  (collided) pattern, naming the colliding sources and warning that the title may
  not match the finding (read the samples). Used by both the single-repo (audit.py)
  and multi-repo (multi.py) finding renderers.
- cli/runner.py: `_warn_detector_id_collisions(detectors)` logs a WARNING at load
  time for every id registered by more than one detector, listing each source's
  description — so the collision is fixable at the source (rename the custom
  detector) instead of being discovered the hard way.

Non-breaking: no detector ids renamed; the builtin readme R1/R2 vs reconcile R1/R2
families and custom-plugin ids keep working, the collision is just no longer silent.

3 new tests (collision marker on/off, load-time warning fires per duplicate and not
for unique ids); 1130 existing tests pass; `ruff check src` clean.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant