Skip to content

fix: replace three quadratic regexes with linear scans - #445

Open
Wenjix wants to merge 1 commit into
hypit-ai:mainfrom
Wenjix:security/regex-quadratic-fixes
Open

Wenjix wants to merge 1 commit into
hypit-ai:mainfrom
Wenjix:security/regex-quadratic-fixes

Conversation

@Wenjix

@Wenjix Wenjix commented Oct 27, 2026 •

Copy link
Copy Markdown

Closes #453

Summary

Three quadratic-regex findings (~1 s at ~50 KB of adversarial input, all on parse paths reachable from authored files):

  • 20 (medium, CWE-1333): dedent in packages/text-fine/src/surface.ts stripped the trailing blank run with /\n\s*$/. On failure the engine retries the tail from every position — "\n" × N plus a non-matching tail is O(N²). The run is now removed by scanning backward over whitespace, keeping the exact rule (drop the tail only when it begins with a newline).
  • 30 (medium): parseAttributes in packages/recipe/src/parser.ts matched each <sheet> attribute with /([a-z][a-z0-9-]*)\s*=\s*(["'])(.*?)\2/g. A long run of name characters with no = backtracks once per position. Replaced with a hand scanner that walks name → = → quoted value directly. Same error codes (SVS_SHEET_ATTRIBUTE, …_DUPLICATE), same positions on the malformed path, and the old single-line-value rule is kept — . never matched line terminators, so a value containing \n/\r/U+2028/U+2029 still fails as malformed.
  • 25 (medium): parameter source ranges in packages/studio/src/parameters.ts located name = at the end of the attribute prefix with a regex anchored by $ — still O(prefix × ws-run) because every offset is retried. A backward scan finds the same boundary (<ws>+ name <ws>* = <ws>* quote) in linear time and yields the identical start index; equivalence checked across suffix-name (xstart vs start), missing-= and boundary cases.

Behavior relative to main

Input main this PR
50 KB trailing-newline text through dedent ~880 ms ~1.5 ms
50 KB name-run <sheet aaaa…> ~1.1 s ~2 ms, same Malformed error
50 KB whitespace before name=" ~850 ms ~0.005 ms, same range
valid inputs in all three accepted, same result accepted, same result (byte-equal where observable)

Red / green

Existing suites cover semantics: recipe attribute acceptance/duplicates/malformed cases, studio parameter ranges assertions, text-fine surface decoding — 30 tests pass unchanged. Equivalence of the three replacements was additionally verified by running old-and-new implementations side by side on edge cases before landing.

Verification

  • node --import tsx --test packages/recipe/test/source-closure.test.ts packages/studio/test/parameters.test.ts packages/studio/test/parameter-companions.test.ts packages/studio/test/attribute-groups.test.ts packages/text-fine/test/text-fine.test.ts — 30 pass.
  • pnpm check — clean.
  • Timings above measured on this machine, before/after.

Merge Danger

  • None user-visible: same accepted language, same errors, same produced ranges; only the algorithm changed.

Checklist

  • The why is documented in this description.
  • Covered by existing suites + explicit equivalence checks.
  • TypeScript check clean.

Generated with Devin

Three hotspots retried a pattern from every input position, so a long
malformed tail cost O(n^2) on the parse thread:

- text-fine dedent() dropped the trailing blank run with /\n\s*$/;
  scanning backward over whitespace keeps the same semantics.
- recipe parseAttributes() matched each attribute with
  /([a-z][a-z0-9-]*)\s*=\s*(["'])(.*?)\2/g; a long run of name
  characters backtracked at every position. A hand scanner parses
  name/=/quoted-value directly and keeps the same error codes,
  duplicate detection and single-line-value rule.
- studio parameter source ranges located "name =" with a regex
  anchored at the prefix end; it still tried every offset. A backward
  scan finds the same start.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 27, 2026 06:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: three parse-path regexes run quadratic on malformed input

2 participants