Skip to content

[EXPIN-51] chore(security): pin dev/CI Node to 22.23.4 (CVE-2026-111497) - #353

Open
Micah Heineck (micahheineckoptimizely) wants to merge 1 commit into
masterfrom
security/cve-2026-111497-node-22.23.4
Open

Micah Heineck (micahheineckoptimizely) wants to merge 1 commit into
masterfrom
security/cve-2026-111497-node-22.23.4

Conversation

@micahheineckoptimizely

@micahheineckoptimizely Micah Heineck (micahheineckoptimizely) commented Oct 22, 2026 •

Copy link
Copy Markdown

Ticket: EXPIN-51

Summary

Pins the Node.js version used for local development, CI and npm/GHR publishing to 22.23.4, the patched release for CVE-2026-111497 (out-of-bounds write in Node's bundled Brotli decoder, possible RCE). Node 16/18/20 are end-of-life and get no fix, so the .nvmrc, devcontainer and GHR backfill job (all on 18) move up a major. The release job (already on 22) gets a patch bump to an exact pin.

This is a customer-facing library. The change covers only the Node we build, test and publish with. It does not change the published support policy (engines) or what customers run.

Files changed (old → new)

File Old New
.nvmrc v18 v22.23.4
.devcontainer/devcontainer.json image javascript-node:1-18-bullseye image javascript-node:1-22-bullseye + ghcr.io/devcontainers/features/node:1 with version: 22.23.4
.github/workflows/react_release.yml (publish to npm + GHR) node-version: 22 node-version: 22.23.4
.github/workflows/ghr_backfill.yml (publish npm tarballs to GHR) node-version: 18 node-version: 22.23.4
.github/workflows/react.yml (unit test matrix) '22' leg '22.23.4' leg ('18', '20', '24' unchanged)

No package.json, package-lock.json or source changes.

Why each one:

  • devcontainer: the MCR image tags only pin the Node major. Today 1-22-bullseye ships Node 22.16.0, which is still vulnerable. The node feature installs exactly 22.23.4 through nvm and puts it first on PATH. I kept the base OS (bullseye) to keep the change small.
  • ghr_backfill.yml: a manually triggered publish job. It runs npm view and npm pack against npmjs, then scripts/publish.sh to npm publish the unmodified npm tarballs to GHR (--ignore-scripts, no rebuild). It only uses the npm CLI, curl, jq and tar, with no project build. Node 22 is fine for it, and the bump also matches it to the release job.

Upgrade analysis

Local dev and CI move from 18 to 22.23.4. The release job was already on 22, so for publishing this is only a patch bump.

  • Build toolchain (rollup 4.55.1, @rollup/plugin-node-resolve 16, @rollup/plugin-terser, TypeScript 5.9.3): no breaking changes on 22. rollup.config.mjs uses import … with { type: 'json' }, which Node 22 supports. The repo has no webpack or babel. TS is compiled by tsc, and the tests are transformed by vitest/esbuild.
  • Tests (vitest 4.0.18 / vite 7.3.1 / happy-dom 20.4.0; jest is not used): these packages declare engines of ^20.0.0 || ^22.0.0 || >=24.0.0, ^20.19.0 || >=22.12.0 and >=20.0.0. They already exclude Node 18. npm ci on 18 prints EBADENGINE warnings for all three. So the old .nvmrc v18 dev setup was outside the test toolchain's declared support, and moving to 22 fixes that.
  • ESLint 8.57.1 / typescript-eslint (@typescript-eslint) 8: works on 22. tsc --noEmit plus eslint report 0 errors and the same 9 warnings as before.
  • @types/node: not a direct dependency. 25.0.3 comes in transitively and tsconfig doesn't depend on it, so there is no change.
  • OpenSSL 3 hashing: not applicable. The repo has no webpack 4 and nothing uses md4/legacy hashes. The build doesn't need --openssl-legacy-provider.
  • Native deps: none compile on install. The only packages with install scripts or platform restrictions are prebuilt optional binaries (esbuild, @esbuild/*, @rollup/rollup-*, fsevents). They are chosen by OS/CPU, not by Node ABI.
  • engines excluding 22: none. I checked all 474 lockfile entries against 22.23.4.
  • Lockfile: npm ci under 22.23.4 (npm 10.9.9) installs from the existing lockfile (v3) with no changes. No lockfile churn is committed.
  • Published bundle: unchanged. dist/ built on Node 18.20.8 and on Node 22.23.4 is byte-identical: react-sdk.es.min.mjs, server.es.min.mjs, the sourcemaps and the .d.ts files have the same SHA-1. The npm pack file lists are identical too. The bundle is still ESM targeting browsers/React, so runtime support doesn't change.

Verification

Run locally on macOS from a fresh clone of master with this branch applied:

# Node 22.23.4 (npm 10.9.9)
npm ci --registry=https://registry.npmjs.org/   # OK, 425 packages, no EBADENGINE warnings
npm test                                        # 14 files, 267/267 tests passed
npm run build                                   # OK (tsc + rollup)
npm run lint                                    # 0 errors, 9 pre-existing warnings
npm pack --ignore-scripts                       # OK

# Node 18.20.8 comparison
npm ci ...    # OK, with EBADENGINE warnings for vitest/vite/happy-dom
npm test      # 267/267 passed
npm run build # dist/ byte-identical to the 22.23.4 build (diff -r clean)

# ghr_backfill read-only path on 22.23.4
npm view @optimizely/react-sdk versions --json   # OK (58 versions)
npm pack @optimizely/react-sdk@4.2.1             # OK, tarball package.json readable via tar/jq
# scripts/publish.sh was not run locally (it publishes)

# devcontainer (devcontainers CLI, postCreate skipped)
devcontainer up && devcontainer exec node -v     # v22.23.4 (/usr/local/share/nvm/current/bin/node)

# Other
nvm use        # resolves .nvmrc -> v22.23.4
# 22.23.4 is in actions/node-versions manifest (setup-node can install it)
# all workflow YAML and devcontainer.json parse

Compatibility matrix and engines (owners' call)

  • In react.yml, the ${{ matrix.node }} matrix tests customer compatibility. Only the 22 leg is pinned, to 22.23.4. The 18 and 20 legs are unchanged. They are EOL with no fix for this CVE, but they still test the published engines: >=18.0.0 range. Keeping or dropping them is the owners' call. Note that the test toolchain (vitest 4/vite 7/happy-dom 20) already declares it doesn't support Node 18. The 18 leg passes today only because npm treats EBADENGINE as a warning. The 24 leg is unchanged and floats to the latest 24.x (24.22.1+ includes the fix).
  • The matrix job name for that leg changes from Run Unit Tests (Node 22) to Run Unit Tests (Node 22.23.4). It is not a required status check (the master ruleset requires only Arnica), so merging isn't affected.
  • package.json engines (>=18.0.0) is intentionally unchanged, because it is the published support policy.

🤖 Generated with Claude Code

Pin the Node used for development, CI and publishing to the patched
22.23.4 release (CVE-2026-111497, Brotli decoder OOB write).

Files:
- .nvmrc: v18 -> v22.23.4
- .devcontainer/devcontainer.json: javascript-node:1-18-bullseye ->
  javascript-node:1-22-bullseye + node feature pinned to 22.23.4
- .github/workflows/react_release.yml: node-version 22 -> 22.23.4
- .github/workflows/ghr_backfill.yml: node-version 18 -> 22.23.4
- .github/workflows/react.yml: matrix leg '22' -> '22.23.4'
  (18/20/24 compatibility legs unchanged)

package.json engines (>=18.0.0) intentionally unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@micahheineckoptimizely Micah Heineck (micahheineckoptimizely) changed the title chore(security): pin dev/CI Node to 22.23.4 (CVE-2026-111497) [EXPIN-51] chore(security): pin dev/CI Node to 22.23.4 (CVE-2026-111497) Oct 22, 2026

This branch has not been deployed

No deployments
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.

1 participant