1
0
Fork 0

release(0.3.3): the lexicon ReDoS fix, and a correction to 0.3.2's claim

Version synced across the four locked points (pyproject, __version__, README
badge, README install pin) + CHANGELOG.

Corrections this release carries, both measured rather than reasoned:

- docs/LIMITATIONS.md said the script-tag change removed "the last"
  quadratic-backtracking site on the output path. It did not. Corrected, with
  the 334.7s gate measurement that falsifies it.
- README's coverage line claimed 126/126 classes; the matrix reports 128/128.
  Stale since before v0.3.2. Test badge was 642, actual 666.

New residual recorded (LIMITATIONS, now 30 items, README synced): the sweep
flags on timing above a 1.5ms noise floor at N=8000, so an arm hiding under it
could still cost ~23s at the cap. What this supports is "no arm worse than ~23s",
not "no quadratic arm remains" -- and the blind spot is demonstrated, since a
generic-payload pass found only one of the two patterns.

666 tests green, coverage matrix 128/128 with 6/6 gaps holding, exit 0.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 21:51:00 +02:00
commit 701a4a47c7
5 changed files with 105 additions and 8 deletions

View file

@ -249,7 +249,12 @@ items; this is the full list, each with the mechanism.
document *about* XSS is flagged alongside a document that *carries* it. This
is a deliberate trade made twice over: requiring the closing tag was a
fail-open (an unclosed `<script>alert(1)` was silently missed by *this label*)
and it was the last quadratic-backtracking site on the output path.
and it removed a quadratic-backtracking site on the output path. **It was not
the last one** — 0.3.2 said so and that claim was wrong. The 0.3.3 sweep of
all 83 lexicon patterns found two more, and because `scan_lexicon` runs on the
output path too, they were reachable through `scan_output`: `"[" * 100_000`
took 334.7s through the gate. The claim was too broad because the sweep behind
it drove `[` only through `scan_active_content`, never through the lexicon.
**Measured, both claims are narrower than they read.** The new label costs no
consumer a disposition: any text containing a literal `<script>` already
produced `active:raw-html` at HIGH on 0.3.1 — so the same prose disposed
@ -275,6 +280,19 @@ items; this is the full list, each with the mechanism.
`PRESET_USER_UPLOAD` it still `fail_secure`s. The credential is never silently
missed; on one preset it is held for review instead of halted.
- **The ReDoS sweep of the lexicon has a measured sensitivity floor, not a clean
bill of health.** All 83 patterns were swept arm by arm — payloads synthesised
per run from each pattern's own skeleton, so `[`, `[system]` and `[system](`
are each probed separately rather than relying on generic units. Two patterns
were quadratic and both are fixed. But the sweep flags on *timing*, and it
ignores measurements below a 1.5 ms noise floor at N=8000. A quadratic arm
sitting just under that floor would still cost **up to ~23 s** at the
1 000 000-char cap the gate accepts. So the claim this sweep supports is
"no arm worse than ~23 s at the cap", not "no quadratic arm remains". The
method's blind spot is real and was demonstrated in this very sweep: a
generic-payload pass found only one of the two patterns, and the second
surfaced only after the payloads were generated per run.
## The six documented gaps (tracked by the coverage matrix)
These are asserted to *still hold* by `tests/test_coverage_matrix.py` — a closed gap