Files
Anas Khan d6a2b39252 fix(utils): prevent ReDoS in social email regex (#3845)
```markdown
`social.parseHandlesFromHtml()` runs `emailsFromText()` on every text node of a
scraped page (`packages/utils/src/internals/social.ts`), and `emailsFromText()` /
`emailsFromUrls()` are exported directly. All of them match untrusted text against
`EMAIL_REGEX_GLOBAL`, built from `EMAIL_REGEX_STRING`.

The dot-atom local part `[...]+(?:\.[...]+)*` and each domain label
`[a-z0-9-]*[a-z0-9]` use unbounded quantifiers. On a long dotted or hyphenated
input that never completes a valid address, e.g. `x` + `.a`*n + `@` (no domain),
V8 explores every way to split the run and backtracks quadratically. Matching time
grows ~4x per input doubling (O(n^2)): a single ~60 KB text node blocks the event
loop for seconds, and it scales to tens of seconds on a larger page, so one crafted
crawled page can stall the crawler (the matching worker is single-threaded). This is
CWE-1333 (ReDoS).

The fix bounds those quantifiers with RFC 5321 lengths instead of leaving them
unbounded: local-part atoms `{1,64}`, dot-groups `{0,32}`, and the domain-label
inner run `{0,62}`. That makes the match linear while still accepting every
realistic address (a max-legal 64-char local part and 63-char label still match).
The quoted-string and IP-literal alternatives are unchanged. Only inputs longer than
what a valid email can contain are affected; matching against the original regex over
hundreds of thousands of random and structured strings showed no difference for any
in-range input.

A pure non-backtracking rewrite is not possible here: V8 has no atomic groups or
possessive quantifiers, and an anchored-lookahead form cannot be used because
`EMAIL_REGEX_GLOBAL` runs unanchored over scraped text.

Added an `emailsFromText()` regression test that feeds the adversarial inputs and
asserts they return `[]` well within a time budget; it fails on the old regex
(tens of seconds) and passes on the fixed one.
```

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
2026-07-15 14:25:52 +02:00
..
2026-01-06 14:24:06 +01:00
2026-01-06 14:24:06 +01:00