{"id":"GHSA-528h-pc64-c93x","revision":null,"records":[{"revision":"0ad85516b79e4fc3995cfcb5c11550d4","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-71429","aliases":[],"title":"stream-json: pick/ignore/filter/replace filters are O(depth²) on nested input — small crafted JSON blocks the event loop for seconds→minutes (DoS)","description":"stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.5.0, the path filters pick, ignore, filter, and replace in src/core/filters/filter-base.js recompute the full path string from the nesting stack for every checkable token. Because the stack length equals the current nesting depth and a checkable token is emitted at every level, a depth D document costs O(D²) rather than O(D) to process. The issue is triggered by nesting depth rather than byte volume, including the documented pick({filter: 'data'}) traversal-until-match path, so an application that sends untrusted JSON through a string or RegExp filter can block the Node.js event loop and cause denial of service with a small deeply nested document. The streamArray, streamObject, and streamValues streamers are not affected because they use the constant-time asm.depth getter. This issue is fixed in version 3.5.0.","state":0,"publishedAt":"2026-09-03T20:15:19.721+00:00","modifiedAt":"2026-09-04T17:44:46.226+00:00","affected":[{"ecosystem":"Vendor product","name":"stream-json","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"uhop\",\n                    \"product\": \"stream-json\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 3.5.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":6.2,"severity":"MEDIUM"}],"references":["https://github.com/uhop/stream-json/security/advisories/GHSA-528h-pc64-c93x","https://github.com/uhop/stream-json/commit/a869fb98aaef9225556f49901a8f55954ff856e6"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-71429","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-71429","identifiers":["CVE-2026-71429"]},"retained":false,"historicalAffected":[]},{"revision":"12f650f44d0a40738725484ea285fb40","observedAt":"2026-11-13T17:02:29.188+00:00","statement":{"source":"euvd","id":"EUVD-2026-70733","aliases":["CVE-2026-71429","GHSA-528h-pc64-c93x"],"title":"stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.5.0, the path filters pick, ignore, filter, and replace in src/core/filters/filter-base.js recompute the full path…","description":"stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.5.0, the path filters pick, ignore, filter, and replace in src/core/filters/filter-base.js recompute the full path string from the nesting stack for every checkable token. Because the stack length equals the current nesting depth and a checkable token is emitted at every level, a depth D document costs O(D²) rather than O(D) to process. The issue is triggered by nesting depth rather than byte volume, including the documented pick({filter: 'data'}) traversal-until-match path, so an application that sends untrusted JSON through a string or RegExp filter can block the Node.js event loop and cause denial of service with a small deeply nested document. The streamArray, streamObject, and streamValues streamers are not affected because they use the constant-time asm.depth getter. This issue is fixed in version 3.5.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":6.2,"severity":null}],"references":["https://github.com/uhop/stream-json/security/advisories/GHSA-528h-pc64-c93x","https://github.com/uhop/stream-json/commit/a869fb98aaef9225556f49901a8f55954ff856e6"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-70733","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Sep 3, 2026, 8:15:19 PM","dateUpdated":"Sep 4, 2026, 5:44:46 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-70733","identifiers":["EUVD-2026-70733","CVE-2026-71429","GHSA-528h-pc64-c93x"]},"retained":false,"historicalAffected":[]},{"revision":"cea628ab0ac145ee828f683c131f472a","observedAt":"2026-10-25T15:18:57.446+00:00","statement":{"source":"nvd","id":"CVE-2026-71429","aliases":[],"title":"stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.5.0, the path filters pick, ignore, filter, and replace in src/core/filters/filter-base.js recompute the full path…","description":"stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.5.0, the path filters pick, ignore, filter, and replace in src/core/filters/filter-base.js recompute the full path string from the nesting stack for every checkable token. Because the stack length equals the current nesting depth and a checkable token is emitted at every level, a depth D document costs O(D²) rather than O(D) to process. The issue is triggered by nesting depth rather than byte volume, including the documented pick({filter: 'data'}) traversal-until-match path, so an application that sends untrusted JSON through a string or RegExp filter can block the Node.js event loop and cause denial of service with a small deeply nested document. The streamArray, streamObject, and streamValues streamers are not affected because they use the constant-time asm.depth getter. This issue is fixed in version 3.5.0.","state":0,"publishedAt":"2026-09-03T21:17:21.66+00:00","modifiedAt":"2026-09-09T21:09:13.08+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":6.2,"severity":"MEDIUM"}],"references":["https://github.com/uhop/stream-json/commit/a869fb98aaef9225556f49901a8f55954ff856e6","https://github.com/uhop/stream-json/security/advisories/GHSA-528h-pc64-c93x"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-71429","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-407"],"productCriteria":[],"sourceDates":{"published":"2026-09-03T21:17:21.660","lastModified":"2026-09-09T21:09:13.080"},"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-71429","identifiers":["CVE-2026-71429"]},"retained":false,"historicalAffected":[]},{"revision":"bb76dc9f4d9e47b0bcc7aa99c256e9f6","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-528h-pc64-c93x","aliases":["CVE-2026-71429"],"title":"stream-json: pick/ignore/filter/replace filters are O(depth²) on nested input — small crafted JSON blocks the event loop for seconds→minutes (DoS)","description":"## Description\n\nThe path filters `pick`, `ignore`, `filter`, and `replace` — the library's headline \"surgical extraction\" feature — recompute the full path string from the nesting stack on **every checkable token**. Because the stack length equals the current nesting depth, and a checkable token is emitted at every level, processing a document of depth *D* costs **O(D²)**, not O(D).\n\nThis is triggered by document **structure (nesting depth), not byte volume**, so a tiny payload achieves outsized CPU cost, and it is the ordinary \"traverse until the filter matches\" path — including the exact README flagship example `pick({filter: 'data'})`. Any service that uses these filters to extract a field from an untrusted (or larger-than-memory) JSON body — the primary documented use case — can be made to block its event loop.\n\n### Affected code (v3.4.0)\n\n`src/core/filters/filter-base.js`:\n\n```js\n// L26-32 — string filter: rejoins the ENTIRE stack on every call\nconst stringFilter = (string, separator) => {\n  const stringWithSeparator = string + separator;\n  return stack => {\n    const path = stack.join(separator);              // O(depth) — every call\n    return path === string || path.startsWith(stringWithSeparator);\n  };\n};\n\n// L34-39 — regexp filter: same\nconst regExpFilter = (regExp, separator) => {\n  return stack => {\n    regExp.lastIndex = 0;\n    return regExp.test(stack.join(separator));       // O(depth) — every call\n  };\n};\n```\n\n```js\n// L194 — filter(stack, chunk) is invoked for EVERY checkable token while in the 'check' state\nconst action = checkableTokens[chunk.name] !== 1 ? nonCheckableAction : filter(stack, chunk) ? specialAction : defaultAction;\n```\n\n`stack` is pushed/popped on `startObject`/`startArray`/end (L239-250), so `stack.length === depth`. For a depth-*D* document that hasn't matched yet, `filter()` runs once per level and each call is O(depth) ⇒ **O(D²)** total.\n\n**Not affected:** the `streamArray`/`streamObject`/`streamValues` streamers use `asm.depth` (an O(1) getter), so they don't exhibit this. The issue is specific to `filter-base.js` recomputing the path string.\n\n## Proof of concept\n\n```\nnpm i stream-json@3.4.0\nnode poc-quadratic-dos.mjs\n```\n\n```js\nimport parserStream from 'stream-json';\nimport { pick } from 'stream-json/filters/pick.js';\nimport chain from 'stream-chain';\n\nfunction run(D) {\n  const doc = '{\"meta\":'.repeat(D) + '1' + '}'.repeat(D); // depth D, never matches \"data\"\n  return new Promise((resolve) => {\n    const t0 = process.hrtime.bigint();\n    const pipeline = chain([parserStream(), pick({ filter: 'data' })]);\n    pipeline.on('data', () => {});\n    pipeline.on('end', () => resolve({ D, bytes: doc.length, ms: Number(process.hrtime.bigint() - t0) / 1e6 }));\n    pipeline.write(doc); pipeline.end();\n  });\n}\nfor (const D of [5000, 10000, 20000, 40000]) {\n  const r = await run(D);\n  console.log(`D=${r.D}  bytes=${r.bytes}  ms=${Math.round(r.ms)}`);\n}\n```\n\nMeasured (Node v24, single core, clean `npm i stream-json@3.4.0`):\n\n```\nD=5000    bytes= 45001   ms=  160\nD=10000   bytes= 90001   ms=  603   (3.8x for 2x input  -> quadratic)\nD=20000   bytes=180001   ms= 2511   (4.2x)\nD=40000   bytes=360001   ms=11823   (4.7x)\n```\n\nA **~360 KB** body (pure nesting, no data) blocks the event loop for **~12 seconds**; extrapolating O(D²), ~1–2 MB reaches single-digit minutes of CPU on one request.\n\n## Impact\n\nRemote, unauthenticated denial of service against any application that runs untrusted JSON through `pick`/`ignore`/`filter`/`replace` with a string or RegExp filter — the documented primary use of the library. A small request pins a CPU core / blocks the Node event loop, degrading or halting the service.\n\n## Suggested fix\n\nMaintain the joined path incrementally instead of rejoining the whole stack per token:\n- On `startObject`/`startArray` push: append `separator + key` to a cached path string (and remember the pre-push length).\n- On end/pop: truncate the cached path back to the remembered length.\n- Filters test/`startsWith` against the cached string — O(1) amortized per token, making the whole traversal O(D).\n\nAlternatively expose/enforce a maximum nesting depth for the filter path check.\n\n## Resolution\n\nFixed in 3.5.0. The path filters now cap JSON nesting depth at 1024 by default and throw a RangeError beyond it; upgrading is enough. Opt out with `maxDepth: Infinity`.","state":0,"publishedAt":"2026-09-03T20:27:53+00:00","modifiedAt":"2026-09-10T03:51:15.1011987+00:00","affected":[{"ecosystem":"npm","name":"stream-json","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.5.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:L/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":"MODERATE"}],"references":["https://github.com/uhop/stream-json/security/advisories/GHSA-528h-pc64-c93x","https://github.com/uhop/stream-json/commit/a869fb98aaef9225556f49901a8f55954ff856e6","https://github.com/uhop/stream-json"],"sourceUrl":"https://github.com/advisories/GHSA-528h-pc64-c93x","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-528h-pc64-c93x","identifiers":["GHSA-528h-pc64-c93x","CVE-2026-71429"]},"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-11-15T06:30:52.8313382+00:00","complete":true,"records":1781,"lastAttemptAt":"2026-11-15T06:30:52.8313382+00:00","lastSuccessAt":"2026-11-15T06:30:52.9739799+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.9739809+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-7de4d7559a5263003f03d9f70577ab06f92ba4178309f26fce53e22a4a959294-5e215490d55c14930215665213ddac0f30153138ec392bed5f309d985bc5e0a1","complete":true,"records":425770,"lastAttemptAt":"2026-11-15T06:30:52.1466825+00:00","lastSuccessAt":"2026-11-15T06:30:52.1389052+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.1466825+00:00","snapshotPublishedAt":"2026-11-15T06:27:22+00:00","importing":false,"stagedRecords":0,"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-5a4abd14253544b78d8c3044915082da","complete":false,"records":13821,"lastAttemptAt":"2026-11-15T06:39:25.4859854+00:00","lastSuccessAt":"2026-11-15T06:39:24.47+00:00","error":"64 identifier lookups failed; latest failures include AccessDenied (HTTP 403). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-15T06:39:24.47+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13810,"expectedIdentifiers":13874,"failedIdentifiers":64},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-15T06:30:52.8313382+00:00","complete":true,"records":1125,"lastAttemptAt":"2026-11-15T06:30:52.8313382+00:00","lastSuccessAt":"2026-11-15T06:30:52.9749685+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.9749715+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-35abf047bdfe46089a52c4c139c37942","complete":false,"records":13833,"lastAttemptAt":"2026-11-15T06:30:58.1452571+00:00","lastSuccessAt":"2026-11-15T05:33:23.837+00:00","error":"12 identifier lookups failed; latest failures include HttpError (HTTP 503). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-15T05:33:23.837+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13862,"expectedIdentifiers":13874,"failedIdentifiers":12},{"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-64eea06dbd27de8464d5dbce1b78cc794cc59e2d901516ae0d3ea76e624cdae0","complete":true,"records":9955,"lastAttemptAt":"2026-11-15T06:30:52.7627278+00:00","lastSuccessAt":"2026-11-14T13:57:23.1208135+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.7627278+00:00","snapshotPublishedAt":"2026-11-14T13:17:57+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-f4424d7d4861b1af1404bd15abcb83f295dcd003412a9340ca99dc8200aa148a","complete":true,"records":2014,"lastAttemptAt":"2026-11-15T06:30:52.191726+00:00","lastSuccessAt":"2026-11-13T19:57:08.2864435+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.191726+00:00","snapshotPublishedAt":"2026-11-13T19:33:10+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-012aff9b2fb349d53e2c4bace6ca57956b6f5f9d82c1dc9fdc1c9b3c738f8977","complete":true,"records":3148,"lastAttemptAt":"2026-11-15T06:30:52.7798435+00:00","lastSuccessAt":"2026-11-14T04:57:03.8279646+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.7798435+00:00","snapshotPublishedAt":"2026-11-14T03:02:50+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-878f718ccd076f540460423958c8e379b139b997b45858275573ecc4d702fe9a","complete":true,"records":232474,"lastAttemptAt":"2026-11-15T06:30:52.6600161+00:00","lastSuccessAt":"2026-11-15T05:33:06.8241102+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.6600161+00:00","snapshotPublishedAt":"2026-11-15T04:33:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}