Vulnerabilities in pkg:npm/re2
PACKAGECVE-2026-71498 is a MODERATE (CVSS 5.1) out-of-bounds heap read vulnerability (CWE-125: Out-of-bounds Read) affecting node-re2 libraries in the npm ecosystem. The issue involves out-of-bounds heap read in `replace`/`split` via a `buffer` ending in a truncated multi-byte utf-8 character → adjacent heap memory disclosed to javascript. Remediation is available by upgrading affected packages to version 1.26.1.
pkg:npm/re2
Vulnerability advisories affecting package pkg:npm/re2. Select an advisory below to view technical details or fixes.
| Advisory ID | CVE / NVD | Severity | Summary |
|---|---|---|---|
| GHSA-j4r3-hg7j-8chg | CVE-2026-71498 | MODERATE | node-re2: Out-of-bounds heap read in `replace`/`split` via a `Buffer` ending in a truncated multi-byte UTF-8 character → adjacent heap memory disclosed to JavaScript |
| GHSA-8hcv-x26h-mcgp | CVE-2026-71430 | MODERATE | 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 |
| GHSA-ff84-5f28-78qj | CVE-2026-67550 | MODERATE | re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS) |
| GHSA-6hxr-mr5r-9836 | CVE-2026-68499 | MODERATE | re2: Global `String.prototype.match` with an empty-matchable pattern never advances → infinite loop with unbounded native memory growth (DoS) |
CVE-2026-71498 at a Glance
Security Impact
CVSS: 5.1
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L).
How to Fix CVE-2026-71498
To remediate this vulnerability, upgrade all affected node-re2 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install re2@1.26.1
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls re2
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-71498, inspect your project dependencies using npm:
npm ls re2
Analyze the terminal output to locate any version of node-re2 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.26.1), your application is affected.
Note: The npm ls re2 command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-125: Out-of-bounds Read, where the product reads data past the end, or before the beginning, of the intended buffer. In node-re2, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-71498 Affected node-re2 Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
re2
|
npm |
< 1.26.1
|
1.26.1
|
References
Frequently Asked Questions
What is CVE-2026-71498?
Which node-re2 versions are affected?
What version fixes CVE-2026-71498?
Is re2 affected?
What is the CVSS score of CVE-2026-71498?
What CWE is associated with CVE-2026-71498?
How can I check npm dependencies for CVE-2026-71498?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-j4r3-hg7j-8chg",
"modified" : "2026-08-06T21:26:14Z",
"published" : "2026-08-06T21:26:14Z",
"aliases" : [ "CVE-2026-71498" ],
"summary" : "node-re2: Out-of-bounds heap read in `replace`/`split` via a `Buffer` ending in a truncated multi-byte UTF-8 character → adjacent heap memory disclosed to JavaScript",
"details" : "## Summary\n\n`re2` infers a character's byte length from its UTF-8 lead byte alone, with no bound on the\nbytes actually remaining in the input. `Buffer` arguments reach the native layer verbatim —\nonly strings are re-encoded into well-formed UTF-8 — so a `Buffer` whose last byte is a\nmulti-byte lead promises continuation bytes that are not there, and the result builders read\nup to 3 bytes past the end of the buffer. In `replace()` and `split()` those bytes are copied\ninto the returned `Buffer`, disclosing adjacent heap memory to JavaScript. The trigger is\ndeterministic and requires no special heap grooming.\n\nOnly `Buffer` input is affected. String input was never at risk: re-encoding guarantees every\nmulti-byte sequence is complete.\n\n## Root cause\n\n`getUtf8CharSize` maps a lead byte to a length of 1–4 and never sees the input size:\n\n```cpp\n// lib/wrapped_re2.h\ninline size_t getUtf8CharSize(char ch)\n{\n return ((0xE5000000 >> ((ch >> 3) & 0x1E)) & 3) + 1;\n}\n```\n\nCallers then read that many bytes. In the zero-width branch of `replace()`, the guard proves\nonly that at least *one* byte remains:\n\n```cpp\n// lib/replace.cc\nelse if ((size_t)offset < size)\n{\n auto sym_size = getUtf8CharSize(data[offset]); // may claim up to 4 bytes\n result.append(data + offset, sym_size); // reads data[offset .. offset + 3]\n byteIndex = offset + sym_size;\n}\n```\n\n`offset < size` permits `offset == size - 1`, so a lead byte of `0xF0` makes `append` read\n`data[size]`, `data[size + 1]` and `data[size + 2]`.\n\nSeven read sites shared the defect:\n\n| Site | Argument | Disclosed to JS |\n|---|---|---|\n| `lib/replace.cc` (zero-width branch) | subject | yes |\n| `lib/replace.cc` (callback replacer) | subject | yes |\n| `lib/replace.cc` (replacement scan) | replacement | yes |\n| `lib/split.cc` | subject | yes |\n| `lib/pattern.cc` `translateRegExp` (x2) | pattern | no |\n| `lib/pattern.cc` `escapeRegExp` | pattern | no |\n\nThree further callers were **not** vulnerable, because they use the result only to advance an\nindex and never dereference past the end: `getUtf16PositionByCounter` in `lib/wrapped_re2.h`\n(clamps its return to the buffer size), `lib/match.cc` (the value feeds `RE2::Match`, which\nrejects `startpos > endpos`), and the `getMaxSubmatch` scan in `lib/replace.cc` (an overshoot\njust ends the loop).\n\n## Proof of concept\n\nEach call returns more bytes than were supplied; the trailing bytes are heap contents and vary\nbetween runs.\n\n```js\nconst RE2 = require('re2');\nconst hex = buf => [...buf].map(b => b.toString(16).padStart(2, '0')).join(' ');\n\n// subject: 2 bytes in, 5 bytes out\nconsole.log(hex(new RE2('', 'g').replace(Buffer.from([0x41, 0xf0]), '')));\n// 41 f0 61 7b eb <- last 3 bytes are adjacent heap memory\n\n// replacement argument\nconsole.log(hex(new RE2('A', 'g').replace(Buffer.from('A'), Buffer.from([0x42, 0xf0]))));\n// 42 f0 41 26 d6\n\n// split\nconsole.log(new RE2('', 'g').split(Buffer.from([0x41, 0xf0])).map(hex));\n// [ '41', 'f0 e2 e4 df' ]\n```\n\n`0xC2` (2-byte lead) and `0xE2` (3-byte lead) over-read 1 and 2 bytes respectively; `0xF0`\nover-reads 3.\n\nFor the pattern path the over-read occurs in `translateRegExp` / `escapeRegExp`, which run\nbefore RE2 validates the pattern, but RE2 then rejects the malformed input, so the bytes are\ndiscarded rather than returned:\n\n```js\nnew RE2(Buffer.from([0xf0])); // SyntaxError: invalid UTF-8 — read already happened\n```\n\n## Impact\n\n**Information disclosure (`replace`, `split`).** Up to 3 bytes of heap memory adjacent to the\ninput buffer are returned to JavaScript per call. The read is repeatable, so an attacker who\ncontrols `Buffer` input and observes output can sample heap memory incrementally. What lands\nthere depends on allocator layout and is not directly steerable, but it may include fragments\nof other buffers.\n\n**Out-of-bounds read (pattern compilation).** No disclosure path, since the malformed pattern\nis rejected — but the read is still undefined behavior and can fault if the buffer ends on a\npage boundary.\n\nApplications that pass only strings, or only well-formed UTF-8 buffers, are unaffected. The\nexposure matters most where `re2` is used as intended: running patterns or subjects derived\nfrom untrusted input.\n\n## Suggested fix\n\nClamp the inferred character size to the bytes that actually remain, at every site whose result\nindexes the buffer:\n\n```cpp\ninline size_t getUtf8CharSize(char ch, size_t remaining)\n{\n size_t size = getUtf8CharSize(ch);\n return size < remaining ? size : remaining;\n}\n```\n\nThis is O(1) and changes no algorithm's complexity. A truncated tail then round-trips as the\nbytes it really holds, which preserves the documented contract that `Buffer` input is passed\nthrough verbatim. Rejecting malformed UTF-8 in `Buffer` input would also close the hole, but\nis a breaking API change.\n\n## Resolution\n\nFixed in `re2@1.26.1`.\n\nAll seven read sites now clamp the character size to the remaining input, so a `Buffer` ending\nin a truncated multi-byte character round-trips as its own bytes instead of reading past the\nend. Regression tests cover the subject, replacement and pattern positions for 2-, 3- and\n4-byte leads, including partially truncated sequences.\n\n**Remediation:** upgrade to `re2@1.26.1` or later.\n\n**Workaround** (if you cannot upgrade): pass strings rather than `Buffer`s, or validate that\n`Buffer` input is well-formed UTF-8 before calling `replace`, `split`, or the `RE2`\nconstructor — for example `Buffer.compare(Buffer.from(buf.toString('utf8')), buf) === 0`.\n\nReported by [@OvOhao](https://github.com/OvOhao) in [#272](https://github.com/uhop/node-re2/issues/272).",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "re2"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.26.1"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 1.26.0"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/uhop/node-re2/security/advisories/GHSA-j4r3-hg7j-8chg"
}, {
"type" : "WEB",
"url" : "https://github.com/uhop/node-re2/issues/272"
}, {
"type" : "WEB",
"url" : "https://github.com/uhop/node-re2/commit/9d72042a6a0da5bc523908b04808ea0e23867cc4"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/uhop/node-re2"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-125" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-08-06T21:26:14Z",
"nvd_published_at" : null
}
}