Skip to content

Move to stream-json 3 for CVE-2026-71429 - #554

Closed
alexandrucojocaru-creatopy wants to merge 1 commit into
Opteo:masterfrom
alexandrucojocaru-creatopy:stream-json-3
Closed

alexandrucojocaru-creatopy wants to merge 1 commit into
Opteo:masterfrom
alexandrucojocaru-creatopy:stream-json-3

Conversation

@alexandrucojocaru-creatopy

Copy link
Copy Markdown

Why

stream-json <= 3.4.0 has a quadratic-time denial of service in its path filters: pick, ignore, filter and replace (GHSA-528h-pc64-c93x, CVE-2026-71429). The fix is only in 3.5.0 and later; there is no 1.x or 2.x backport.

This library uses only the parser and streamArray, which the advisory lists as not affected. It still pins stream-json ^1.8.0, though, so every project that depends on google-ads-api gets a vulnerable version installed and flagged by Dependabot. Downstream projects can't fix this with an override: stream-json 3 is ESM-only and renamed the modules this library requires, so an override breaks google-ads-api as soon as it loads.

Changes

  • src/customer.ts: the two stream-json pipelines, in streamer and handleStreamError, move to the 3.x API.
    • new Parser(options) → parser.asStream(options)
    • streamArray() → streamArray.asStream(), from stream-json/streamers/stream-array.js
    • Parser options are unchanged.
  • package.json:
    • stream-json ^1.8.0 → ^3.7.0.
    • stream-chain ^4.2.6 is now declared. customer.ts imports it directly but only received it through stream-json 1.x.
    • @types/stream-json is removed, because 3.x ships its own types.
    • engines.node goes from >=22 to >=22.12. The CommonJS build loads these ESM-only packages with require(), which Node enables without a flag from 22.12. On 22.0–22.11 it fails with ERR_REQUIRE_ESM.
  • src/stream-json.d.ts: stream-json 3 publishes its streamers only through its exports map ("./*": "./src/*"). Neither tsconfig resolves types through exports: the CJS build uses node10 resolution, and the ESM build sets resolvePackageJsonExports: false. This declaration points the one subpath's types at its file, and Node resolves the runtime import through the map. Nothing reaches the published .d.ts, because the stream types aren't part of the public API.
  • jest.config.js: compiles stream-json and stream-chain to CommonJS for tests only, since Jest 29's CommonJS runtime can't load ES modules. Neither package uses import.meta or top-level await.
  • src/customer.spec.ts: three new tests for handleStreamError, the path every failed query() takes:
    • a GoogleAdsFailure parsed from the error details;
    • the API message when there are no details;
    • the parser's error for a non-JSON body.

Verification

  • Existing behaviour is unchanged.
    • The three new tests pass both on current master (stream-json 1.9.1) and on this branch (3.7.0).
    • Negative controls:
      • removing streamArray from the report pipeline fails the reportStream tests;
      • removing it from the error pipeline fails the two error tests that depend on it.
  • Wider equivalence check. Outside this repo, I ran the old and new pipelines side by side on 2,800 generated searchStream bodies across six seeds. They included result batches, summary rows, error bodies, and truncated or malformed JSON, split at random byte boundaries, including inside multi-byte characters. The yielded values, the first error value, and the failure behaviour were identical in every case. A deliberately wrong parser option in the same harness matched only 15 of 100 cases.
  • Local run of the CI steps, on Node 22.21.1 and 24.11.1:
    • yarn lint passes;
    • yarn test passes (200 tests: the 197 existing plus the 3 new);
    • yarn test:package passes.
  • Types: @arethetypeswrong/cli --profile node16 --pack . passes.
  • Builds: the CJS and ESM build/*/customer.js both load and import the new modules.

tsc -p tsconfig.json still reports one error, TS2769 in scripts/services.ts, which this change doesn't touch. The published builds use tsconfig.cjs.json and tsconfig.esm.json, and both compile cleanly.

🤖 Generated with Claude Code

stream-json <= 3.4.0 has a quadratic-time denial of service in its path
filters (GHSA-528h-pc64-c93x). This library only uses the parser and
streamArray, which are not affected, but every install still pulls in a
vulnerable stream-json 1.x and gets flagged for it.

stream-json 3 is ESM-only and changed its API:
- `new Parser(options)` becomes `parser.asStream(options)`, and
  `streamArray()` becomes `streamArray.asStream()`, imported from
  `stream-json/streamers/stream-array.js`.
- stream-chain, which customer.ts imports directly, is now declared
  (^4.2.6) instead of arriving through stream-json.
- The CJS build loads both packages with require(esm), which Node enables
  without a flag from 22.12, so engines moves from >=22 to >=22.12.
- Neither tsconfig resolves types through package `exports`, so
  src/stream-json.d.ts points the streamer subpath at its file.
- Jest compiles the two ESM packages to CommonJS for tests.

New tests pin the error path every failed query() takes
(handleStreamError): a GoogleAdsFailure with details, an API message
without details, and a non-JSON body. They pass unchanged on stream-json
1.9.1 and 3.7.0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@alexandrucojocaru-creatopy

Copy link
Copy Markdown
Author

@Lewis-Clayton @avermeil could one of you approve the workflow run and review this when you have a moment? It moves the library to stream-json 3, so installs stop pulling in a stream-json flagged by CVE-2026-71429. The library itself only uses the unaffected parser and streamArray. The Node 22/24 tests, test:package and arethetypeswrong pass locally; details are in the description. Happy to adjust anything, such as the engines bump.

@Lewis-Clayton

Copy link
Copy Markdown
Contributor

Thanks @alexandrucojocaru-creatopy, always appreciate a PR. We can't support change at this time. The library only uses the parser and streamArray, which the advisory lists as unaffected, and moving to ESM-only stream-json 3 breaks CommonJS consumers on Node < 22.12 and any downstream Jest suite that loads the package. We'll revisit this when Google release the next major version of the API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants