fix(#3477): run untrusted key_links patterns on a linear-time engine (#3496)

`cmdVerifyKeyLinks` compiled `must_haves.key_links[].pattern` from plan frontmatter with `new RegExp()` and tested it against whole file contents, so a nested-quantifier pattern such as `(a+)+$` hung `verify-phase` indefinitely (CWE-1333). JavaScript has no regex-execution timeout.

Untrusted patterns now run on RE2 (re2js), whose match time is linear in input length — the class is closed by the engine, not by a heuristic screen. The screen lost in the ADR-0174 consolidation was deliberately NOT restored: it never worked, since `(a|a)*$`, `((a+))+$`, `(a+){2,}$` and `(a{1,3})+$` all evade it. A refused pattern's matcher returns false for every input, so it cannot report a match no matter what the caller does.

The engine is vendored at gsd-core/bin/lib/vendor/re2js.cjs because gsd-core/bin/** is copied into installed trees with no node_modules; runtime dependencies are unchanged. New ESLint rule local/no-external-require-in-bin enforces that invariant, which had been documented in a comment since the #3024/#2071 bug class and enforced nowhere.

Backreferences and look-around are unsupported by RE2 by construction — disclosed in a Changed changeset.

Closes #3477

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-08-14 14:34:36 -04:00
committed by GitHub
parent b946051a46
commit 895d9df96d
19 changed files with 9067 additions and 17 deletions

View File

@@ -0,0 +1,5 @@
---
type: Security
pr: 3496
---
**`verify key-links` can no longer be hung by a plan's `key_links` pattern** — the pattern was compiled straight from plan frontmatter with a backtracking engine and tested against whole file contents, so a nested-quantifier pattern such as `(a+)+$` pinned a CPU core indefinitely and stalled any `verify-phase` run that reached it. Untrusted patterns now execute on the RE2 engine, whose match time is linear in the input length, and a pattern that cannot be compiled is refused outright rather than guessed at — a refused pattern can never report a match. (#3477)

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3496
---
**`must_haves.key_links[].pattern` now uses RE2 syntax** — backreferences and look-around are no longer supported in a key-links pattern, because they are the constructs that require a backtracking engine and cannot be evaluated in guaranteed linear time. A pattern using them is reported as `pattern_neutralized: "unsupported"` with the link marked unverified, rather than being silently matched as literal text. Ordinary patterns, including every example shipped in the docs, are unaffected. (#3477)