GitHub Advisoryadvisory · npm
GHSA-8hcv-x26h-mcgp node-re2: String.prototype.replace(re2, template) aborts the Node process (uncatchable ToLocalChecked on empty MaybeLocal) when the result exceeds V8's max string length
MEDIUM
Official page ↗
- Aliases
- CVE-2026-71430
- Ecosystem
- npm
- Packages
- re2
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- Weaknesses
- CWE-617
- Reviewed
- 2026-08-06
Affected packages
• npm re2: < 1.25.1 → fixed in 1.25.1
Description
Description
`WrappedRE2::Replace` builds the replacement result and hands it to V8 with `.ToLocalChecked()` without checking for the empty `MaybeLocal` that V8 returns when the string/buffer exceeds its maximum length:
`lib/replace.cc` (v1.24.1):
```cpp
// L553 — Buffer return path
info.GetReturnValue().Set(Nan::CopyBuffer(result.data(), result.size()).ToLocalChecked());
// L556 — String return path
info.GetReturnValue().Set(Nan::New(result).ToLocalChecked());
```
When a global replace uses an output-amplifying template — `$'` (text after the match) or `` $` `` (text before the match) — the result grows to O(input²). For an input of ~40,000+ identical single-char matches the result exceeds V8's `String::kMaxLength` (~536,870,888 chars on 64-bit). `Nan::New(result)` then returns an empty `MaybeLocal`, and the unchecked `.ToLocalChecked()` calls `v8::Utils::ReportApiFailure` → `FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal` → `abort()` (SIGABRT).
This is an uncatchable crash: it is not a JavaScript exception, so a surrounding `try/catch` cannot stop it — the entire Node process (or worker) dies.
The built-in regex engine handles the identical case correctly by throwing a *catchable* `RangeError: Invalid string length`. node-re2 diverges from that contract and aborts instead.
Proof of concept
```
npm i re2
node poc.js
```
```js
const RE2 = require('re2');
// Built-in engine: same case -> CATCHABLE RangeError (correct)
try { 'a'.repeat(50000).replace(/a/g, "$'"); }
catch (e) { console.log('native:', e.constructor.name, e.message); } // RangeError: Invalid string length
// re2: ABORTS the whole process (uncatchable; try/catch does not help)
'a'.repeat(50000).replace(new RE2('a', 'g'), "$'");
// -> FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal (process exits 134 / SIGABRT)
```
Observed (Node v24, clean `npm i re2` → [email protected]): native branch prints `RangeError: Invalid string length`; the re2 branch aborts with `FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal`, stack top `WrappedRE2::Replace`, process exit code 134.
Threshold matches the mechanism precisely: input of 30,000 chars completes; 40,000 aborts (30000²/2 ≈ 4.5e8 max). `$&`/constant templates and non-global replaces do not amplify and do not crash.
Impact
A remote, unauthenticated denial of service against any service that runs `String.prototype.replace` / the re2 `[Symbol.replace]` path where either the replacement template (containing `$'` or `` $` ``) or the input size is attacker-influenced. Because the failure is a native `abort()`, it cannot be contained by `try/catch` or domains — one request takes down the whole process/worker. This is especially impactful for re2's core audience, who adopt it specifically to process untrusted patterns/inputs safely.
Suggested fix
Check the `MaybeLocal` before `ToLocalChecked` on both return paths (and the intermediate group-string builds), and throw a catchable `RangeError` to match the built-in engine:
```cpp
auto maybe = Nan::New(result);
if (maybe.IsEmpty()) { Nan::ThrowRangeError("Invalid string length"); return; }
info.GetReturnValue().Set(maybe.ToLocalChecked());
```
(Apply equivalently to the `Nan::CopyBuffer(...)` buffer path at L553 and to the per-group `Nan::New(data, size).ToLocalChecked()` sites used by the replacer-function path.)
Resolution
Resolved in `re2` `1.25.1`. `WrappedRE2::Replace` now checks the returned `MaybeLocal` on every result path and throws a catchable `RangeError: Invalid string length` (matching the built-in engine) instead of aborting the process with an uncatchable `SIGABRT`. No API changes --- upgrade to `re2` >= `1.25.1` via a plain `npm upgrade` to receive the fix.
Related
Alias · CVE (1)
- CVE-2026-71430 node-re2 provides RE2 regular expression bindings for Node.js. vulnerability
Weakness · CWE (1)
- CWE-617 Reachable Assertion weakness