Repository navigation
fix(security): rebuild HyperDX on Node 22.23.5 (digest-pinned) - #7
Conversation
Node security release 2026-11-11 (22.23.5): CVE-2026-123864, -123866, -123863, -123865, -123872, -123874, -123828, -123867, -123868, -123871, -123875, -123877, -123829, -123862, -123873; llhttp 9.4.4, sqlite 3.54.1. Both Node stages move to node:22.23.5-alpine3.24 pinned to the multi-arch OCI index sha256:1314fb30ec37592d5845e5a160d522c0e680972aa534549940b8d73bde88481a (same index as node:22.23.5-alpine). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Independent max-effort review. Verdict: CLEAN, with no HIGH or MED findings. Merging with a merge commit.
Scope.
- The only file changed is
docker/hyperdx/Dockerfile(+6/−6):NODE_VERSION=22.23.5,NODE_DIGEST=sha256:1314fb30…481a, and the comment. - Both Node stages pick the pin up through the existing
FROM node:${NODE_VERSION}-alpine3.24@${NODE_DIGEST}:node_baseat L50 andprodat L112.common-utils-builderandbuilderderive fromnode_base. - Nothing overrides either ARG.
gascity-image.ymlpasses onlyCODE_VERSION. - Head
f4b257aesits directly on the currentgascity-ssotip,59a28cbd.
Base digest sha256:1314fb30ec37592d5845e5a160d522c0e680972aa534549940b8d73bde88481a
- Tags:
crane digestreturns it fornode:22.23.5-alpine3.24,22.23.5-alpine,22.23-alpine3.24and22-alpine3.24. - Independent sources agree: the registry
Docker-Content-Digestmatches, the SHA-256 recomputed over the raw index bytes matches, and so do the Docker Hub tag API and docker-libraryrepo-info. - Index contents: an OCI image index covering linux/amd64, arm/v6, arm/v7, arm64/v8 and s390x, plus attestation manifests.
- amd64 config:
NODE_VERSION=22.23.5, created 2026-11-11. - Provenance:
- SLSA provenance says docker-library built it from
nodejs/docker-node@1274f3ee:22/alpine3.24. - official-images
library/nodemaps the tag to the same GitCommit and Directory. - That Dockerfile installs the GPG-verified nodejs.org v22.23.5 tarball.
- SLSA provenance says docker-library built it from
Advisories.
- The description's 15 CVEs (6 High, 6 Medium, 3 Low) match the official v22.23.5 changelog's notable changes exactly.
- The nodejs.org dist index marks 22.23.5 as a security release. OpenSSL (3.5.9) and the module ABI (127) are unchanged, so native modules are unaffected.
- The CVE-2026-123877 change checks out in source:
lib/https.jsat v22.23.5 encodes TLS option keys as<type><len>:<value>, soca:'X'becomess1:X.
Build. I ran an independent docker buildx build --no-cache --pull --target prod from a pristine git archive of f4b257ae, with the CI build contexts, in a fresh isolated builder that shares no cache. It succeeded:
yarn install, the API build and the Next build (26/26 static pages) are all OK.- The image's first 4 layers are byte-identical (by diff ID) to the pinned amd64 base.
- Its config has
NODE_VERSION=22.23.5, and it has 16 layers, like the earlier published images.
CI.
- The required
auth quality gate(Actions, app 15368) is green onf4b257ae: immutable install, lint 0 errors (301 warnings),tsc, jest 4 suites 82/82, API build, and the production-symbol guard 10/10. - CodeQL is green.
Runtime evidence is from the author's local run. I re-analyzed the raw artifacts rather than taking the summary.
node --versionis v22.23.5, with llhttp 9.4.4 and sqlite 3.54.1.- The image symbol guard passes 10/10.
- Smoke is 107/107. Its PASS/FAIL/SUMMARY lines are identical to the d0bf80e5 baseline run, which was also 107/107.
- The Trivy finding sets are identical (224 rows), and so are the osv sets (68 rows).
- Alpine 3.24.3 → 3.24.4 changes only
alpine-release,apk-toolsandlibapk(3.0.8 → 3.0.9).
Merging only builds and publishes.
- On a push to
gascity-sso, onlygascity-image.ymlruns. Its steps are verify, build, the symbol guard, and push:2.42.0-gascity-<sha12>and:gascity-sso. It has no deploy step. - The repo has no webhooks and no environments.
- The only consumer of
ghcr.io/gascity/hyperdxpins it by digest and has no image automation, so nothing deploys until the separate repin. - This PR is not under the release freeze: it is not identity-ha, a box-shared image, or the S1-min tree.
Findings (none blocking)
- INFO: CI runtime drift.
gascity-pr.ymlL36–38 andgascity-image.ymlL44–46 usesetup-nodenode-version: 22withoutcheck-latest. As a result, this PR's required gate ran on the runner's cached v22.23.4, and the image's runtime is never exercised before merge.- The author's in-container verify on 22.23.5 and the independent build cover that gap here.
- Optional follow-up: pin the verify jobs to the Dockerfile's
NODE_VERSION.
- LOW (process, carried over from #5): bots can't see the pin. The ARG-based pin (Dockerfile L14–15, L50, L112) is invisible to Dependabot and Renovate, so each Node security release remains a manual bump.
- INFO: the smoke A/B counter differences are timing, not a regression.
- Posture B shows "CH proxy captured 6" (baseline 1) and 117 stdout lines (baseline 84). Both come from one 5-query evaluation (
hdx-alert-*query ids) of the alert seeded in posture A, which landed inside B's capture window. - Posture C differs only in trace volume and the OTLP batch count.
- T13 still passed against every captured request.
- The harness could ignore
hdx-alert-*captures to make these counters deterministic.
- Posture B shows "CH proxy captured 6" (baseline 1) and 117 stdout lines (baseline 84). Both come from one 5-query evaluation (
- INFO: the 22.23.5 changes don't touch HyperDX's runtime paths.
- llhttp 9.4.4 (a private release) only tightens header-value validation; the fast path used to admit 0x0E–0x1F.
- The TLS, http2, sqlite and permission-model changes are not on HyperDX's path: there is no TLS server, no HTTP/2, no
node:sqlite, and no first-party use of Agent keys.
- INFO (description): the d0bf80e5 "Brotli KEV item" label.
- The description calls d0bf80e5 "the Brotli KEV item". Prod
17098cbfalready runs Node 22.23.4, which has the CVE-2026-111497 fix. - The KEV-aliased item in #6 is andybalholm/brotli (GO-2026-6888) in the
migratetool. Only the all-in-one targets ship that tool, notprod. - No change is needed here.
- The description calls d0bf80e5 "the Brotli KEV item". Prod
Not re-run independently, because of a host disk-pressure pause:
- the smoke harness;
- the Trivy and osv scans;
- running
node --versionagainst my own build.
For these I re-analyzed the author's raw logs and scan JSON instead. After merging I'll check the published image through registry metadata.
🤖 Generated with Claude Code
|
Merged and published.
🤖 Generated with Claude Code |
Not HOLD. HyperDX runs in internal-services, not identity-ha, and it is not a box-shared image. Merging into
gascity-ssoonly runsgascity-image.yml. That workflow verifies, builds, runs the symbol guard and pushesghcr.io/gascity/hyperdx:2.42.0-gascity-<sha12>plus:gascity-sso. Nothing deploys until the separate repin PR in gascity/internal-services is merged.Prod status. Prod HyperDX still runs
sha256:17098cbf…(Node 22.23.4).sha256:d0bf80e5…(2.42.0-gascity-59a28cbdd09c, the #6 merge) is built and waiting for a repin; it is the Brotli KEV item. The image built from this PR would replace d0bf80e5 for the repin: it is the same app on Node 22.23.5.Advisories fixed
These come from the Node.js security release of 2026-11-11, moving Node 22.23.4 to 22.23.5:
https.AgentTLS policy key isolation)Our own triage found these not reachable as configured: HyperDX speaks plain HTTP/1.1 behind the gate and has no TLS server or HTTP/2. This is a defense-in-depth rebuild. Alpine moves from 3.24.3 to 3.24.4.
Change
docker/hyperdx/Dockerfile:NODE_VERSION=22.23.5andNODE_DIGEST=sha256:1314fb30ec37592d5845e5a160d522c0e680972aa534549940b8d73bde88481a. Both Node stages (node_base,prod) pick up the change through the existingFROM node:${NODE_VERSION}-alpine3.24@${NODE_DIGEST}. Nothing else changes.These files are deliberately not touched:
.nvmrc(22.23.1) andpackages/{app,api}/Dockerfile(node:22.16.0-alpine). They are upstream-owned and identical to upstream. Neither the fork CI nor the image build uses them, and changing them would widen the fork's upstream footprint.Verified (on this exact tree, from a pristine
git archive)crane digest node:22.23.5-alpine3.24=node:22.23.5-alpine=22.23-alpine3.24=22-alpine3.24=sha256:1314fb30…481a. It is an OCI image index for linux/amd64, arm/v6, arm/v7, arm64/v8 and s390x, plus attestations. Source:nodejs/docker-node@1274f3ee:22/alpine3.24, configNODE_VERSION=22.23.5gascity-prsteps innode:22.23.5-alpine3.24@1314fb30yarn install --immutableOK; api lint 0 errors (301 warnings, unchanged);tsc --noEmitclean; jest fork suites 4/4, 82/82; API build OK; source symbol guard 10/10docker buildx build --no-cache --pull --target prodwith the CI build contexts andCODE_VERSIONnode --versionv22.23.5, llhttp 9.4.4, sqlite 3.54.1 (d0bf80e5 has v22.23.4 / 9.4.3 / 3.53.4), OpenSSL 3.5.9, Alpine 3.24.4new https.Agent().getName({host:'h',port:443,ca:'X'})returnsh:443::s1:X:…, the new type/length-encoded TLS policy key. The d0bf80e5 image returnsh:443::X:…zlib.constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH= -17 (still present)gascity-image.ymllist)gascity/smokeharnessauth31 plus seed/verify-seed; production posture (SMOKE_TELEMETRY=off)auth31,telemetry4 and verify-seed in a second container; trace plusOTEL_LOG_LEVEL=debugauth31;failclosed3;rotation7. 0 restarts, no OOM, 0 uncaught/TLS/fatal/deprecation lines, 0 fake-secret hits in stdout. The baseline d0bf80e5 gives 107/107 with identical outcome linesNeither scanner catalogs the Node runtime, so the Node CVEs never appear in their output, old or new. The evidence that they are fixed is
node --version, the llhttp and sqlite versions, thehttps.Agentmarker, and the official v22.23.5 changelog, which lists all 15 CVEs above.Rollout
gascity-sso. Thegascity-imagerun publishesghcr.io/gascity/hyperdx:2.42.0-gascity-<sha12>; take the digest from the run's "published" output.docker run --rm --entrypoint node <img@digest> --versionprints v22.23.5. Optionally rerun the smoke harness against the digest.flux/apps/hyperdx/hyperdx.yaml. That is a separate PR and deploy step for Julian, and it is not opened here. This image replaces the pending d0bf80e5 repin. If the d0bf80e5 repin lands first, this one follows it./api/healthshows the newCODE_VERSION, with 0 restarts; rerun the §6.3 count-only checks.Rollback
Revert this commit on
gascity-sso, or simply don't repin. Prod keeps whatever digest internal-services pins:17098cbf…today, ord0bf80e5…once that repin lands. Both are the same app on Node 22.23.4.🤖 Generated with Claude Code