llm-security/tests
Kjell Tore Guttormsen 18bc1dc92e fix(llm-security): the ReDoS gate timed every pattern and reached 8 of 45
The v8.x-A whole-table gate times every exported pattern against a corpus
of 8 hand-written units and asserts its own coverage -- but the assertion
`covers every exported pattern` guards the pattern LIST, not the input
corpus. A pattern is only measured if some unit happens to carry its
leading literal; otherwise it fails on the first character and reports
green having measured nothing.

Measured on the pre-swap tables: 37 of the 45 prefix-bearing patterns
were never reached, including BOTH quadratic hybrid-xss rows this gate
was believed to cover. `<script ` and `<iframe ` appear in no unit, so
the two rows commons independently measured as quadratic ran their
literal-prefix check and stopped. Same defect class as all of v7.8.2:
reported success without running.

Hand-writing 37 more units does not fix it -- it re-arms the same trap at
the next pattern. Class 3 derives each attack unit from the pattern's OWN
literal prefix, so coverage is a function of the table rather than a list
someone must remember to extend. 64KB rather than the 512KB read cap for
the class-1 reason: a quadratic pattern met at 512KB stalls the run for
minutes instead of failing it.

Proven to fire, both directions, against the vendored file:
  - gate written first, pre-swap: RED, naming script-tag 1429ms and
    iframe-src 1161ms against a 150ms budget (exit 1)
  - post-swap: GREEN, 17.7ms for all 45 probes (exit 0)
  - vendored JSON mutated back to [^>]*: golden AND ReDoS gates both exit 1
  - vendored JSON corrupted: golden exit 1, conformance 3 fail
  - restored: all green

Clean-table margin at 64KB is ~700x: worst legitimate pattern 1.66ms.

Carried with the commons v0.4.3 subtree pull, which is what makes the
gate passable. v0.4.0 was the tag commons announced; v0.4.1-v0.4.3 came
after and touch no data table -- lexicon 0.8.0 and secret-egress
0.3.0/19 are identical across all four -- so v0.4.3 was taken for the
conformance manifest correction (302625e) they sent separately.

Golden re-blessed after a post-by-post diff: exactly 2 changed records,
both [^>]* -> [^><]*, 0 added, 0 removed, reference run 61/61 unchanged.
The file-sha256 layer did NOT move, contrary to the note in STATE: it
pins scanners/lib/injection-patterns.mjs, which has held no literals
since be14867. The vendored lexicon is covered by the regex layer only.

The script-tag tripwire pinned the old form and fired correctly. Updated
to the v0.4.x form and widened to the iframe row, which had no tripwire
while it was quadratic -- which is why nobody had named it.

Full suite 2193 pass / 6 skipped. The one red is the documented
pre-compact size-cap timing flake; passes alone (exit 0), as do
attack-simulator and the gate itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJxU3xuwfMq8W1mxtiGhLk
2026-08-13 20:30:31 +02:00
..
e2e test(llm-security): cover TRG/SIG/AST in e2e pipeline + SIG miner/hacktool/rot13 families (#58,#59) 2026-07-18 10:46:19 +02:00
fixtures feat(llm-security): swap injection tables to vendored commons lexicon 2026-08-11 14:13:36 +02:00
golden fix(llm-security): the ReDoS gate timed every pattern and reached 8 of 45 2026-08-13 20:30:31 +02:00
helpers test(llm-security): add JetBrains fixture tree + build helper 2026-04-18 10:49:49 +02:00
hooks fix(llm-security): close OpenAI legacy key recall gap in pre-edit-secrets hook 2026-08-11 13:43:27 +02:00
lib fix(llm-security): the ReDoS gate timed every pattern and reached 8 of 45 2026-08-13 20:30:31 +02:00
scanners feat(llm-security)!: v8 Phase 3 complete - riskScoreV1, posture heuristic, docs 2026-08-09 10:25:03 +02:00