1
0
Fork 0
Commit graph

4 commits

Author SHA1 Message Date
74656123f9 docs(redos): the sweep now measures on the clock its own numbers justify
docs/redos-sweep.py timed on time.monotonic() while every ReDoS bound in the
suite had moved to process CPU time, so the 1.5 ms sensitivity floor and the
"~23 s at the cap" figure in docs/LIMITATIONS.md were produced by a different
instrument than the bounds they support. The script imports scan_seconds now.

Re-derived on that instrument, the floor came back UNCHANGED at 1.5 ms. Twelve
full runs of all 2585 arms: median ratio 1.95-2.03 in every size bucket above
50 us, but two-point excursions past the 2.6 threshold survive at every
magnitude (p99 2.9-3.3 even above 1 ms). Flagged arms per run by floor: 6.9 at
0.5 ms, 1.1 at 1.0 ms, 0.33 at 1.5 ms. Four arms flagged across the twelve runs,
each in exactly one; six arms that have ever flagged re-measure at exponent
0.97-1.09 over six doublings, at most 1.2 s at the cap. Descheduling was never
what made this sweep noisy -- a ratio from two points is.

Measured before publishing and it cost a correction: an earlier draft of this
change said the sweep "reports 0 candidates". The next run reported 2. Nine of
twelve instrumented runs are clean and eight consecutive shipped-script runs
under load flagged 0-3, so neither a clean run nor a flagged one is evidence
on its own -- the doc says that now.

Also corrects the pattern count in the same bullet: 150 -> 152, the script's
own printed total. The 0.3.4 CHANGELOG entry keeps its 150 as a snapshot.

No exported surface, no detector behaviour, no calibration touched.
802 passed, 129/129 classes, 6/6 gaps, 43 limitations, gitleaks clean.
2026-08-18 16:56:01 +02:00
73fa1b99ae fix(sanitize,okf,active_content): three quadratic patterns, two on the input path
The generalised sweep found what 0.3.2's hand-written rows missed. All three are
the documented class -- a run in front of a required literal that never arrives,
so every start position rescans the tail -- and all three are worse than the
0.3.3 findings, because `sanitize`, `neutralize`, `scan_active_content` and the
okf link graph apply NO input cap. `scan_lexicon`/`scan_output` are the only
entry points that do, so there is no ceiling to extrapolate to.

  sanitize._HTML_COMMENT_RE   `<!--`*100_000        20.1s, exponent 1.96-2.14
  active_content.URL_IN_TEXT_RE  `<a `+`A`*100_000  12.99s / 14.9s, exponent ~2.0
  okf._MD_LINK_RE             `[`*100_000            7.1s, exponent 1.99-2.05

Each fix is the one the pattern's own shape allows, not a copied choice:

  - The comment stripper drops the regex for `str.find`. Excluding `<` would lose
    every comment containing markup; bounding the run would be a carrier bypass
    of the exact construct the stripper exists to remove.
  - `URL_IN_TEXT_RE` bounds its scheme run to an RFC 3986 scheme (`{0,63}`).
    Bounding is safe *here* only because it is a defanger inside a tag already
    flagged `active:raw-html`. A lookbehind was measured too and rejected: it
    drops `-http://evil.com`, a one-character evasion. Bounded: 0.185s at 1M.
  - `_MD_LINK_RE` excludes `[`, matching `active_content.MD_LINK_RE` exactly,
    including the nested-label trade already documented there.

`sanitize` claimed "no catastrophic backtracking" in a comment; that claim was
wrong in the same way `output`'s was before 0.3.2, and is corrected in place.

676 tests (+10), coverage 128/128 + 6/6 gaps, sweep clean across 150 patterns.
The okf destination run gets no row: `[^)\s]+` cannot fail, so a row for it
could never go red.
2026-08-01 20:06:36 +02:00
abbfe5f0fd docs(redos): sweep every regex surface, not just the lexicon
0.3.3 swept the 83 lexicon patterns arm-by-arm and left the other tables on
0.3.2's hand-written rows -- the class of sweep that misses arms. Generalise the
generator over all eleven regex-bearing modules and make the collector mechanical
on both axes: walk each module namespace for compiled patterns (a pattern added
later is swept without anyone listing it) and derive each one's call mode by
grepping the module source, since `.sub()`/`.finditer()` visit every start
position where `.match()` cannot. Patterns reached only through a helper
parameter get the worst mode, marked `*`, so the fallback can over-measure but
never miss.

Sweeps 137 patterns across 11 tables where 0.3.3 covered 83 in one.
2026-08-01 19:57:59 +02:00
23475f9ec6 docs(redos): preserve the arm-by-arm sweep that found the 0.3.3 patterns
The generator, not just its results. It synthesises an almost-match sample from
each regex's own skeleton and uses every token-boundary prefix as a repeating
unit, so `[`, `[system]` and `[system](` are each probed as separate arms.

Committed because the next step depends on it: 0.3.2 swept the other detector
tables with hand-crafted rows, and this session showed that class of sweep
misses arms -- a generic-payload pass found only one of the two patterns here.

Docstring carries the caveat the script cannot enforce: the ratio is computed
from two points, so a hit near the noise floor is a coin flip. Post-fix it flags
sub-agent:delegate-bypass at x2.8; measured over four doublings the exponent is
0.96-1.05, i.e. linear, 0.2s at the cap. Re-measure before concluding.

Not part of the package -- docs/, stdlib-only, no effect on the wheel.
2026-07-31 21:55:51 +02:00