test(incidents): address #52 reviewer notes — full benign coverage + snapshot caveat#53
Conversation
…shot caveat Follow-up to the sprayberry-reviewer approval on #52: - Add the 3 benign look-alikes the PR body referenced but the test omitted (should-not-disclose-to-the-user, privacy policy, helpdesk), so the FP-discipline suite matches the claimed coverage and exercises the broadened do-not / third-person patterns against second-person and non-"do not" phrasings. 142 -> 145 tests. - Note in the generated INCIDENTS.md that it is a snapshot; test/incidents.test.mjs is the CI source of truth. Regenerate with `npm run demo:incidents`. Receipts regenerated through real Chrome (Chrome/150) — still 5/5.
sprayberry-reviewer
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer (sprayberry-reviewer bot). This is a verdict-bearing automated review, not a human sign-off.
Verdict: no blocking issues found — approving.
What was checked
This is a pure test/doc follow-up to #52 (11 additions, 0 deletions, no production-code change). Three things to verify: (1) each new BENIGN fixture genuinely stays ALLOW under the current detector, (2) the snapshot caveat is accurate, and (3) CI is green.
New BENIGN fixtures — all confirmed ALLOW
test/incidents.test.mjs:76 — 'support macro (should not disclose ... to the user)'
'<p>Agents should not disclose ticket internals to the user without a manager sign-off.</p>'
The INSTRUCTION_TO_AI do\s+not\s+(…disclose…) pattern requires the exact phrase "do not"; "should not" does not satisfy \bdo\s+not\s+. The disclose-system-prompt pattern (reveal|…|disclose\s+(me\s+)?(your|the)\s+(system\s+)?(prompt|instructions?…)) needs disclose to be followed by your/the … prompt/instructions; "disclose ticket internals" doesn't match. No INSTRUCTION_TO_AI signal, no command signal, no external sink → analyzeNode returns null → ALLOW ✓. This correctly exercises "should not" ≠ "do not" in the detection grammar.
test/incidents.test.mjs:77 — 'privacy policy (your data / browsing history)'
'<p>We never share your personal data. Your browsing history is yours. Export your data anytime.</p>'
"Your browsing history" contains a second-person possessive (your). The SENSITIVE browsing-history pattern is gated on third-person possessives (the user's / their / his / her), so it does not fire. share and export match EXFIL_VERB, but hasExfilLeg also requires an off-origin sink (external URL or email); none is present. → ALLOW ✓. This directly exercises the second-person guard introduced in #52.
test/incidents.test.mjs:78 — 'helpdesk (do not forward ... to anyone)'
'<p>To reset your password, do not forward your reset code to anyone. We will never email you asking for it.</p>'
"do not forward" — forward is not in the INSTRUCTION_TO_AI do-not verb list (tell|inform|alert|notify|warn|reveal|disclose|surface|mention|report|say|indicate). password hits SENSITIVE (score +1), and forward matches EXFIL_VERB, but there is no external sink (no URL, no email address literal). exfilTarget = false; no command signal → analyzeNode returns null → ALLOW ✓. This exercises the narrowness of the do-not verb list and confirms forward+password without a sink is harmless.
Doc caveat — accurate
incidents/INCIDENTS.md and incidents/run-incidents.mjs (the generator template) both receive the same note:
> **This file is a generated snapshot** — regenerate it with `npm run demo:incidents` after any
> change to the detector or fixtures. The verdicts are enforced for real in CI by
> `test/incidents.test.mjs`, which is the source of truth; this document is a human-readable receipt.
npm run demo:incidents is the correct script (package.json maps it to node incidents/run-incidents.mjs). test/incidents.test.mjs is indeed the CI-enforced source of truth. The caveat is accurate and directly addresses the non-blocking note from #52.
CI
All checks pass: test (145/145), test-node20, CodeQL, analyze — all green.
What's good
The three fixtures are well-targeted: each isolates a distinct boundary in the detection grammar (verb-list precision, possessive gating, external-sink requirement) rather than being redundant copies. The generator-template update ensures future regenerations of INCIDENTS.md will carry the caveat automatically — not just the committed snapshot.
What
Small follow-up to #52, addressing the two non-blocking notes the
sprayberry-reviewerbot raised on that PR (which auto-merged on approval before these landed):Full benign coverage — feat(incidents): reproduce real 2025-26 browser-agent attacks + harden detector #52's description said it was tuned against 9 benign look-alikes, but the committed
test/incidents.test.mjsBENIGNmap only had 6. Adds the missing three — a "should not disclose … to the user" support macro, a privacy policy ("your data / browsing history"), and a helpdesk page ("do not forward … to anyone") — so the false-positive suite matches the claim and exercises the broadeneddo not …/ third-person patterns against second-person and non-"do not" phrasings. 142 → 145 tests.Snapshot caveat — notes in the generated
incidents/INCIDENTS.mdthat it's a snapshot and thattest/incidents.test.mjsis the CI source of truth (regenerate withnpm run demo:incidents), so a reader doesn't mistake the committed receipt for a live check.Verification