1
0
Fork 0

release(0.3.2): the ReDoS fix, with both residuals measured against the v0.3.1 tag

Version sync in all four places (pyproject, __version__, README badge, README
install pin) plus the changelog entry for cff0437.

Two claims that were about to ship in the release note did not survive being
measured against a v0.3.1 worktree, and are corrected in LIMITATIONS first:

- "Report-only, so the cost is a review, not a block" was wrong twice. HIGH under
  a low-trust preset is fail_secure, not a review -- report-only means the text is
  never mutated, not that a finding cannot block. And the new script-tag false
  positive costs no consumer a disposition at all: any text containing a literal
  `<script>` already produced active:raw-html at HIGH on 0.3.1, so the same prose
  disposed fail_secure under PRESET_USER_UPLOAD before this change and after it.
  The fail-open that was closed is narrower for the same reason -- it existed only
  in scan_lexicon called on its own; through either composed gate, raw-html
  already caught the unclosed tag. What changed is the label, not the outcome.

- "The realistic long value is still caught by egress:jwt-token" was true and
  hid the part that matters. Measured at the 257-char boundary: a generic long
  password still trips entropy:base64-blob at CRITICAL, disposition unchanged.
  A JWT used as a DB password is the case that moves -- its remaining detections
  top out below CRITICAL, so the any-tier block is lost and PRESET_TRUSTED_SOURCE
  drops from fail_secure to quarantine_review. PRESET_USER_UPLOAD still
  fail_secures. Recorded as a behaviour change in the changelog, not buried.

The first probe for that boundary used a 300-char run of "A" and found nothing on
either version: all-same-char values are suppressed as placeholders. The probe was
wrong, not the pattern.

662 passed, coverage matrix exit 0, LIMITATIONS still 29 items = README's 29.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 21:16:45 +02:00
commit 75ae48277b
5 changed files with 99 additions and 9 deletions

View file

@ -248,17 +248,32 @@ items; this is the full list, each with the mechanism.
pattern matches the opening tag and no longer requires `</script>`, so a
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) and it was the
last quadratic-backtracking site on the output path. Report-only, so the cost
is a review, not a block.
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.
**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
`fail_secure` under `PRESET_USER_UPLOAD` before this change and after it. The
fail-open was equally confined to `scan_lexicon` called on its own; through
either composed gate, `active:raw-html` already caught the unclosed tag. What
changed is the *label*, not the outcome. And the outcome is not "a review":
HIGH under a low-trust preset is `fail_secure`. Report-only means the text is
never mutated — it does not mean the finding cannot block.
- **A connection-string password longer than 256 chars is not matched.** The
password run in the `*-connstr` egress patterns is bounded by
`MAX_CONNSTR_VALUE`; unbounded, it sits in front of a mandatory `@` and makes
crafted input quadratic. Excluding the anchor character instead — the fix the
active-content table uses — is unavailable here because that character is `/`,
and a password containing `/` is the common case. The realistic long value (a
token used as a DB password) is still caught by `egress:jwt-token`.
and a password containing `/` is the common case. **What the residual actually
costs, measured at the 257-char boundary:** a generic long password still trips
`entropy:base64-blob` at CRITICAL, so the disposition is unchanged. A *JWT* used
as a DB password is the case that moves — the remaining detections
(`entropy:base64-blob` HIGH, `egress:jwt-token` MEDIUM) top out below CRITICAL,
so the any-tier CRITICAL block is lost: under `PRESET_TRUSTED_SOURCE` such a
document drops from `fail_secure` to `quarantine_review`. Under
`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 six documented gaps (tracked by the coverage matrix)