Skip to content

Feat/node 26 support - #2699

Open
alleria173 wants to merge 3 commits into
gchq:masterfrom
alleria173:feat/node-26-support
Open

Feat/node 26 support#2699
alleria173 wants to merge 3 commits into
gchq:masterfrom
alleria173:feat/node-26-support

Conversation

@alleria173

Copy link
Copy Markdown
Contributor

Add Node 26 support alongside Node 24

Closes #2397

Summary

Adds Node.js 26 compatibility ahead of it becoming the active LTS release in November 2026, per the phased approach suggested in #2397: Node 24 remains the officially supported version, with Node 26 added to the CI test matrix. A follow-up change can make Node 26 official once it reaches LTS.

Branch: feat/node-26-support (commit a182fe74, based on upstream master @ c56dd233)

Diagnostics performed

The full toolchain was run under Node 26.5.0 (and re-verified under 24.18.0 as a baseline):

Check Node 24 Node 26 (before fix) Node 26 (after fix)
npm ci (incl. postinstall grunt tasks)
npm test (2280 operation + 272 Node API tests) ✅ all pass ❌ 4 failures + 1 error ✅ all pass
npm run lint
npm run build (webpack prod)
npm run node + grunt testnodeconsumer (CJS + ESM)
npm run testui not run locally (needs Chrome/xvfb) — covered by CI on both matrix legs

Static analysis of the dependency tree found:

  • No native addons anywhere in the tree (zero binding.gyp), so no NODE_MODULE_VERSION/ABI concerns. Heavy dependencies are WASM-based (argon2-browser, libyara-wasm, libbzip2-wasm, jq-web, tesseract.js).
  • No dependency caps Node below 26 in its engines field.
  • No usage of Node 26 semver-major removals in CyberChef source: _stream_* core modules (only present inside self-contained bundled copies of readable-stream), http.Server.writeHeader(), module.register(). The Temporal global being enabled by default causes no conflicts.
  • The --openssl-legacy-provider flag used by npm test still works on Node 26 (OpenSSL 3.5.x retains the legacy provider). Note: this is a future risk when Node moves to OpenSSL 4.

The one incompatibility found: cbor

All five test failures were in the CBOR Encode operation. Outputs were truncated to the first CBOR byte, e.g. encoding "Text" produced 64 instead of 64 54 65 78 74.

Root cause: Node 26 includes the semver-major change "stream: readable read one buffer at a time" (nodejs/node#60441). cbor@10's synchronous encoder (Encoder._encodeAll) pipes the encoder stream into a NoFilter and performs a single bs.read(), assuming it returns the entire concatenated encoding. On Node 26 that read returns only the first buffered chunk.

Alternatives evaluated:

  1. Upgrade nofilternofilter@6 has an incompatible API (NoFilter is not a constructor from cbor's CJS require); cbor@10 pins nofilter@^3.
  2. Switch to Cbor.encodeAsync() — works (collects 'data' events instead of a sync read), but keeps an effectively unmaintained dependency with a latent stream bug.
  3. Replace cbor with cbor2 — same author's actively maintained successor (cbor2@2.3.0, engines >=20), pure data API with no Node stream dependency, works in browsers and Node identically.

Output equivalence verified byte-for-byte against the old package, including RFC 8949 canonical form:

  • encode(input, {sortKeys: sortCoreDeterministic}) reproduces encodeCanonical's length-first key ordering (b sorts before aa) — the default cbor2 encode does not sort keys, so the explicit sort option is required.
  • Primitives, half-precision floats (1.5f9 3e 00), maps, lists, integers, booleans, and null all match.
  • Decode path verified against the same vectors.

Changes

  • package.json / package-lock.json
    • engines: ">=24 <25"">=24 <27"
    • Dependency swap: cbor@10.0.12cbor2@2.3.0 (pinned exact, matching the previous pin convention)
  • src/core/operations/CBOREncode.mjs — use cbor2's encode with sortCoreDeterministic key sorting
  • src/core/operations/CBORDecode.mjs — use cbor2's decode on the raw Uint8Array (drops the old hex-string round-trip)
  • .github/workflows/master.yml, pull_requests.ymlnode-version becomes a matrix [24, 26] with fail-fast: false; artefact upload and GitHub Pages deploy are gated to the Node 24 leg so they run exactly once. releases.yml intentionally unchanged (npm publish stays on the officially supported Node 24 until Phase B).
  • .github/dependabot.yml — removed the now-stale cbor >=10 ignore entry
  • README.md / AGENTS.md — Node.js support notes updated

Testing

  • Regression coverage: the existing CBOR operation tests fail on Node 26 before the fix and pass after it (they also continue to pass on Node 24).
  • Full non-UI suite green on both Node 24.18.0 and 26.5.0.
  • Production build and Node consumer (CJS/ESM) tests green on Node 26.
  • UI tests will run on both matrix legs in CI.

Follow-up (Phase B — when Node 26 reaches LTS, ~Nov 2026)

  • engines">=26 <27"; CI node-version: 26 everywhere including releases.yml
  • Dockerfile → node:26-alpine@sha256:... — note dependabot was told to ignore this major version for the Docker node image when PR Bump node from 24-alpine to 26-alpine #2381 was closed, so this must be done manually
  • .devcontainer/devcontainer.jsonjavascript-node:26-*
  • README/AGENTS.md, browserslist node >= 26, CHANGELOG entry, version bump via npm run minor

AI disclosure
Compatibility and testing done with Github Copilot with Claude Fable.

- Widen engines range to >=24 <27
- Test against Node 24 and 26 in CI (build/deploy artefacts remain on 24)
- Replace unmaintained 'cbor' dependency with 'cbor2': the cbor package's
  synchronous encode relies on a single stream read() returning the whole
  encoding, which breaks on Node 26 where readable streams return one
  buffer at a time
- Remove stale dependabot ignore for the removed cbor package
- Update Node.js support notes in README and AGENTS
@alleria173
alleria173 marked this pull request as ready for review July 28, 2026 14:00
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.

Feature request: Update to Node 26

2 participants