## 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 < 5.37e8 max; 40000²/2 ≈ 8e8 > 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.
Rendering an untrusted `architecture-beta` diagram lets the diagram author write an arbitrary property with the value `horizontal` or `vertical` onto `Object.prototype`. A group id of `__proto__` is accepted as a valid parent. ### Impact Any code in the same realm that reads a property of that name from an arbitrary object, or enumerates an object with bare `for...in`, observes the injected value (which can only be the string `horizontal` or `vertical`. This may mean corrupted option/config defaults, bypassed truthiness checks, causing denial of service or logic corruption in the embedding application. Because the injected value cannot be an object or function, this is not directly exploitable for remote code execution. ### PoC ``` architecture-beta group mermaidPrototypePollutionMarker(cloud)[Marker] service a(server)[A] in __proto__ service b(server)[B] in mermaidPrototypePollutionMarker a:R -- L:b ``` The vulnerable write was introduced in commit [cb0a4703bdf01d47508bde1c08aa9a980d70bc20](https://github.com/mermaid-js/mermaid/commit/cb0a4703bdf01d47508bde1c08aa9a980d70bc20) and first shipped in `[email protected]`. The lines are unchanged in every release since. ### Patches This has been patched by https://github.com/mermaid-js/mermaid/commit/99af3fc35ef0a9a9c8c6314521344d67523ddccf, released in [Mermaid v11.16.1](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.16.1) ### Workarounds There are no known workarounds. Please update to a patched version. ### References _Are there any links users can visit to find out more?_ - https://github.com/mermaid-js/mermaid/commit/99af3fc35ef0a9a9c8c6314521344d67523ddccf - https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.16.1