From 3395c555a6969231ebb7726efdaae47b9041beb5 Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Tue, 11 Aug 2026 22:39:20 -0700 Subject: [PATCH 1/3] fix(security): upgrade Node.js 22 runtime Signed-off-by: Senthil Ravichandran --- .github/actions/ci-reviewed-npm-audit/action.yaml | 2 +- .github/workflows/candidate-compatibility.yaml | 2 +- .github/workflows/e2e.yaml | 4 ++-- Dockerfile | 10 +++++----- Dockerfile.base | 4 ++-- agents/langchain-deepagents-code/Dockerfile.base | 4 ++-- ci/reviewed-npm-audit.json | 2 +- scripts/patch-bundled-npm-tar.mts | 2 +- test/e2e/live/openclaw-plugin-runtime-exdev.test.ts | 2 +- .../openshell-gateway-auth-source-contract-helpers.ts | 2 +- ...claw-plugin-runtime-exdev-workflow-boundary.test.ts | 2 +- ...ell-gateway-auth-contract-workflow-boundary.test.ts | 2 +- test/openclaw-real-patched-dist-harness.test.ts | 6 +++--- test/platform-vitest-main-workflow.test.ts | 6 +++--- test/reviewed-npm-audit-workflow.test.ts | 2 +- tools/e2e/cli-artifact-workflow-boundary.mts | 2 +- ...openclaw-plugin-runtime-exdev-workflow-boundary.mts | 2 +- ...enshell-gateway-auth-contract-workflow-boundary.mts | 2 +- tools/wsl/ci-helper.ps1 | 6 +++--- 19 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/actions/ci-reviewed-npm-audit/action.yaml b/.github/actions/ci-reviewed-npm-audit/action.yaml index 7b8693c3f5f..483fb99fd45 100644 --- a/.github/actions/ci-reviewed-npm-audit/action.yaml +++ b/.github/actions/ci-reviewed-npm-audit/action.yaml @@ -18,7 +18,7 @@ runs: - name: Setup production-compatible Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "22.23.1" + node-version: "22.23.2" - name: Download and verify production npm shell: bash diff --git a/.github/workflows/candidate-compatibility.yaml b/.github/workflows/candidate-compatibility.yaml index ac1b438de35..38ea0a3e76c 100644 --- a/.github/workflows/candidate-compatibility.yaml +++ b/.github/workflows/candidate-compatibility.yaml @@ -304,7 +304,7 @@ jobs: continue-on-error: true working-directory: candidate-source env: - DOCKER_GRPC_PROBE_IMAGE: node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba + DOCKER_GRPC_PROBE_IMAGE: node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c E2E_ARTIFACT_DIR: ${{ github.workspace }}/candidate-source/e2e-artifacts/live/openshell-gateway-auth-contract E2E_JOB: "1" E2E_TARGET_ID: openshell-gateway-auth-contract diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index deedc25bd5b..b2cc638ce7f 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -1031,7 +1031,7 @@ jobs: NEMOCLAW_RUN_LIVE_E2E: "1" NEMOCLAW_NON_INTERACTIVE: "1" NEMOCLAW_OPENSHELL_PIN_VERSION: "0.0.101" - DOCKER_GRPC_PROBE_IMAGE: "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba" + DOCKER_GRPC_PROBE_IMAGE: "node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -6233,7 +6233,7 @@ jobs: shell: bash run: | set -euo pipefail - docker pull node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba + docker pull node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c - name: Remove Docker auth before release-pinned fixture if: always() diff --git a/Dockerfile b/Dockerfile index 5584d5292da..d4203687bbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ ARG CODEX_ACP_LINUX_AMD64_0_11_1_INTEGRITY=sha512-30vSoZuW1DP6Nuz24Gg3jgVC37IYe0 ARG CODEX_ACP_LINUX_ARM64_0_11_1_INTEGRITY=sha512-I1f6WoSLbLlsWq4zH+vtwdoc4Y41mqRXPpSkfgIifxBw34QmWJmi37etZ7lKTYp6R+J/Z4PUN0rsmnsmKpBZTw== # Stage 1: Build TypeScript plugin from source -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS builder +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c AS builder ENV NPM_CONFIG_AUDIT=false \ NPM_CONFIG_FUND=false \ NPM_CONFIG_UPDATE_NOTIFIER=false \ @@ -139,7 +139,7 @@ FROM codex-acp-${TARGETARCH}-archive AS codex-acp-platform-archive # Reviewed-archive invariants (#5896): checksum-addressed source archives, # committed SRI verification, offline installation, and exact architecture. -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS codex-acp-runtime +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c AS codex-acp-runtime ARG TARGETARCH ARG CODEX_ACP_0_11_1_INTEGRITY ARG CODEX_ACP_LINUX_AMD64_0_11_1_INTEGRITY @@ -162,7 +162,7 @@ RUN --network=none set -eu; \ rm -rf /tmp/codex-acp; \ command -v codex-acp >/dev/null -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS wechat-npm-cache +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c AS wechat-npm-cache COPY agents/openclaw/wechat-runtime/package.json agents/openclaw/wechat-runtime/package-lock.json /opt/wechat-runtime/ COPY scripts/checks/materialize-locked-npm-cache-seed.mts /opt/checks/ COPY scripts/lib/reviewed-npm-archive.mts scripts/lib/seed-reviewed-npm-cache.mts /opt/nemoclaw-build-tools/ @@ -476,10 +476,10 @@ FROM openclaw-managed-messaging-npm-${TARGETARCH}-archives AS openclaw-managed-m # Keep the complete managed-image messaging dependency graph inert for normal # Dockerfile builds. Release-image builds select the exact-lock cache stage. -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS openclaw-managed-messaging-npm-cache-0 +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c AS openclaw-managed-messaging-npm-cache-0 RUN install -d -o root -g root -m 0755 /out/npm-cache -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS openclaw-managed-messaging-npm-cache-1 +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c AS openclaw-managed-messaging-npm-cache-1 ARG TARGETARCH ENV NPM_CONFIG_AUDIT=false \ NPM_CONFIG_FUND=false \ diff --git a/Dockerfile.base b/Dockerfile.base index 89f9d1dcbdf..959fb728ce6 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -59,7 +59,7 @@ ARG PERL_PACKAGE_REVISION=1nemoclaw1 # Debian trixie has not published libssh2 or Python packages containing the # reviewed upstream fixes. Build native packages once and install the same # artifacts into the managed OpenClaw image. -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS native-security-builder +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c AS native-security-builder RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential=12.12 \ @@ -96,7 +96,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN bash /scripts/security/build-perl-security-packages.sh \ /out "${PERL_VERSION}" "${PERL_SHA256}" "${PERL_PACKAGE_REVISION}" -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c COPY --from=perl-builder /out /tmp/nemoclaw-native-security diff --git a/agents/langchain-deepagents-code/Dockerfile.base b/agents/langchain-deepagents-code/Dockerfile.base index e07e2391fab..a11bc6bdbd7 100644 --- a/agents/langchain-deepagents-code/Dockerfile.base +++ b/agents/langchain-deepagents-code/Dockerfile.base @@ -13,7 +13,7 @@ ARG PERL_PACKAGE_REVISION=1nemoclaw1 ARG NEMOCLAW_CORPORATE_CA_B64= -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS native-security-builder +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c AS native-security-builder ARG NEMOCLAW_CORPORATE_CA_B64 @@ -61,7 +61,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN bash /scripts/security/build-perl-security-packages.sh \ /out "${PERL_VERSION}" "${PERL_SHA256}" "${PERL_PACKAGE_REVISION}" -FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba +FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c ARG NEMOCLAW_CORPORATE_CA_B64 diff --git a/ci/reviewed-npm-audit.json b/ci/reviewed-npm-audit.json index 7c55889367b..dd7e33e96f3 100644 --- a/ci/reviewed-npm-audit.json +++ b/ci/reviewed-npm-audit.json @@ -1,6 +1,6 @@ { "schemaVersion": 2, - "nodeVersion": "22.23.1", + "nodeVersion": "22.23.2", "registryOrigin": "https://registry.npmjs.org/", "severityThreshold": "high", "exceptionFile": "ci/npm-audit-exceptions.json", diff --git a/scripts/patch-bundled-npm-tar.mts b/scripts/patch-bundled-npm-tar.mts index 45439d2410d..25c61f153bb 100755 --- a/scripts/patch-bundled-npm-tar.mts +++ b/scripts/patch-bundled-npm-tar.mts @@ -39,7 +39,7 @@ export const MINIMUM_SAFE_TAR_VERSION = "7.5.19"; * The Dockerfile contract test forces that review whenever either pin changes. */ export const NODE_BASES_REQUIRING_BUNDLED_NPM_TAR_PATCH = [ - "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba", + "node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c", "node:24-trixie-slim@sha256:05c08ce4291e9a58f59456a7985176defb12cdd42271f35ff81a3e167ea61d4c", ] as const; diff --git a/test/e2e/live/openclaw-plugin-runtime-exdev.test.ts b/test/e2e/live/openclaw-plugin-runtime-exdev.test.ts index 7407bb5acf3..8ecfa184ba6 100644 --- a/test/e2e/live/openclaw-plugin-runtime-exdev.test.ts +++ b/test/e2e/live/openclaw-plugin-runtime-exdev.test.ts @@ -95,7 +95,7 @@ const NEMOCLAW_SOURCE_REPOSITORY = "https://github.com/NVIDIA/NemoClaw.git"; const RELEASE_BUILDER_IMAGE_REF = "node:22-trixie-slim@sha256:2d9f5c76c8f4dd36e8f253bee5d828a83a6c09f36188f0b0414325232e0b175d"; const CURRENT_BUILDER_IMAGE_REF = - "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba"; + "node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c"; const TOOL_DISCLOSURE_ENV_REFERENCE = "${NEMOCLAW_TOOL_DISCLOSURE}"; const SANDBOX_NAME = process.env.NEMOCLAW_SANDBOX_NAME ?? "e2e-oc-exdev"; const ONBOARD_TIMEOUT_MS = 25 * 60_000; diff --git a/test/e2e/live/openshell-gateway-auth-source-contract-helpers.ts b/test/e2e/live/openshell-gateway-auth-source-contract-helpers.ts index d10e0583435..b57d41b5fc3 100644 --- a/test/e2e/live/openshell-gateway-auth-source-contract-helpers.ts +++ b/test/e2e/live/openshell-gateway-auth-source-contract-helpers.ts @@ -26,7 +26,7 @@ import type { TestProgress } from "../fixtures/progress.ts"; const SANDBOX_JWT_SUBJECT_PREFIX = "spiffe://openshell/sandbox/"; const DOCKER_GRPC_PROBE_IMAGE = - "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba"; + "node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c"; type SkipFn = (message?: string) => void; diff --git a/test/e2e/support/openclaw-plugin-runtime-exdev-workflow-boundary.test.ts b/test/e2e/support/openclaw-plugin-runtime-exdev-workflow-boundary.test.ts index 604f2fa2dd5..b4937e3e021 100644 --- a/test/e2e/support/openclaw-plugin-runtime-exdev-workflow-boundary.test.ts +++ b/test/e2e/support/openclaw-plugin-runtime-exdev-workflow-boundary.test.ts @@ -86,7 +86,7 @@ describe("OpenClaw plugin runtime EXDEV workflow boundary", () => { "openclaw-plugin-runtime-exdev action 'actions/checkout@v6' must pin a full SHA", "openclaw-plugin-runtime-exdev checkout must disable persisted credentials", "openclaw-plugin-runtime-exdev must use the reviewed prepare-e2e action", - "openclaw-plugin-runtime-exdev step 'Pre-pull release-matched Docker Hub builder image' must run: docker pull node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba", + "openclaw-plugin-runtime-exdev step 'Pre-pull release-matched Docker Hub builder image' must run: docker pull node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c", "openclaw-plugin-runtime-exdev must always revoke Docker auth before the release-pinned fixture", "openclaw-plugin-runtime-exdev must use the pinned Docker auth cleanup action before artifact restore", "openclaw-plugin-runtime-exdev runtime proof must not receive workflow credentials", diff --git a/test/e2e/support/openshell-gateway-auth-contract-workflow-boundary.test.ts b/test/e2e/support/openshell-gateway-auth-contract-workflow-boundary.test.ts index dac7d82d58c..04a579ee593 100644 --- a/test/e2e/support/openshell-gateway-auth-contract-workflow-boundary.test.ts +++ b/test/e2e/support/openshell-gateway-auth-contract-workflow-boundary.test.ts @@ -69,7 +69,7 @@ describe("OpenShell gateway auth contract workflow boundary", () => { "openshell-gateway-auth-contract must run on main pushes and retain manual selectors", "openshell-gateway-auth-contract must run on ubuntu-latest", "openshell-gateway-auth-contract must retain its 20 minute resource budget", - "openshell-gateway-auth-contract must set DOCKER_GRPC_PROBE_IMAGE=node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba", + "openshell-gateway-auth-contract must set DOCKER_GRPC_PROBE_IMAGE=node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c", "openshell-gateway-auth-contract must set E2E_ARTIFACT_DIR=${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-auth-contract", "openshell-gateway-auth-contract must set NEMOCLAW_OPENSHELL_PIN_VERSION to an exact version", "openshell-gateway-auth-contract must not expose NVIDIA_API_KEY at job scope", diff --git a/test/openclaw-real-patched-dist-harness.test.ts b/test/openclaw-real-patched-dist-harness.test.ts index 5da8422377e..863d4ee03e5 100644 --- a/test/openclaw-real-patched-dist-harness.test.ts +++ b/test/openclaw-real-patched-dist-harness.test.ts @@ -175,7 +175,7 @@ function resolveRealOpenClawNodeRuntime( supportedNode22 || runtimeMismatch( version, - "Node >=22.22.3 <23 (the Dockerfile runtime is Node 22.23.1)", + "Node >=22.22.3 <23 (the Dockerfile runtime is Node 22.23.2)", REAL_OPENCLAW_NODE_ENV, ); @@ -317,11 +317,11 @@ describe("OpenClaw real patched-dist materialization guard", () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openclaw-node-runtime-")); try { const fakeNode = path.join(tmp, "node"); - fs.writeFileSync(fakeNode, "#!/bin/sh\nprintf 'v22.23.1\\n'\n", { mode: 0o700 }); + fs.writeFileSync(fakeNode, "#!/bin/sh\nprintf 'v22.23.2\\n'\n", { mode: 0o700 }); expect(resolveRealOpenClawNodeRuntime({ [REAL_OPENCLAW_NODE_ENV]: fakeNode })).toEqual({ executable: fakeNode, - version: "v22.23.1", + version: "v22.23.2", }); } finally { fs.rmSync(tmp, { recursive: true, force: true }); diff --git a/test/platform-vitest-main-workflow.test.ts b/test/platform-vitest-main-workflow.test.ts index 37fbd1a1b4d..d785e9c77cd 100644 --- a/test/platform-vitest-main-workflow.test.ts +++ b/test/platform-vitest-main-workflow.test.ts @@ -45,12 +45,12 @@ describe("platform Vitest main workflow", () => { for (const installStep of installSteps) { expect(installStep, "missing WSL Node.js install step").toBeDefined(); const run = installStep?.run ?? ""; - expect(run).toContain('node_version="22.23.1"'); + expect(run).toContain('node_version="22.23.2"'); expect(run).toMatch( - /x86_64\)[\s\S]*?node_arch="x64"[\s\S]*?node_sha256="9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578"[\s\S]*?;;/u, + /x86_64\)[\s\S]*?node_arch="x64"[\s\S]*?node_sha256="d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307"[\s\S]*?;;/u, ); expect(run).toMatch( - /aarch64 \| arm64\)[\s\S]*?node_arch="arm64"[\s\S]*?node_sha256="0294e8b915ab75f92c7513d2fcb830ae06e10684e6c603e99a87dbf8835389c1"[\s\S]*?;;/u, + /aarch64 \| arm64\)[\s\S]*?node_arch="arm64"[\s\S]*?node_sha256="fff4078c5def658577f92c88db7db3bc0072924bfb93fe52c1e744a54e94abb8"[\s\S]*?;;/u, ); expect(run).toContain( 'node_url="https://nodejs.org/dist/v${node_version}/node-v${node_version}-linux-${node_arch}.tar.xz"', diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index 8ed51c8fbd2..ae5efa44541 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -119,7 +119,7 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { replacementLockSha256: digest, }, ], - nodeVersion: "22.23.1", + nodeVersion: "22.23.2", registryOrigin: "https://registry.npmjs.org/", schemaVersion: 2, severityThreshold: "high", diff --git a/tools/e2e/cli-artifact-workflow-boundary.mts b/tools/e2e/cli-artifact-workflow-boundary.mts index 3ba5eed8ca1..ea33ae8f605 100644 --- a/tools/e2e/cli-artifact-workflow-boundary.mts +++ b/tools/e2e/cli-artifact-workflow-boundary.mts @@ -39,7 +39,7 @@ const CLI_ARTIFACT_PROVENANCE_STEP = "Record CLI artifact provenance"; const CANDIDATE_CHECKOUT_STEP_CONTENT_SHA256 = "3578a053cede863f7aa4814d8399b4ca21ea0b77cee712e6d549c684818f11dd"; const CLI_ARTIFACT_WORKFLOW_CONTRACT_SHA256 = - "6b9110770f2a6f5370910054d40eee6935161eb8fc5ee4ed3afec808cf536e89"; + "fb85b3fd613f1a29d74356faeb4be9092a14319a34fc9b291af1a5a020695496"; type WorkflowRecord = Record; type WorkflowStep = WorkflowRecord & { diff --git a/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts b/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts index 27bacd29b21..4647734d975 100644 --- a/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts +++ b/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts @@ -36,7 +36,7 @@ const JOB_CONTRACTS = [ runName: "Run OpenClaw custom-plugin lifecycle and runtime-deps EXDEV live test", uploadName: "Upload OpenClaw plugin runtime-deps EXDEV artifacts", builderImage: - "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba", + "node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c", }, ] as const; diff --git a/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts b/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts index d6ecbb49f93..ba278b343cb 100644 --- a/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts +++ b/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts @@ -16,7 +16,7 @@ const FULL_SHA_ACTION = /^[^\s@]+@[0-9a-f]{40}$/u; const MAIN_AND_MANUAL_CONDITION = "${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openshell-gateway-auth-contract,') || contains(format(',{0},', inputs.targets), ',openshell-gateway-auth-contract,') }}"; const GATEWAY_PROBE_IMAGE = - "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba"; + "node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c"; const ARTIFACT_SAFETY_GATED_UPLOAD = "${{ always() && steps.artifact_safety.outcome == 'success' && steps.artifact_safety.outputs.approved_path != '' }}"; const APPROVED_ARTIFACT_PATH = "${{ steps.artifact_safety.outputs.approved_path }}"; diff --git a/tools/wsl/ci-helper.ps1 b/tools/wsl/ci-helper.ps1 index 182f0fe407f..c9ac607b4b8 100644 --- a/tools/wsl/ci-helper.ps1 +++ b/tools/wsl/ci-helper.ps1 @@ -302,15 +302,15 @@ function Install-WslUbuntuDependencies { function Get-WslNodeInstallScript { return @' set -euo pipefail -node_version="22.23.1" +node_version="22.23.2" case "$(uname -m)" in x86_64) node_arch="x64" - node_sha256="9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578" + node_sha256="d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307" ;; aarch64 | arm64) node_arch="arm64" - node_sha256="0294e8b915ab75f92c7513d2fcb830ae06e10684e6c603e99a87dbf8835389c1" + node_sha256="fff4078c5def658577f92c88db7db3bc0072924bfb93fe52c1e744a54e94abb8" ;; *) echo "Unsupported Node.js architecture: $(uname -m)" >&2 From 03ea1ee7042d2599508955f80b7b6758e9b5b809 Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Tue, 11 Aug 2026 23:14:25 -0700 Subject: [PATCH 2/3] test(security): cover Deep Agents Node base Signed-off-by: Senthil Ravichandran --- test/node-tar-dockerfile-contract.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/node-tar-dockerfile-contract.test.ts b/test/node-tar-dockerfile-contract.test.ts index 6cef4000710..7fd68fe81c2 100644 --- a/test/node-tar-dockerfile-contract.test.ts +++ b/test/node-tar-dockerfile-contract.test.ts @@ -48,7 +48,11 @@ function namedStage(source: string, name: string): string { describe("node-tar image remediation contract", () => { it("binds the remediation lifecycle to the affected upstream Node image pins", () => { - const pinnedBaseSources = ["Dockerfile.base", "agents/hermes/Dockerfile.base"] + const pinnedBaseSources = [ + "Dockerfile.base", + "agents/hermes/Dockerfile.base", + "agents/langchain-deepagents-code/Dockerfile.base", + ] .map((file) => fs.readFileSync(path.join(repoRoot, file), "utf8")) .join("\n"); From 7e0cbc5058caf64778124e10400ba1712e5eed7b Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Tue, 11 Aug 2026 23:49:09 -0700 Subject: [PATCH 3/3] test(security): reject isolated Node base drift Signed-off-by: Senthil Ravichandran --- ci/source-shape-test-budget.json | 5 ++ test/node-tar-dockerfile-contract.test.ts | 57 +++++++++++++++++++---- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index 173be7a0203..f5b24746c6f 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -361,6 +361,11 @@ "test": "replaces the reviewed OpenClaw core tar and brace-expansion graph", "category": "security" }, + { + "file": "test/node-tar-dockerfile-contract.test.ts", + "test": "rejects an isolated unreviewed Deep Agents Code Node base pin", + "category": "security" + }, { "file": "test/openclaw-dependency-review.test.ts", "test": "copies the legacy OpenClaw remediation helper before the base build invokes it", diff --git a/test/node-tar-dockerfile-contract.test.ts b/test/node-tar-dockerfile-contract.test.ts index 7fd68fe81c2..4e4685321a2 100644 --- a/test/node-tar-dockerfile-contract.test.ts +++ b/test/node-tar-dockerfile-contract.test.ts @@ -32,6 +32,29 @@ const dockerfiles = [ ] as const; const patchCommand = "node --experimental-strip-types /scripts/patch-bundled-npm-tar.mts"; const npmRootArguments = ["--npm-root", "/usr/local/lib/node_modules/npm"] as const; +const pinnedBaseDockerfiles = [ + "Dockerfile.base", + "agents/hermes/Dockerfile.base", + "agents/langchain-deepagents-code/Dockerfile.base", +] as const; +const reviewedNodeBases = new Set(NODE_BASES_REQUIRING_BUNDLED_NPM_TAR_PATCH); + +function nodeBaseReferences(source: string): string[] { + return [ + ...new Set( + [...source.matchAll(/^FROM\s+(node:[^\s]+@sha256:[0-9a-f]{64})(?:\s|$)/gmu)].map( + (match) => match[1]!, + ), + ), + ].sort(); +} + +function assertReviewedNodeBases(file: string, source: string): void { + const bases = nodeBaseReferences(source); + assert(bases.length > 0, `${file} must pin at least one upstream Node base image`); + const unreviewed = bases.filter((base) => !reviewedNodeBases.has(base)); + assert.deepEqual(unreviewed, [], `${file} contains an unreviewed upstream Node base image`); +} function completedStage(source: string): string { const finalStageStart = [...source.matchAll(/^FROM\b/gmu)].at(-1)?.index; @@ -48,17 +71,31 @@ function namedStage(source: string, name: string): string { describe("node-tar image remediation contract", () => { it("binds the remediation lifecycle to the affected upstream Node image pins", () => { - const pinnedBaseSources = [ - "Dockerfile.base", - "agents/hermes/Dockerfile.base", - "agents/langchain-deepagents-code/Dockerfile.base", - ] - .map((file) => fs.readFileSync(path.join(repoRoot, file), "utf8")) - .join("\n"); - - for (const base of NODE_BASES_REQUIRING_BUNDLED_NPM_TAR_PATCH) { - expect(pinnedBaseSources, base).toContain(`FROM ${base}`); + const observedBases = new Set(); + for (const file of pinnedBaseDockerfiles) { + const source = fs.readFileSync(path.join(repoRoot, file), "utf8"); + assertReviewedNodeBases(file, source); + for (const base of nodeBaseReferences(source)) observedBases.add(base); } + expect([...observedBases].sort()).toEqual( + [...NODE_BASES_REQUIRING_BUNDLED_NPM_TAR_PATCH].sort(), + ); + }); + + // source-shape-contract: security -- Each managed Dockerfile must remain bound to a reviewed Node base digest. + it("rejects an isolated unreviewed Deep Agents Code Node base pin", () => { + const file = "agents/langchain-deepagents-code/Dockerfile.base"; + const source = fs.readFileSync(path.join(repoRoot, file), "utf8"); + const reviewedBase = NODE_BASES_REQUIRING_BUNDLED_NPM_TAR_PATCH.find((base) => + base.startsWith("node:22-"), + ); + assert(reviewedBase !== undefined, "the reviewed Node 22 base must be registered"); + const unreviewedBase = `node:22-trixie-slim@sha256:${"0".repeat(64)}`; + const changedSource = source.replaceAll(reviewedBase, unreviewedBase); + + expect(() => assertReviewedNodeBases(file, changedSource)).toThrow( + `${file} contains an unreviewed upstream Node base image`, + ); }); it.each([