{"id":"CVE-2026-116795","revision":null,"records":[{"revision":"6df8f1a034b7419b813952989444a227","observedAt":"2026-10-28T18:46:48.818+00:00","statement":{"source":"cve-program","id":"CVE-2026-116795","aliases":[],"title":"RE2JS: Quadratic-time denial of service in re2js lookbehind matching","description":"RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching. From 2.2.0 until 2.8.7, enabling LOOKBEHINDS and using repeated matching through matcher().find(), matchAll(), split(), or matcher().replaceAll() can cause quadratic total work. Machine.match restarts lookbehind scanning from offset zero for each requested start position, while Matcher-based APIs request capture slots that prevent the early exit used for captureless matching. Dense matches therefore repeatedly rescan the input, allowing attacker-influenced text to monopolize a single-threaded worker and deny service. This issue is fixed in version 2.8.7.","state":0,"publishedAt":"2026-10-28T17:14:15.988+00:00","modifiedAt":"2026-10-28T17:14:15.988+00:00","affected":[{"ecosystem":"Vendor product","name":"re2js","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"le0pard\",\n                    \"product\": \"re2js\",\n                    \"versions\": [\n                        {\n                            \"version\": \">= 2.2.0, < 2.8.7\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5,"severity":"HIGH"}],"references":["https://github.com/le0pard/re2js/security/advisories/GHSA-gg6h-894w-4m9c","https://github.com/le0pard/re2js/pull/59","https://github.com/le0pard/re2js/commit/dfdd947785dfa113bdc97416375c246a39458395","https://github.com/le0pard/re2js/releases/tag/2.8.7"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-116795","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-116795","identifiers":["CVE-2026-116795"]},"retained":false,"historicalAffected":[]},{"revision":"bebf9bcdca1246caac31f1a14450b9a4","observedAt":"2026-10-30T22:47:56.412+00:00","statement":{"source":"euvd","id":"EUVD-2026-112268","aliases":["CVE-2026-116795","GHSA-gg6h-894w-4m9c"],"title":"RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching. From 2.2.0 until 2.8.7, enabling LOOKBEHINDS and using repeated matching through matcher().find(), matchAll(), split(), or matcher().replac…","description":"RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching. From 2.2.0 until 2.8.7, enabling LOOKBEHINDS and using repeated matching through matcher().find(), matchAll(), split(), or matcher().replaceAll() can cause quadratic total work. Machine.match restarts lookbehind scanning from offset zero for each requested start position, while Matcher-based APIs request capture slots that prevent the early exit used for captureless matching. Dense matches therefore repeatedly rescan the input, allowing attacker-influenced text to monopolize a single-threaded worker and deny service. This issue is fixed in version 2.8.7.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5,"severity":null}],"references":["https://github.com/le0pard/re2js/security/advisories/GHSA-gg6h-894w-4m9c","https://github.com/le0pard/re2js/pull/59","https://github.com/le0pard/re2js/commit/dfdd947785dfa113bdc97416375c246a39458395","https://github.com/le0pard/re2js/releases/tag/2.8.7"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-112268","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 28, 2026, 5:14:15 PM","dateUpdated":"Oct 28, 2026, 5:14:15 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-112268","identifiers":["EUVD-2026-112268","CVE-2026-116795","GHSA-gg6h-894w-4m9c"]},"retained":false,"historicalAffected":[]},{"revision":"2048fb72325141acbb745fd84e5b7023","observedAt":"2026-10-30T22:45:44.312+00:00","statement":{"source":"nvd","id":"CVE-2026-116795","aliases":[],"title":"RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching. From 2.2.0 until 2.8.7, enabling LOOKBEHINDS and using repeated matching through matcher().find(), matchAll(), split(), or matcher().replac…","description":"RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching. From 2.2.0 until 2.8.7, enabling LOOKBEHINDS and using repeated matching through matcher().find(), matchAll(), split(), or matcher().replaceAll() can cause quadratic total work. Machine.match restarts lookbehind scanning from offset zero for each requested start position, while Matcher-based APIs request capture slots that prevent the early exit used for captureless matching. Dense matches therefore repeatedly rescan the input, allowing attacker-influenced text to monopolize a single-threaded worker and deny service. This issue is fixed in version 2.8.7.","state":0,"publishedAt":"2026-10-28T18:17:14.303+00:00","modifiedAt":"2026-10-29T16:19:37.75+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5,"severity":"HIGH"}],"references":["https://github.com/le0pard/re2js/commit/dfdd947785dfa113bdc97416375c246a39458395","https://github.com/le0pard/re2js/pull/59","https://github.com/le0pard/re2js/releases/tag/2.8.7","https://github.com/le0pard/re2js/security/advisories/GHSA-gg6h-894w-4m9c"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-116795","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Awaiting Analysis","weaknesses":["CWE-407","CWE-1333"],"productCriteria":[],"sourceDates":{"published":"2026-10-28T18:17:14.303","lastModified":"2026-10-29T16:19:37.750"},"relatedAdvisories":[],"note":"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."},"key":"nvd:CVE-2026-116795","identifiers":["CVE-2026-116795"]},"retained":false,"historicalAffected":[]},{"revision":"e3c757720e574e0a856f53e2a9d9804c","observedAt":"2026-10-28T21:55:29.474+00:00","statement":{"source":"osv","id":"GHSA-gg6h-894w-4m9c","aliases":["CVE-2026-116795"],"title":"RE2JS: Quadratic-time denial of service in re2js lookbehind matching","description":"### Summary\n\nUsing the opt-in `LOOKBEHINDS` flag together with any repeated-matching API\n(`matcher().find()` in a loop, `matchAll()`, `split()`, `matcher().replaceAll()`)\ncauses quadratic-time (Θ(n²)) matching, contradicting the library's own\n\"linear time matching\" guarantee. A single call with a 16,000-character input\ntakes **36.7–38.7 seconds of CPU**.\n\n### Description\n\n#### Root cause\n\nIn `build/index.cjs`, `Machine.match`:\n\n```js\nlet currentPos = this.prog.numLb > 0 ? 0 : pos;   // ~line 1907\n...\nif (this.ncap === 0 && this.matched) break;        // ~line 1953\n```\n\nAny pattern containing a lookbehind sets `prog.numLb > 0`, which forces the\nscan to **always restart at offset 0**, regardless of the requested start\nposition — the lookbehind automaton is re-seeded and the whole input is\nre-walked on every single `find()` call. The early-exit optimization that\nwould otherwise stop at the first match only fires when `ncap === 0`, but\nevery `Matcher`-based API (`find`/`matchAll`/`split`/`replaceAll`) always\nrequests `ncap = 2` (one capture group slot) **even for a captureless\nlookbehind pattern** — so the early exit never triggers through those APIs,\nand the scan continues to the end of the input on every find.\n\nA find-loop over *k* matches in an *n*-character string therefore costs\n*k* × Θ(*n*); for dense matches *k* ∝ *n*, giving **Θ(n²)** total.\n\n#### Isolating control\n\nIdentical find-loop, identical input sizes, same 2-character literal pattern\n*shape* — but **without** the `LOOKBEHINDS` flag:\n\n| n | with `LOOKBEHINDS` (lookbehind pattern) | without (plain literal) | native JS `RegExp` lookbehind |\n|---|---|---|---|\n| 2,000 | 204–228 ms | 3 ms | 0 ms |\n| 4,000 | 877–899 ms | 4 ms | 0 ms |\n| 8,000 | 5,746–6,599 ms | 7 ms | 1 ms |\n| 16,000 | 36,667–38,732 ms | 6 ms | 1 ms |\n\nThis isolates the lookbehind machinery itself — not pattern length, not\nmatch density in the abstract — as the exact and sole cause.\n\n#### Impact\n\n- `re2js`'s entire value proposition is being an RE2-style engine with\n  **linear-time, no-backtracking** matching, specifically to resist\n  algorithmic-complexity attacks that plague backtracking regex engines.\n  This finding defeats that guarantee for any consumer using the advertised\n  `LOOKBEHINDS` feature.\n- Realistic affected code paths: log/text processing that tokenizes or\n  redacts using a lookbehind-based pattern (`matchAll`, `split`,\n  `replaceAll` are the natural APIs for exactly this), applied to\n  attacker-influenced text (user input, uploaded logs, scraped content).\n- A single ~16–32 KB request pins a CPU core for tens of seconds to minutes;\n  Node.js being single-threaded, this freezes the whole worker process for\n  the duration. No authentication or special pattern knowledge is required\n  from the attacker — ordinary alternating text (`\"abababab...\"`) triggers it.\n\n#### Reproduction (copy-paste runnable)\n\ncreate file poc.js\n\n```js\n'use strict';\n// PoC: quadratic-time DoS in re2js's lookbehind matching (opt-in LOOKBEHINDS flag).\n// Usage:\n//   npm pack re2js@2.8.6 --ignore-scripts && tar -xzf re2js-2.8.6.tgz\n//   node poc.js ./package\nconst path = require('path');\nconst pkgDir = process.argv[2] || './package';\nconst { RE2JS } = require(path.resolve(pkgDir, 'build/index.cjs'));\n\nconst re = RE2JS.compile('(?<=a)b', RE2JS.LOOKBEHINDS);\n\nfunction timeFindLoop(n) {\n  const s = 'ab'.repeat(n / 2);\n  const t0 = Date.now();\n  const m = re.matcher(s);\n  let count = 0;\n  while (m.find()) count++;\n  return { ms: Date.now() - t0, count };\n}\n\nconsole.log('n, matches, ms');\nconst sizes = [2000, 4000, 8000, 16000];\nconst results = [];\nfor (const n of sizes) {\n  const r = timeFindLoop(n);\n  results.push(r.ms);\n  console.log(`${n}, ${r.count}, ${r.ms}`);\n}\n\nconst ratio = results[results.length - 1] / results[results.length - 2];\nconsole.log('');\nconsole.log('time ratio for the last doubling of n:', ratio.toFixed(2), '(~2 => linear, ~4 => quadratic)');\nconsole.log(ratio > 3 ? 'PASS  quadratic scaling confirmed on the REAL published package' : 'FAIL  scaling looks linear');\nprocess.exit(ratio > 3 ? 0 : 1);\n```\n\nand run in terminal\n\n```sh\nnpm pack re2js@2.8.6 --ignore-scripts\ntar -xzf re2js-2.8.6.tgz\nnode poc.js ./package\n```\n\n`poc.js` times a `matcher(s).find()` loop over `(?<=a)b` +\n`RE2JS.LOOKBEHINDS` on inputs of 2,000/4,000/8,000/16,000 chars and reports\nthe doubling ratio. Expected: clearly superlinear (ratio ≫ 2, approaching or\nexceeding the 4× quadratic signature), reaching tens of seconds at 16,000\nchars. Run twice; results are deterministic.","state":0,"publishedAt":"2026-10-28T21:10:01+00:00","modifiedAt":"2026-10-28T21:30:03.8862081+00:00","affected":[{"ecosystem":"npm","name":"re2js","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"2.2.0"},{"kind":"fixed","version":"2.8.7"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/le0pard/re2js/security/advisories/GHSA-gg6h-894w-4m9c","https://nvd.nist.gov/vuln/detail/CVE-2026-116795","https://github.com/le0pard/re2js/pull/59","https://github.com/le0pard/re2js/commit/dfdd947785dfa113bdc97416375c246a39458395","https://github.com/le0pard/re2js","https://github.com/le0pard/re2js/releases/tag/2.8.7"],"sourceUrl":"https://github.com/advisories/GHSA-gg6h-894w-4m9c","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-gg6h-894w-4m9c","identifiers":["GHSA-gg6h-894w-4m9c","CVE-2026-116795"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-31T00:45:27.0287685+00:00","complete":true,"records":1766,"lastAttemptAt":"2026-10-31T00:45:27.0287685+00:00","lastSuccessAt":"2026-10-31T00:45:27.1820559+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:27.1820567+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-7e2c4e7288f2c464ad8c61f3bb6017374714faedcc781283ac06a706ee1a820b-2ab14bbac7aed146eb4566d0c0f2f6122020a00e6e03e98c548f6ca78b564d0c","complete":true,"records":417480,"lastAttemptAt":"2026-10-31T01:45:32.282049+00:00","lastSuccessAt":"2026-10-30T23:45:37.8421483+00:00","error":null,"lastVerifiedAt":"2026-10-30T23:45:38.1384309+00:00","snapshotPublishedAt":"2026-10-30T23:26:20+00:00","importing":true,"stagedRecords":358500,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-39149994e3854f27b5e713b62480af55","complete":true,"records":13563,"lastAttemptAt":"2026-10-31T00:45:56.6248575+00:00","lastSuccessAt":"2026-10-31T00:45:56.62+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:56.62+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13635,"expectedIdentifiers":13635,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-31T00:45:27.0287685+00:00","complete":true,"records":1094,"lastAttemptAt":"2026-10-31T00:45:27.0287685+00:00","lastSuccessAt":"2026-10-31T00:45:27.1829931+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:27.1829933+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-01dfb738868f495991dd71b57183de53","complete":false,"records":13575,"lastAttemptAt":"2026-10-31T00:45:54.6049966+00:00","lastSuccessAt":"2026-10-31T00:45:54.602+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:54.602+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13599,"expectedIdentifiers":13635,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-21dcd6662970dfea02e4ea60ee637115e8d92664947484d2b9276b3f6568a84f","complete":true,"records":9815,"lastAttemptAt":"2026-10-31T00:45:26.9752288+00:00","lastSuccessAt":"2026-10-30T23:47:34.1817587+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:26.9752288+00:00","snapshotPublishedAt":"2026-10-30T23:33:17+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-2de9ccc5290f060c7aa63f89c25dd76f655a64ba1aba7c05c97fa3bee4f09899","complete":true,"records":1967,"lastAttemptAt":"2026-10-31T00:45:26.4902262+00:00","lastSuccessAt":"2026-10-28T20:55:34.2982937+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:26.4902262+00:00","snapshotPublishedAt":"2026-10-28T20:03:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-f95f76cd406ae5bae3de632ad37fd2d5160b44f5e6b51748ac6f8f0c0c88e72b","complete":true,"records":3028,"lastAttemptAt":"2026-10-31T00:45:26.9885441+00:00","lastSuccessAt":"2026-10-30T23:47:38.4914494+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:26.9885441+00:00","snapshotPublishedAt":"2026-10-30T23:18:12+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-3bba47d78eb61bae29443e8f4fe215a6614d54f3560d07419145a76e9ac024ad","complete":true,"records":230992,"lastAttemptAt":"2026-10-31T00:45:26.8701271+00:00","lastSuccessAt":"2026-10-30T23:47:24.2343384+00:00","error":null,"lastVerifiedAt":"2026-10-31T00:45:26.8701271+00:00","snapshotPublishedAt":"2026-10-30T23:18:17+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}