Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 272/272 feeds/APIs · 469/469 docs · 8/9 advisories · checked 13:41 UTC Customize Public modeDiscuss ChangeIntel on Discord

re2: Global `String.prototype.match` with an empty-matchable pattern never advances → infinite loop with unbounded native memory growth (DoS)

Collected exploitation and prediction evidence

Risk source coverage

CISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.

No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.

cve-program · CVE-2026-68499

Published

re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.

Source published
30 Jul 2026 20:07:01 UTC
Source modified
31 Jul 2026 19:19:08 UTC
Observed here
23 Oct 2026 15:48:27 UTC
Source terms
CVE Program Terms of Use

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 3.1 · 6.2 CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

nvd · CVE-2026-68499

Published

re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.

NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.

Source analysis status: Awaiting Analysis

Weakness classifications: CWE-835

Original publisher date fields

published: 2026-07-30T21:18:12.870

lastModified: 2026-10-23T22:10:00.160

Source published
30 Jul 2026 21:18:12 UTC
Source modified
23 Oct 2026 22:10:00 UTC
Observed here
25 Oct 2026 15:18:51 UTC
Source terms
NIST NVD usage terms; underlying source attribution retained

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 3.1 · [email protected] · Secondary · 6.2 CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

osv · GHSA-6hxr-mr5r-9836

Published

## Summary `String.prototype.match` with a **global** `RE2` collects all matches in a native loop that advances the cursor by the match length. A **zero-width (empty) match** has length 0, so the cursor never advances: the same empty match is found forever and appended to an ever-growing native vector. Any pattern that can match the empty string (`a*`, `b?`, `x{0,3}`, `(a)|`, `(?:)`, …) therefore causes an infinite loop with unbounded memory growth. The call is synchronous native code, so it blocks the entire event loop and cannot be interrupted by `try/catch`, `AbortController`, `--max-old-space-size`, or timers — the process must be killed externally. This diverges from the built-in engine, where `'xxxx'.match(/a*/g)` returns a finite array. ## Root cause ```cpp // lib/match.cc:44 — global branch of WrappedRE2::Match while (re2->regexp.Match(str, byteIndex, str.size, anchor, &match, 1)) { groups.push_back(match); byteIndex = match.data() - str.data + match.size(); // += 0 for a zero-width match } ``` When `match.size() == 0`, `byteIndex` is unchanged, so the next iteration matches the same empty position again; `groups` grows without bound. The other iteration paths already guard this: `lib/split.cc:50-55` advances by `getUtf8CharSize` on an empty match, and `exec` advances `lastIndex`. Only this global `Match` loop is missing the guard. ## Proof of concept ```js const RE2 = require('re2'); 'x'.match(new RE2('a*', 'g')); // never returns; grows memory until OOM // also: 'b?', 'x{0,3}', '(a)|', 'c*d*', '(?:)'; empty subject '' triggers it too ``` Compare with the built-in engine, which terminates: ```js 'xxxx'.match(/a*/g); // -> ["", "", "", "", ""] ``` Measured on a clean `npm install [email protected]` (latest), stock prebuilt binary: resident memory grew **~550 MB → 2.3 GB in ~3 seconds** at 100% CPU, and the process had to be `SIGKILL`ed externally. ## Impact Denial of service. Reachable remotely and without authentication wherever an application runs a **global** `RE2` through `String.prototype.match` and either the pattern or the subject is attacker-influenced — e.g. a user-supplied regular expression, or a fixed empty-matchable pattern applied to user input. Because the loop blocks the event loop and exhausts memory in seconds, a single request can wedge a worker and, via memory exhaustion, affect the whole host. ## Suggested fix Mirror the empty-match handling already present in `split.cc`: when the match is zero-width, advance the cursor by one code point. ```cpp // lib/match.cc, inside the global while-loop groups.push_back(match); size_t off = match.data() - str.data; if (match.size()) { byteIndex = off + match.size(); } else { byteIndex = off + (off < str.size ? getUtf8CharSize(str.data[off]) : 1); } ``` ## Resolution Fixed in re2 1.25.2. The global match loop in `lib/match.cc` now advances the cursor by one Unicode code point when a match is zero-width, so a pattern that can match the empty string terminates with a finite result identical to the built-in engine (`'xxxx'.match(/a*/g)` returns five empty strings). This mirrors the guard already present in `split`. **Remediation:** upgrade to `[email protected]` or later. **Workaround** (if you cannot upgrade): do not run a global `RE2` through `String.prototype.match` when the pattern is attacker-influenced or can match the empty string. Iterate with `matchAll`/`exec`, or use the non-global form; both already advanced the cursor correctly.

Source published
31 Jul 2026 16:53:08 UTC
Source modified
31 Jul 2026 17:00:21 UTC
Observed here
24 Oct 2026 13:17:17 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS_V3 · CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Publisher rating · MODERATE

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 26 Oct 13:41 UTC · 272 of 272 readable · 270 fully read · documentation 469/469 current