Repository navigation
fix(security): Node 22.23.4 base pinned by digest; protobufjs 6.11.6 - #5
Conversation
Move both Node stages of docker/hyperdx/Dockerfile (node_base, prod) from the floating node:22.22-alpine tag (resolved to 22.22.3) to node:22.23.4-alpine3.24, pinned to the multi-arch index digest sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914. Node 22.23.4 fixes CVE-2026-111497 (Brotli decoder out-of-bounds write, CVSS 9.2, CISA KEV 2026-10-27, actively exploited) and CVE-2026-105481, -105482, -106069, -106072, -106073, -106074 (HTTP agent reuse, DNS NUL truncation, TLS hostname verification, crypto caches). The new base also moves Alpine 3.24.1 -> 3.24.3 (libssl3/libcrypto3 3.5.7 -> 3.5.9, musl, zlib), clearing 23 OS-package findings including HIGH CVE-2026-14456. Resolve @hyperdx/otel-web-session-recorder's protobufjs ~6.11.2 to ~6.11.6 (6.11.4 -> 6.11.6) for CVE-2026-41242 / GHSA-xq3m-2v4x-88gg. The recorder only imports protobufjs/minimal, so the browser bundle is byte-identical; this clears the lockfile finding. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Review (max effort): approve, merging. No HIGH or MED findings. I re-verified everything independently on b9e70a09, from a pristine git archive export, and did not reuse the earlier local evidence.
1. Base image digest. sha256:fe4f6d74…9914 is the official node:22.23.4-alpine3.24 OCI index. I checked it four ways:
crane digestresolves22.23.4-alpine3.24to it. It is also the current22.23.4-alpine,22.23-alpine3.24and22-alpine3.24.- Registry API: the
Docker-Content-Digestheader matches, and so does the SHA-256 recomputed over the raw index bytes. - Docker Hub API: the
digestfield matches (updated 2026-10-22). - docker-library
repo-infoandofficial-imageslibrary/node: the tag maps to GitCommit80f02a54,22/alpine3.24, for the same 5 platforms.
The amd64 SLSA provenance names nodejs/docker-node@80f02a54:22/alpine3.24, built by the docker-library builder. That Dockerfile installs Node from the GPG-verified nodejs.org/dist linux-x64-musl tarball.
Both Node stages (node_base, prod) use the pin. Nothing else in the Dockerfile changed, and no script passes NODE_VERSION as a build arg.
2. Node 22.22.3 → 22.23.4. This crosses five releases, not one: 22.23.0 (security, semver-minor), 22.23.1, 22.23.2 (security), 22.23.3, and 22.23.4 (security). The floating 22.22 tag stopped at 22.22.3, so the deployed image is also missing the 22.23.0 (11 CVEs) and 22.23.2 (10 CVEs) fixes, and this PR closes those too.
The 22.23.4 CVE list in the description matches the October advisory for 22.x exactly (7 CVEs, including CVE-2026-106072).
Reviewed for impact on HyperDX:
- TLS client changes. These are hostname normalization, non-canonical-IP rejection, session binding to the authenticated host, X.509/SAN hardening, the SPKI blocklist (8 eMudhra intermediates), and Node's bundled roots going from 145 to 116.
- The roots dropped by NSS 3.123.1 / 3.125 / 3.131.1 include DigiCert Global Root CA, Assured ID, High Assurance EV, COMODO, QuoVadis 2/3, GTS R2, Entrust and emSign.
- Against our deployment manifests (private), none of these are on the deployed request paths.
- If an HTTPS egress (alert webhooks, a TLS ClickHouse) is opened later, check that endpoint's chain against Node's bundled roots, or set
NODE_EXTRA_CA_CERTS.
- http.Agent. 22.23.0 added an idle-socket data guard, 22.23.1 fixed its regression, and 22.23.4 adds a pending-read check before reuse. These only protect, and the smoke run exercises them on the Next → API proxy hop.
- llhttp 9.3.1 → 9.4.3. Stricter only: CRLF is required in the status line, and an empty
Transfer-Encodingis rejected. - Smaller tightenings. More than 2000 request headers now returns 431. zlib throws on out-of-bounds buffers instead of aborting. DNS rejects NUL bytes.
- undici 6.24.1 → 6.29.1. These are security fixes. The API's
fetch()connection test was exercised end to end. - http2 priority-signaling removal (SEMVER-MAJOR, landed in 22.23.0). It now warns and is a no-op, and HyperDX's production paths don't use http2.
- V8 12.4.254.21, libuv 1.51.0 and ABI 127 are unchanged.
3. protobufjs. The resolution is scoped correctly.
@hyperdx/otel-web-session-recorder@0.16.2is the only dependent on~6.11.2. The three 7.x descriptors are untouched.- The 6.11.4 → 6.11.6 tarball diff changes only
src/type.js(one line), plus the dist banners. - The recorder's
dist/imports onlyprotobufjs/minimal, whose source closure is unchanged. - In the built image, all 76
.next/staticchunk files are byte-identical to the deployed image, and so is all ofnode_modules. 6.11.x is not shipped.
4. The #4 patch is intact. All patched API files, both workflows and gascity/smoke are blob-identical to 753b16de.
Re-run here:
- Pristine tree in
node:22.23.4-alpine3.24@sha256:fe4f6d74…(thegascity-prsteps):yarn install --immutable.- API lint: 0 errors (301 warnings, within the budget of 302).
tsc --noEmit: clean.- jest: 4 suites, 82/82.
- API build plus the artifact symbol guard: 10/10.
- Fresh prod image (
--no-cache --pull --target prod, CI build contexts):node --versionv22.23.4, bundled OpenSSL 3.5.9, Alpine 3.24.3 (libssl3 3.5.9).zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH= -17 (undefined on the deployed image), and a Brotli round-trip works.- The
gascity-imagesymbol guard passes 10/10.
- Smoke harness on the fresh image: 107/107, 0 failures.
authin three postures, 31/31 each: default, production (SMOKE_TELEMETRY=off), and trace withOTEL_LOG_LEVEL=debug.telemetry4/4,failclosed3/3,rotation7/7, and the seeded state read back across containers.- In every container:
/ready200, 0 restarts, 0 deprecation warnings, 0 TLS errors, and 0 secret hits in the full logs.
- A/B: the same driver against the currently deployed image produces identical outcome lines (107/107).
Non-blocking (LOW):
- (a) The description doesn't mention the 22.23.0 and 22.23.2 CVEs it also fixes.
- (b) An ARG-based
FROM ...@${NODE_DIGEST}isn't visible to Dependabot version updates, so the next Node security release needs a manual bump. The old floating22.22tag went stale the same way. - (c) A
parent/depresolution applies to every recorder version. Remove it once the recorder moves off protobufjs 6.x; aresolutionsNotesentry would record that. - (d) Pre-existing upstream behavior, identical on the deployed image:
pages/api/[...all].tsbuilds an http-proxy-middleware instance per request, and each one adds acloselistener to the Next server (MaxListenersExceededWarning). This is tracked separately.
Merging with a merge commit (the gascity-sso convention). The required auth quality gate is green on b9e70a09.
CVEs fixed
@hyperdx/otel-web-session-recorder): CVE-2026-41242 / GHSA-xq3m-2v4x-88gg. The recorder uses onlyprotobufjs/minimal, so the browser bundle is byte-identical. This clears the lockfile finding.Change. Both Node stages now use
node:22.23.4-alpine3.24@sha256:fe4f6d74…, the multi-arch index digest. A yarn resolution scoped to the recorder; the lockfile diff is 4 lines.Verified on this tree:
yarn install --immutable; api lint 0 errors;tscclean; jest fork suites 82/82node --version= v22.23.4;zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCHpresent (the CVE-2026-111497 fix marker)Rollout.
gascity-imagepublishes2.42.0-gascity-<sha12>.flux/apps/hyperdx/hyperdx.yaml./api/healthversion and run the §6.3 count-only checks.Rollback. Repin
sha256:b2080937…(2.42.0-gascity-753b16de700f, same app; no probe change).🤖 Generated with Claude Code
Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.