fix(#2128): bound the phase-tag clause to {0,200} — kill quadratic ReDoS
The canonical OPTIONAL_PHASE_TAG_SOURCE tag clause `(?:\s*\([^)\n]*\))?` (and its
inlined literal mirrors across 11 modules) had an UNBOUNDED body, making the
optional-group + /g header scan quadratic on adversarial ROADMAP.md/STATE.md — a
long run of `(` after a header ran ~18.8s at 1.7MB. Bound the body to {0,200} in
the constant AND every mirror in lockstep (the #1729 "both forms change together"
contract), so the scan is linear: the same 1.7MB input now resolves in ~9ms
(measured), while real tags (a handful of chars) still match and a 201-char tag
is rejected. Added a #2128 boundary regression to the #1729 suite.
Pre-existing (byte-identical before/after the Phase 4 migrations); folded in at
maintainer direction rather than deferred.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -37,9 +37,9 @@ const OPTIONAL_PROJECT_CODE_PREFIX_SOURCE = '(?:[A-Z][A-Z0-9_]*-)?';
|
||||
// Enumeration/parse call sites that read phase headers from a regex *literal*
|
||||
// (rather than a `new RegExp` built from an interpolated phase number) cannot
|
||||
// reference this constant; they inline its literal-regex mirror instead —
|
||||
// `(?:\s*\([^)\n]*\))?` — kept character-for-character equivalent to this
|
||||
// `(?:\s*\([^)\n]{0,200}\))?` — kept character-for-character equivalent to this
|
||||
// source. Both forms must change together; see the #1729 regression test.
|
||||
const OPTIONAL_PHASE_TAG_SOURCE = '(?:\\s*\\([^)\\n]*\\))?';
|
||||
const OPTIONAL_PHASE_TAG_SOURCE = '(?:\\s*\\([^)\\n]{0,200}\\))?';
|
||||
|
||||
// #2128: the canonical phase-NUMBER-TOKEN grammar — a phase number with an
|
||||
// optional single-letter variant suffix and optional dotted sub-phases
|
||||
|
||||
Reference in New Issue
Block a user