From db85e273f26d84d655968022126049282a29695a Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Mon, 13 Jul 2026 19:13:08 -0700 Subject: [PATCH 01/16] fix(images): lock OpenClaw production graph Signed-off-by: Ho Lim --- .../actions/ci-reviewed-npm-audit/action.yaml | 28 +- .../resolve-sandbox-base-image/action.yaml | 8 +- .github/workflows/base-image.yaml | 3 + .github/workflows/main.yaml | 3 + .github/workflows/pr.yaml | 3 + Dockerfile | 63 +- Dockerfile.base | 81 +- agents/openclaw/dependency-review.md | 21 +- .../openclaw-runtime/package-lock.json | 3586 +++++++++++++++++ agents/openclaw/openclaw-runtime/package.json | 14 + ci/reviewed-npm-audit.json | 18 +- .../openclaw-2026.6.10-dependency-review.md | 20 +- scripts/audit-reviewed-npm-graph.mts | 56 +- scripts/lib/reviewed-npm-archive.mts | 274 +- src/lib/sandbox/build-context.ts | 2 +- test/openclaw-dependency-review.test.ts | 13 +- test/openclaw-integrity-pin-suite.ts | 223 +- test/openclaw-locked-install.test.ts | 322 ++ test/reviewed-npm-archive.test.ts | 34 + test/sandbox-build-context.test.ts | 4 +- 20 files changed, 4620 insertions(+), 156 deletions(-) create mode 100644 agents/openclaw/openclaw-runtime/package-lock.json create mode 100644 agents/openclaw/openclaw-runtime/package.json create mode 100644 test/openclaw-locked-install.test.ts diff --git a/.github/actions/ci-reviewed-npm-audit/action.yaml b/.github/actions/ci-reviewed-npm-audit/action.yaml index c6aec7245e4..d21dec3f60f 100644 --- a/.github/actions/ci-reviewed-npm-audit/action.yaml +++ b/.github/actions/ci-reviewed-npm-audit/action.yaml @@ -2,7 +2,15 @@ # SPDX-License-Identifier: Apache-2.0 name: ci-reviewed-npm-audit -description: Materialize and audit the reviewed production npm graphs. +description: Audit a target tree's reviewed production npm graphs from trusted CI code. + +inputs: + target-root: + description: Absolute path to the checked-out target tree whose graph inputs are audited. + required: true + report-dir: + description: Report directory relative to target-root. + required: true runs: using: composite @@ -12,14 +20,28 @@ runs: with: node-version: "22.22.2" + - name: Pin production npm + shell: bash + run: >- + cd "$RUNNER_TEMP" && + npm install --global npm@10.9.4 + --userconfig /dev/null + --registry https://registry.npmjs.org/ + --ignore-scripts --no-audit --no-fund + - name: Materialize and audit reviewed npm graphs shell: bash - run: node --experimental-strip-types scripts/audit-reviewed-npm-graph.mts + env: + NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT: ${{ inputs.target-root }} + NEMOCLAW_REVIEWED_NPM_AUDIT_REPORT_DIR: ${{ inputs.report-dir }} + NPM_CONFIG_REGISTRY: https://registry.npmjs.org/ + NPM_CONFIG_USERCONFIG: /dev/null + run: node --experimental-strip-types "$GITHUB_ACTION_PATH/../../../scripts/audit-reviewed-npm-graph.mts" - name: Upload reviewed npm audit reports if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: reviewed-npm-audit - path: coverage/reviewed-npm-audit/*.json + path: ${{ inputs.target-root }}/${{ inputs.report-dir }}/*.json if-no-files-found: error diff --git a/.github/actions/resolve-sandbox-base-image/action.yaml b/.github/actions/resolve-sandbox-base-image/action.yaml index 5e3acc7e65d..0597bd93e9e 100644 --- a/.github/actions/resolve-sandbox-base-image/action.yaml +++ b/.github/actions/resolve-sandbox-base-image/action.yaml @@ -14,7 +14,13 @@ runs: image="ghcr.io/nvidia/nemoclaw/sandbox-base" min_glibc="2.39" - base_inputs=(Dockerfile.base nemoclaw-blueprint/blueprint.yaml) + base_inputs=( + Dockerfile.base + agents/openclaw/openclaw-runtime/package.json + agents/openclaw/openclaw-runtime/package-lock.json + nemoclaw-blueprint/blueprint.yaml + scripts/lib/reviewed-npm-archive.mts + ) source "${GITHUB_ACTION_PATH}/../base-image-resolver.sh" normalize_version_tag() { diff --git a/.github/workflows/base-image.yaml b/.github/workflows/base-image.yaml index 87a4d49be2f..eb91dd1d673 100644 --- a/.github/workflows/base-image.yaml +++ b/.github/workflows/base-image.yaml @@ -23,6 +23,9 @@ on: # takes effect immediately after merge instead of waiting for another tag. - ".github/workflows/base-image.yaml" - "Dockerfile.base" + - "agents/openclaw/openclaw-runtime/package.json" + - "agents/openclaw/openclaw-runtime/package-lock.json" + - "scripts/lib/reviewed-npm-archive.mts" - "agents/hermes/Dockerfile.base" - "agents/langchain-deepagents-code/Dockerfile.base" - "agents/langchain-deepagents-code/manifest.yaml" diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ff6cdaba33d..c59b0079f06 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -92,6 +92,9 @@ jobs: - name: Audit reviewed production npm graphs uses: ./.github/actions/ci-reviewed-npm-audit + with: + target-root: ${{ github.workspace }} + report-dir: artifacts/reviewed-npm-audit real-openclaw-dist-harness: runs-on: ubuntu-latest diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 0d5fdd12135..03e4bf761e2 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -286,6 +286,9 @@ jobs: - name: Audit reviewed production npm graphs uses: ./.github/actions/ci-reviewed-npm-audit + with: + target-root: ${{ github.workspace }} + report-dir: artifacts/reviewed-npm-audit cli-test-shards: needs: changes diff --git a/Dockerfile b/Dockerfile index 1a316332f52..b8b79bbe4a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,6 +65,8 @@ ARG CODEX_ACP_0_11_1_INTEGRITY=sha512-My2VSlBtvJipJhImHjFDej2ut/p00QqOISRnZgLgLr ARG MCPORTER_VERSION=0.7.3 ARG MCPORTER_0_7_3_INTEGRITY=sha512-egoPVYqTnWb3NjRIxo+xc8OrAI0dlPrJm9pAiZx0pImuNIV5rKhGtTnIfH/Y1ldGPVu74ibj3KR5c9U/QSdQFA== ARG MCPORTER_0_7_3_TARBALL=https://registry.npmjs.org/mcporter/-/mcporter-0.7.3.tgz +COPY agents/openclaw/openclaw-runtime/package.json /usr/local/lib/nemoclaw/openclaw-runtime/package.json +COPY agents/openclaw/openclaw-runtime/package-lock.json /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json COPY agents/openclaw/mcporter-runtime/package.json /usr/local/lib/nemoclaw/mcporter-runtime/package.json COPY agents/openclaw/mcporter-runtime/package-lock.json /usr/local/lib/nemoclaw/mcporter-runtime/package-lock.json COPY agents/openclaw/wechat-runtime/package.json /usr/local/lib/nemoclaw/wechat-runtime/package.json @@ -234,8 +236,9 @@ RUN chmod 755 /usr/local/lib/nemoclaw/patch-openclaw-tool-catalog.mts \ # Pack the already-reviewed tarball URL after verifying current registry # metadata. Re-resolving package@version here would introduce another mutable # registry selection between the reviewed identity check and installation. -# Reviewed-archive invariants (#5896): registry SRI, packed-byte SRI, contained -# basename in a fresh directory, local-archive-only install, and cleanup. +# Current production OpenClaw consumes the committed transitive lock. Legacy E2E +# fixture pins retain the reviewed local-archive path until their separate +# compatibility retirement. # # hadolint ignore=DL3059,DL4006,DL3016 RUN set -eu; \ @@ -263,8 +266,9 @@ RUN set -eu; \ # # OPENCLAW_VERSION is the NemoClaw runtime build target. It must be at least the # blueprint minimum, which also supports the legacy direct-blueprint image path. -# Reviewed-archive invariants (#5896): registry SRI, packed-byte SRI, contained -# basename in a fresh directory, local-archive-only install, and cleanup. +# Reviewed-archive invariants (#5896): legacy fixture installs retain registry +# SRI, packed-byte SRI, contained basename, local-archive install, and cleanup. +# The current production pin instead consumes the committed lock closure. # hadolint ignore=DL3059,DL4006,DL3016 RUN set -eu; \ echo "$OPENCLAW_VERSION" | grep -qxE '[0-9]+(\.[0-9]+)*' \ @@ -296,6 +300,15 @@ RUN set -eu; \ MCPORTER_LOCK_SHA256="$(sha256sum /usr/local/lib/nemoclaw/mcporter-runtime/package-lock.json | awk '{print $1}')"; \ [ -n "$MCPORTER_LOCK_SHA256" ] \ || { echo "ERROR: Could not hash the committed mcporter lockfile" >&2; exit 1; }; \ + OPENCLAW_LOCK_SHA256=none-legacy-fixture; \ + OPENCLAW_INSTALL_RECIPE=ignore-scripts+reviewed-lifecycle-v1; \ + if [ "$OPENCLAW_VERSION" = "2026.6.10" ]; then \ + OPENCLAW_LOCK_SHA256=a0f91c7e0b769e73c3f6119b2a6ee2dfd9bcb32b3dc69655b22696c654694d2d; \ + ACTUAL_OPENCLAW_LOCK_SHA256="$(sha256sum /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json | awk '{print $1}')"; \ + [ "$ACTUAL_OPENCLAW_LOCK_SHA256" = "$OPENCLAW_LOCK_SHA256" ] \ + || { echo "ERROR: OpenClaw lock SHA-256 mismatch (expected $OPENCLAW_LOCK_SHA256, found $ACTUAL_OPENCLAW_LOCK_SHA256)" >&2; exit 1; }; \ + OPENCLAW_INSTALL_RECIPE=locked-ci+reviewed-lifecycle-v2; \ + fi; \ CUR_VER=$(openclaw --version 2>/dev/null | awk '{print $2}' || true); \ CUR_VER="${CUR_VER:-0.0.0}"; \ CUR_MCPORTER_VER=$(mcporter --version 2>/dev/null || true); \ @@ -303,11 +316,12 @@ RUN set -eu; \ OPENCLAW_PROVENANCE_PATH=/usr/local/share/nemoclaw/openclaw-base-provenance-v1; \ OPENCLAW_EXPECTED_PROVENANCE="$(mktemp)"; \ printf '%s\n' \ - 'schema=2' \ + 'schema=3' \ "package=openclaw@${OPENCLAW_VERSION}" \ "integrity=${EXPECTED_INTEGRITY}" \ "tarball=${EXPECTED_TARBALL}" \ - 'recipe=ignore-scripts+reviewed-lifecycle-v1' \ + "lock-sha256=${OPENCLAW_LOCK_SHA256}" \ + "recipe=${OPENCLAW_INSTALL_RECIPE}" \ "mcporter-package=mcporter@${MCPORTER_VERSION}" \ "mcporter-integrity=${MCPORTER_EXPECTED_INTEGRITY}" \ "mcporter-tarball=${MCPORTER_EXPECTED_TARBALL}" \ @@ -337,21 +351,44 @@ RUN set -eu; \ echo "ERROR: Base image has OpenClaw $CUR_VER, which is newer than reviewed target $OPENCLAW_VERSION" >&2; exit 1; \ else \ echo "INFO: Base image OpenClaw $CUR_VER lacks exact reviewed provenance; installing $OPENCLAW_VERSION"; \ - OPENCLAW_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ - --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ - --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")"; \ - OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ # npm 10's atomic-move install can hit EROFS on overlayfs when the prior # install spans image layers. Removing it first also prevents unreviewed # files from surviving a same-version reinstall. rm -rf /usr/local/lib/node_modules/openclaw /usr/local/bin/openclaw; \ - npm install -g --no-audit --no-fund --no-progress --ignore-scripts "$OPENCLAW_PACK_PATH"; \ + if [ "$OPENCLAW_VERSION" = "2026.6.10" ]; then \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts --verify-lock \ + --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --registry-origin https://registry.npmjs.org/ \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}"; \ + npm --prefix /usr/local/lib/nemoclaw/openclaw-runtime ci \ + --ignore-scripts --omit=dev --no-audit --no-fund --no-progress \ + --userconfig /dev/null --registry https://registry.npmjs.org/; \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --verify-installed-lock --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --install-root /usr/local/lib/nemoclaw/openclaw-runtime \ + --label "OpenClaw ${OPENCLAW_VERSION}"; \ + else \ + OPENCLAW_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")"; \ + OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ + npm install -g --no-audit --no-fund --no-progress --ignore-scripts "$OPENCLAW_PACK_PATH"; \ + rm -rf "$OPENCLAW_PACK_DIR"; \ + fi; \ case "$OPENCLAW_VERSION" in \ - 2026.4.24|2026.6.10) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ + 2026.6.10) node /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ + 2026.4.24) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ 2026.3.11) ;; \ *) echo "ERROR: OpenClaw ${OPENCLAW_VERSION} has no reviewed lifecycle policy" >&2; exit 1 ;; \ esac; \ - rm -rf "$OPENCLAW_PACK_DIR"; \ + if [ "$OPENCLAW_VERSION" = "2026.6.10" ]; then \ + mkdir -p /usr/local/lib/node_modules; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw /usr/local/lib/node_modules/openclaw; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/.bin/openclaw /usr/local/bin/openclaw; \ + fi; \ fi; \ if [ "$USE_REVIEWED_BASE_RUNTIME" = "1" ]; then \ echo "INFO: Reusing reviewed base mcporter $CUR_MCPORTER_VER with exact lock provenance"; \ diff --git a/Dockerfile.base b/Dockerfile.base index b96754c334f..24c7c999698 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -222,6 +222,8 @@ ARG OPENCLAW_2026_4_24_TARBALL=https://registry.npmjs.org/openclaw/-/openclaw-20 ARG MCPORTER_VERSION=0.7.3 ARG MCPORTER_0_7_3_INTEGRITY=sha512-egoPVYqTnWb3NjRIxo+xc8OrAI0dlPrJm9pAiZx0pImuNIV5rKhGtTnIfH/Y1ldGPVu74ibj3KR5c9U/QSdQFA== ARG MCPORTER_0_7_3_TARBALL=https://registry.npmjs.org/mcporter/-/mcporter-0.7.3.tgz +COPY agents/openclaw/openclaw-runtime/package.json /usr/local/lib/nemoclaw/openclaw-runtime/package.json +COPY agents/openclaw/openclaw-runtime/package-lock.json /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json COPY agents/openclaw/mcporter-runtime/package.json /usr/local/lib/nemoclaw/mcporter-runtime/package.json COPY agents/openclaw/mcporter-runtime/package-lock.json /usr/local/lib/nemoclaw/mcporter-runtime/package-lock.json COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts @@ -235,10 +237,14 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] # Install OpenClaw CLI + PyYAML. # .openclaw is now writable by default, so exec-approvals writes to # ~/.openclaw/exec-approvals.json natively — no sed patch needed. -# Reviewed-archive invariants (#5896): registry SRI, packed-byte SRI, contained -# basename in a fresh directory, local-archive-only install, and cleanup. +# Reviewed-archive invariants (#5896): legacy E2E fixture pins retain the +# reviewed local-archive path until their separate compatibility retirement. +# Current production OpenClaw consumes the committed transitive lock. +# Both paths disable lifecycle scripts and run only +# the version-reviewed bundled-plugin lifecycle explicitly. # hadolint ignore=DL3016 RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/blueprint.yaml \ + set -eu; \ echo "$OPENCLAW_VERSION" | grep -qxE '[0-9]+(\.[0-9]+)*' \ || { echo "Error: OPENCLAW_VERSION='$OPENCLAW_VERSION' is invalid (expected e.g. 2026.3.11)."; exit 1; }; \ OPENCLAW_MIN_VERSION=$(grep -m 1 'min_openclaw_version' /tmp/blueprint.yaml | awk '{print $2}' | tr -d '"'); \ @@ -265,25 +271,61 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep if [ -z "$EXPECTED_INTEGRITY" ]; then \ echo "Error: OpenClaw ${OPENCLAW_VERSION} has no committed npm integrity pin"; exit 1; \ fi; \ - OPENCLAW_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ - --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ - --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")"; \ - if [ -z "$OPENCLAW_PACK_PATH" ] || [ ! -f "$OPENCLAW_PACK_PATH" ] || [ -L "$OPENCLAW_PACK_PATH" ]; then \ - echo "Error: reviewed OpenClaw archive path is empty or invalid"; exit 1; \ + OPENCLAW_LOCK_SHA256=none-legacy-fixture; \ + OPENCLAW_INSTALL_RECIPE=ignore-scripts+reviewed-lifecycle-v1; \ + if [ "$OPENCLAW_VERSION" = "2026.6.10" ]; then \ + OPENCLAW_LOCK_SHA256=a0f91c7e0b769e73c3f6119b2a6ee2dfd9bcb32b3dc69655b22696c654694d2d; \ + ACTUAL_OPENCLAW_LOCK_SHA256="$(sha256sum /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json | awk '{print $1}')"; \ + if [ "$ACTUAL_OPENCLAW_LOCK_SHA256" != "$OPENCLAW_LOCK_SHA256" ]; then \ + echo "Error: OpenClaw lock SHA-256 mismatch (expected $OPENCLAW_LOCK_SHA256, found $ACTUAL_OPENCLAW_LOCK_SHA256)"; exit 1; \ + fi; \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts --verify-lock \ + --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --registry-origin https://registry.npmjs.org/ \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}" \ + || exit 1; \ + OPENCLAW_INSTALL_RECIPE=locked-ci+reviewed-lifecycle-v2; \ + rm -rf /usr/local/lib/node_modules/openclaw /usr/local/bin/openclaw; \ + npm --prefix /usr/local/lib/nemoclaw/openclaw-runtime ci \ + --ignore-scripts --omit=dev --no-audit --no-fund --no-progress \ + --userconfig /dev/null --registry https://registry.npmjs.org/ \ + || exit 1; \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --verify-installed-lock --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --install-root /usr/local/lib/nemoclaw/openclaw-runtime \ + --label "OpenClaw ${OPENCLAW_VERSION}" \ + || exit 1; \ + else \ + OPENCLAW_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")" \ + || exit 1; \ + if [ -z "$OPENCLAW_PACK_PATH" ] || [ ! -f "$OPENCLAW_PACK_PATH" ] || [ -L "$OPENCLAW_PACK_PATH" ]; then \ + echo "Error: reviewed OpenClaw archive path is empty or invalid"; exit 1; \ + fi; \ + OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ + npm install -g --ignore-scripts "$OPENCLAW_PACK_PATH" || exit 1; \ + rm -rf "$OPENCLAW_PACK_DIR"; \ fi; \ - OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ - npm install -g --ignore-scripts "$OPENCLAW_PACK_PATH" \ - && case "$OPENCLAW_VERSION" in \ - 2026.4.24|2026.6.10) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ + case "$OPENCLAW_VERSION" in \ + 2026.6.10) node /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ + 2026.4.24) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ 2026.3.11) ;; \ *) echo "Error: OpenClaw ${OPENCLAW_VERSION} has no reviewed lifecycle policy"; exit 1 ;; \ - esac \ - && rm -rf "$OPENCLAW_PACK_DIR" \ - && OPENCLAW_INSTALLED_VERSION="$(openclaw --version 2>/dev/null | awk '{print $2}')" \ - && if [ "$OPENCLAW_INSTALLED_VERSION" != "$OPENCLAW_VERSION" ]; then \ + esac; \ + if [ "$OPENCLAW_VERSION" = "2026.6.10" ]; then \ + mkdir -p /usr/local/lib/node_modules; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw /usr/local/lib/node_modules/openclaw; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/.bin/openclaw /usr/local/bin/openclaw; \ + fi; \ + OPENCLAW_INSTALLED_VERSION="$(openclaw --version 2>/dev/null | awk '{print $2}')"; \ + if [ "$OPENCLAW_INSTALLED_VERSION" != "$OPENCLAW_VERSION" ]; then \ echo "Error: Installed OpenClaw ${OPENCLAW_INSTALLED_VERSION:-unknown} does not match reviewed target ${OPENCLAW_VERSION}"; exit 1; \ - fi \ - && MCPORTER_EXPECTED_INTEGRITY="" \ + fi; \ + MCPORTER_EXPECTED_INTEGRITY="" \ && MCPORTER_EXPECTED_TARBALL="" \ && if [ "$MCPORTER_VERSION" = "0.7.3" ]; then MCPORTER_EXPECTED_INTEGRITY="$MCPORTER_0_7_3_INTEGRITY"; MCPORTER_EXPECTED_TARBALL="$MCPORTER_0_7_3_TARBALL"; fi \ && if [ -z "$MCPORTER_EXPECTED_INTEGRITY" ]; then \ @@ -306,11 +348,12 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep && mkdir -p "$OPENCLAW_PROVENANCE_DIR" \ && OPENCLAW_PROVENANCE_TMP="$(mktemp "${OPENCLAW_PROVENANCE_PATH}.tmp.XXXXXX")" \ && printf '%s\n' \ - 'schema=2' \ + 'schema=3' \ "package=openclaw@${OPENCLAW_VERSION}" \ "integrity=${EXPECTED_INTEGRITY}" \ "tarball=${EXPECTED_TARBALL}" \ - 'recipe=ignore-scripts+reviewed-lifecycle-v1' \ + "lock-sha256=${OPENCLAW_LOCK_SHA256}" \ + "recipe=${OPENCLAW_INSTALL_RECIPE}" \ "mcporter-package=mcporter@${MCPORTER_VERSION}" \ "mcporter-integrity=${MCPORTER_EXPECTED_INTEGRITY}" \ "mcporter-tarball=${MCPORTER_EXPECTED_TARBALL}" \ diff --git a/agents/openclaw/dependency-review.md b/agents/openclaw/dependency-review.md index a7ff15c252f..515d40fe2b0 100644 --- a/agents/openclaw/dependency-review.md +++ b/agents/openclaw/dependency-review.md @@ -1,9 +1,26 @@ -# OpenClaw MCP Runtime Dependency Review +# OpenClaw Runtime Dependency Review -This file records the reviewed `mcporter` baseline installed in the OpenClaw sandbox image. +## OpenClaw production runtime graph + +- Package: `openclaw@2026.6.10`. +- Registry source: `https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz`. +- npm integrity: `sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug==`. +- NemoClaw lock: `agents/openclaw/openclaw-runtime/package-lock.json` (npm lockfile version 3, 306 package entries). +- Lock SHA-256: `a0f91c7e0b769e73c3f6119b2a6ee2dfd9bcb32b3dc69655b22696c654694d2d`. +- Deterministic regeneration: `cd agents/openclaw/openclaw-runtime && npx --yes npm@10.9.4 install --package-lock-only --ignore-scripts --omit=dev --no-audit --no-fund --registry=https://registry.npmjs.org/ --userconfig=/dev/null`. +- Installation boundary: both current production Docker paths validate the lock digest, exact root version/SRI/tarball, official registry origin, and sha512 metadata for every transitive entry before `npm ci --ignore-scripts --omit=dev` consumes that lock. They then bind every installed non-optional package location to the lock's exact manifest name and version, reject symlinked package roots or manifests, invoke the reviewed bundled-plugin postinstall, and only then expose the dedicated runtime through the canonical global package and binary symlinks. +- Provenance: `openclaw-base-provenance-v1` schema 3 records the lock SHA-256 and `locked-ci+reviewed-lifecycle-v2` recipe. A final image reuses a base install only when the protected marker, installed OpenClaw version, installed mcporter version, and both lock identities match. +- Default audit: `scripts/audit-reviewed-npm-graph.mts` validates and installs this same lock under Node `22.22.2`, verifies the installed manifest identities, runs `npm audit --omit=dev --json`, uploads the raw report, and fails at the threshold in `ci/reviewed-npm-audit.json`. +- Regression: `test/openclaw-locked-install.test.ts` rejects lock-byte, root-version, integrity, missing-transitive-SRI, registry-origin, installed-manifest, and symlink drift and keeps both Docker install paths, CI audit ownership, base-image rebuild triggers, and provenance synchronized. The integrity-pin suite injects `npm ci` and reviewed lifecycle failures and proves neither runtime symlinks nor base provenance are published. + +The lock is a NemoClaw-owned review artifact materialized from the pinned official npm package. The package-internal shrinkwrap is upstream evidence, but production installation and CI audit consume the committed NemoClaw lock rather than independently resolving the transitive graph. + +## mcporter runtime graph + +This section records the reviewed `mcporter` baseline installed in the OpenClaw sandbox image. Update it and `agents/openclaw/mcporter-runtime/package*.json` together whenever `MCPORTER_VERSION` or its integrity value changes in `Dockerfile.base` or `Dockerfile`. - Package: `mcporter@0.7.3` diff --git a/agents/openclaw/openclaw-runtime/package-lock.json b/agents/openclaw/openclaw-runtime/package-lock.json new file mode 100644 index 00000000000..a4aa84e5713 --- /dev/null +++ b/agents/openclaw/openclaw-runtime/package-lock.json @@ -0,0 +1,3586 @@ +{ + "name": "nemoclaw-openclaw-runtime", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nemoclaw-openclaw-runtime", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "openclaw": "2026.6.10" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/openclaw": { + "version": "2026.6.10", + "resolved": "https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz", + "integrity": "sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug==", + "hasInstallScript": true, + "hasShrinkwrap": true, + "license": "MIT", + "dependencies": { + "@agentclientprotocol/sdk": "0.22.1", + "@anthropic-ai/sdk": "0.100.1", + "@clack/core": "1.3.1", + "@clack/prompts": "1.4.0", + "@earendil-works/pi-tui": "0.78.0", + "@google/genai": "2.7.0", + "@grammyjs/runner": "2.0.3", + "@grammyjs/transformer-throttler": "1.2.1", + "@homebridge/ciao": "1.3.9", + "@lydell/node-pty": "1.2.0-beta.12", + "@mistralai/mistralai": "2.2.5", + "@modelcontextprotocol/sdk": "1.29.0", + "@mozilla/readability": "0.6.0", + "@openclaw/fs-safe": "0.3.0", + "@openclaw/proxyline": "0.3.3", + "chalk": "5.6.2", + "chokidar": "5.0.0", + "clawpdf": "0.3.0", + "commander": "14.0.3", + "croner": "10.0.1", + "diff": "9.0.0", + "dotenv": "17.4.2", + "express": "5.2.1", + "file-type": "22.0.1", + "glob": "13.0.6", + "grammy": "1.43.0", + "highlight.js": "11.11.1", + "hosted-git-info": "10.1.1", + "ignore": "7.0.5", + "jiti": "2.7.0", + "json5": "2.2.3", + "jszip": "3.10.1", + "kysely": "0.29.2", + "linkedom": "0.18.12", + "minimatch": "10.2.5", + "node-edge-tts": "1.2.10", + "openai": "6.39.1", + "partial-json": "0.1.7", + "playwright-core": "1.60.0", + "proper-lockfile": "4.1.2", + "qrcode": "1.5.4", + "quickjs-wasi": "3.0.0", + "rastermill": "0.3.1", + "tar": "7.5.16", + "tree-sitter-bash": "0.25.1", + "tslog": "4.10.2", + "typebox": "1.1.39", + "typescript": "6.0.3", + "undici": "8.5.0", + "web-push": "3.6.7", + "web-tree-sitter": "0.26.9", + "ws": "8.21.0", + "yaml": "2.9.0", + "zod": "4.4.3" + }, + "bin": { + "openclaw": "openclaw.mjs" + }, + "engines": { + "node": ">=22.19.0" + }, + "optionalDependencies": { + "sqlite-vec": "0.1.9" + } + }, + "node_modules/openclaw/node_modules/@agentclientprotocol/sdk": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.22.1.tgz", + "integrity": "sha512-DfqXtl/8gO9NImq094MTaCXEU2vkhh6v7q/kT+9UjZxUqj8hYaya2OjLVIqn16MzNHcXEpShTR2RIauLSYeDQQ==", + "license": "Apache-2.0", + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/openclaw/node_modules/@anthropic-ai/sdk": { + "version": "0.100.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.100.1.tgz", + "integrity": "sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/openclaw/node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/openclaw/node_modules/@clack/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.3.1.tgz", + "integrity": "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/openclaw/node_modules/@clack/prompts": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.4.0.tgz", + "integrity": "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.3.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/openclaw/node_modules/@earendil-works/pi-tui": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.78.0.tgz", + "integrity": "sha512-3a705FnsVVUhAyceShNB3kS2rpxcxLcx+hqB0u6MMMpHwQGbW+m++MqA6r7eOzq/8FLx5e3vDh38h/SVTk2qzw==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "1.6.0", + "marked": "15.0.12" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/openclaw/node_modules/@google/genai": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.7.0.tgz", + "integrity": "sha512-tv0DRtcndt2oEhBYy+5mA0TaXH98+L1Gt0AP9unBfH7DP20KhB7+O3QqAN1Lz+laMARGTHS7BFQSNpLbl4gm1g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/@grammyjs/runner": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@grammyjs/runner/-/runner-2.0.3.tgz", + "integrity": "sha512-nckmTs1dPWfVQteK9cxqxzE+0m1VRvluLWB8UgFzsjg62w3qthPJt0TYtJBEdG7OedvfQq4vnFAyE6iaMkR42A==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0" + }, + "engines": { + "node": ">=12.20.0 || >=14.13.1" + }, + "peerDependencies": { + "grammy": "^1.13.1" + } + }, + "node_modules/openclaw/node_modules/@grammyjs/transformer-throttler": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@grammyjs/transformer-throttler/-/transformer-throttler-1.2.1.tgz", + "integrity": "sha512-CpWB0F3rJdUiKsq7826QhQsxbZi4wqfz1ccKX+fr+AOC+o8K7ZvS+wqX0suSu1QCsyUq2MDpNiKhyL2ZOJUS4w==", + "license": "MIT", + "dependencies": { + "bottleneck": "^2.0.0" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + }, + "peerDependencies": { + "grammy": "^1.0.0" + } + }, + "node_modules/openclaw/node_modules/@grammyjs/types": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", + "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/@homebridge/ciao": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.3.9.tgz", + "integrity": "sha512-TMy9zy173jDOpnFXDqL3BPIQn5lfcAkSsivYQatCCakoHk4fLGd7QjfAaNGYE3Ox+/ZI6Lq0e1gGcz1qdw/IbA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "fast-deep-equal": "^3.1.3", + "source-map-support": "^0.5.21", + "tslib": "^2.8.1" + }, + "bin": { + "ciao-bcs": "lib/bonjour-conformance-testing.js" + } + }, + "node_modules/openclaw/node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/openclaw/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/openclaw/node_modules/@lydell/node-pty": { + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/@lydell/node-pty/-/node-pty-1.2.0-beta.12.tgz", + "integrity": "sha512-qIK890UwPupoj07osVvgOIa++1mxeHbcGry4PKRHhNVNs81V2SCG34eJr46GybiOmBtc8Sj5PB1/GGM5PL549g==", + "license": "MIT", + "optionalDependencies": { + "@lydell/node-pty-darwin-arm64": "1.2.0-beta.12", + "@lydell/node-pty-darwin-x64": "1.2.0-beta.12", + "@lydell/node-pty-linux-arm64": "1.2.0-beta.12", + "@lydell/node-pty-linux-x64": "1.2.0-beta.12", + "@lydell/node-pty-win32-arm64": "1.2.0-beta.12", + "@lydell/node-pty-win32-x64": "1.2.0-beta.12" + } + }, + "node_modules/openclaw/node_modules/@lydell/node-pty-darwin-arm64": { + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-arm64/-/node-pty-darwin-arm64-1.2.0-beta.12.tgz", + "integrity": "sha512-tqaifcY9Cr41SblO1+FLzh8oxxtkNhuW9Dhl22lKme9BreYvKvxEZcdPIXTuqkJc5tagOEC4QHShKmJjLyLXLQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/openclaw/node_modules/@lydell/node-pty-darwin-x64": { + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-x64/-/node-pty-darwin-x64-1.2.0-beta.12.tgz", + "integrity": "sha512-4LrS5pCJwqHKDVf1zS2gyNV0m4hKAXch+XZNhbZ6LY8uwVL8BhchzQBO40Os5anuRxRCWzHpw4Sp64Ie8q7E4Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/openclaw/node_modules/@lydell/node-pty-linux-arm64": { + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-arm64/-/node-pty-linux-arm64-1.2.0-beta.12.tgz", + "integrity": "sha512-Sx+A71x5BDGHt9ansfrtGxwq2VFVDWvJUAdlUL0Hv0qeiJUfts+hgopx+CgT4PSwahKjdEgtu0+FAfY9rICKRw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/openclaw/node_modules/@lydell/node-pty-linux-x64": { + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-x64/-/node-pty-linux-x64-1.2.0-beta.12.tgz", + "integrity": "sha512-bJzs94njofYhGg/UDqW1nj0dtvvu+2OvxMY+RlLS1T17VgcktKoIR6PuenTwE5HJ/D6StCPADmXcT0nNsCKmIQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/openclaw/node_modules/@lydell/node-pty-win32-arm64": { + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-arm64/-/node-pty-win32-arm64-1.2.0-beta.12.tgz", + "integrity": "sha512-p7POgjVEiFaBC3/y+AKuV1FzePCsJ6HmZDv2XK+jBZSfwP8+uBAw181ZiKYN1YuRa/XpmBGaWezcI8hZkbW++g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/openclaw/node_modules/@lydell/node-pty-win32-x64": { + "version": "1.2.0-beta.12", + "resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-x64/-/node-pty-win32-x64-1.2.0-beta.12.tgz", + "integrity": "sha512-IDFa00g7qUDGUYgByrUBJtC+mOjYVt/8KYyWivCg5JjGOHbBUACUQZLl0jTWmnr+tld/UyTpX90a2PY6oTVtRw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/openclaw/node_modules/@mistralai/mistralai": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.5.tgz", + "integrity": "sha512-ATbWzKkNzNAZ+gtw9MI/c/ULTMG80tKUiRNIbQFfg4OP0uEZZpTfXZeBCNfs5Dq0uqMQ/tQWc4o6RRJQtMrpDA==", + "license": "Apache-2.0", + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + } + }, + "node_modules/openclaw/node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/openclaw/node_modules/@mozilla/readability": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz", + "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/openclaw/node_modules/@openclaw/fs-safe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.3.0.tgz", + "integrity": "sha512-uIBE441CIt1kIURoP9qRGKZ8LkGyfD9ZzeESjwAd29ZPWtghws/5GR3Pjb67jKdcJHP1I6roNXcvnhzAU7lHlA==", + "license": "MIT", + "engines": { + "node": ">=20.11" + }, + "optionalDependencies": { + "jszip": "^3.10.1", + "tar": "7.5.13" + } + }, + "node_modules/openclaw/node_modules/@openclaw/proxyline": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@openclaw/proxyline/-/proxyline-0.3.3.tgz", + "integrity": "sha512-sftHnW69NHQqLjCxBTvQ8f/eQl+peZ5pHCBQtuTWBbeuYRHZ0/GXVTmw/O/YKsShMbqPWhJB0UYtPPdvCUSS8w==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "undici": ">=8.3.0 <9" + } + }, + "node_modules/openclaw/node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/openclaw/node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "license": "Apache-2.0" + }, + "node_modules/openclaw/node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/openclaw/node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/openclaw/node_modules/@types/retry": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", + "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/openclaw/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/openclaw/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/openclaw/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/openclaw/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/openclaw/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/openclaw/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/openclaw/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/openclaw/node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/openclaw/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/openclaw/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/openclaw/node_modules/clawpdf": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/clawpdf/-/clawpdf-0.3.0.tgz", + "integrity": "sha512-41+3AnKk9yek2sm+/9XvUlDTN8Wi+ag7fmxZuqw+ySn4lqaf/fCgLeamqPLiXY4gVbizKEHGoTG/JrIIFNE2rw==", + "license": "MIT", + "bin": { + "clawpdf": "dist/cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/openclaw/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/openclaw/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/openclaw/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/openclaw/node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/openclaw/node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/croner": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/croner/-/croner-10.0.1.tgz", + "integrity": "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==", + "funding": [ + { + "type": "other", + "url": "https://paypal.me/hexagonpp" + }, + { + "type": "github", + "url": "https://github.com/sponsors/hexagon" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/openclaw/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/openclaw/node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/openclaw/node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/openclaw/node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/openclaw/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/openclaw/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/openclaw/node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/openclaw/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/openclaw/node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/openclaw/node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/openclaw/node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/openclaw/node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/openclaw/node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/openclaw/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/openclaw/node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, + "node_modules/openclaw/node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/openclaw/node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/openclaw/node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/openclaw/node_modules/file-type": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-22.0.1.tgz", + "integrity": "sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.5", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/openclaw/node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/gaxios": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.5.tgz", + "integrity": "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/openclaw/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/openclaw/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/openclaw/node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openclaw/node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/openclaw/node_modules/google-auth-library": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.7.0.tgz", + "integrity": "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/openclaw/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/openclaw/node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/grammy": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.43.0.tgz", + "integrity": "sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==", + "license": "MIT", + "dependencies": { + "@grammyjs/types": "3.27.3", + "abort-controller": "^3.0.0", + "debug": "^4.4.3", + "node-fetch": "^2.7.0" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + } + }, + "node_modules/openclaw/node_modules/grammy/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/openclaw/node_modules/hono": { + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/openclaw/node_modules/hosted-git-info": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-10.1.1.tgz", + "integrity": "sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==", + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/openclaw/node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/openclaw/node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/http_ece": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz", + "integrity": "sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/openclaw/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/openclaw/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/openclaw/node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/openclaw/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/openclaw/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/openclaw/node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openclaw/node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/openclaw/node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/openclaw/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/openclaw/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/openclaw/node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/openclaw/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/openclaw/node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/openclaw/node_modules/kysely": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.2.tgz", + "integrity": "sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==", + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/openclaw/node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/openclaw/node_modules/linkedom": { + "version": "0.18.12", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.12.tgz", + "integrity": "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==", + "license": "ISC", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^10.0.0", + "uhyphen": "^0.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": ">= 2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/openclaw/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/openclaw/node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/openclaw/node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/openclaw/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openclaw/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/openclaw/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/openclaw/node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/openclaw/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/node-addon-api": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", + "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/openclaw/node_modules/node-domexception": { + "name": "@nolyfill/domexception", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@nolyfill/domexception/-/domexception-1.0.28.tgz", + "integrity": "sha512-tlc/FcYIv5i8RYsl2iDil4A0gOihaas1R5jPcIC4Zw3GhjKsVilw90aHcVlhZPTBLGBzd379S+VcnsDjd9ChiA==", + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/openclaw/node_modules/node-edge-tts": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/node-edge-tts/-/node-edge-tts-1.2.10.tgz", + "integrity": "sha512-bV2i4XU54D45+US0Zm1HcJRkifuB3W438dWyuJEHLQdKxnuqlI1kim2MOvR6Q3XUQZvfF9PoDyR1Rt7aeXhPdQ==", + "license": "MIT", + "dependencies": { + "https-proxy-agent": "^7.0.1", + "ws": "^8.13.0", + "yargs": "^17.7.2" + }, + "bin": { + "node-edge-tts": "bin.js" + } + }, + "node_modules/openclaw/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/openclaw/node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/openclaw/node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/openclaw/node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openclaw/node_modules/openai": { + "version": "6.39.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.39.1.tgz", + "integrity": "sha512-z3dO9fEWOXBzlXynVb/xZ/tujzUjFWQWn3C0n0mw6Vo0zJTbEkaN4b2cLWjhJ6haJQx8LlREoafHRl+Gu/Hl+A==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openclaw/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/openclaw/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/openclaw/node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/openclaw/node_modules/path-to-regexp": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", + "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/openclaw/node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/openclaw/node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/openclaw/node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/openclaw/node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/openclaw/node_modules/protobufjs": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz", + "integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/openclaw/node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/openclaw/node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/openclaw/node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/openclaw/node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/openclaw/node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/quickjs-wasi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.0.0.tgz", + "integrity": "sha512-X7ouKC4ZVf9bXQ8rsE7+L6TeBbesejAJH61x16xRaGAQGfBHHRcniWgzJZZVtHc8rS9yVsY+Tvk8/usAosg4bg==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/openclaw/node_modules/rastermill": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/rastermill/-/rastermill-0.3.1.tgz", + "integrity": "sha512-CX4nij6+ZLHYIaojJNfLTr7W+AiH/IPJi6E9Aw1br2///1KZL2KBOHd68rkcLedc47MPvb4hhH+fzYeGFa4A/Q==", + "license": "MIT", + "dependencies": { + "@silvia-odwyer/photon-node": "0.3.4" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/openclaw/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/openclaw/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/openclaw/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/openclaw/node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/openclaw/node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/openclaw/node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/openclaw/node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/openclaw/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/openclaw/node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openclaw/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/openclaw/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/openclaw/node_modules/sqlite-vec": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec/-/sqlite-vec-0.1.9.tgz", + "integrity": "sha512-L7XJWRIBNvR9O5+vh1FQ+IGkh/3D2AzVksW5gdtk28m78Hy8skFD0pqReKH1Yp0/BUKRGcffgKvyO/EON5JXpA==", + "license": "MIT OR Apache", + "optional": true, + "optionalDependencies": { + "sqlite-vec-darwin-arm64": "0.1.9", + "sqlite-vec-darwin-x64": "0.1.9", + "sqlite-vec-linux-arm64": "0.1.9", + "sqlite-vec-linux-x64": "0.1.9", + "sqlite-vec-windows-x64": "0.1.9" + } + }, + "node_modules/openclaw/node_modules/sqlite-vec-darwin-arm64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-arm64/-/sqlite-vec-darwin-arm64-0.1.9.tgz", + "integrity": "sha512-jSsZpE42OfBkGL/ItyJTVCUwl6o6Ka3U5rc4j+UBDIQzC1ulSSKMEhQLthsOnF/MdAf1MuAkYhkdKmmcjaIZQg==", + "cpu": [ + "arm64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/openclaw/node_modules/sqlite-vec-darwin-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-x64/-/sqlite-vec-darwin-x64-0.1.9.tgz", + "integrity": "sha512-KDlVyqQT7pnOhU1ymB9gs7dMbSoVmKHitT+k1/xkjarcX8bBqPxWrGlK/R+C5WmWkfvWwyq5FfXfiBYCBs6PlA==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/openclaw/node_modules/sqlite-vec-linux-arm64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-linux-arm64/-/sqlite-vec-linux-arm64-0.1.9.tgz", + "integrity": "sha512-5wXVJ9c9kR4CHm/wVqXb/R+XUHTdpZ4nWbPHlS+gc9qQFVHs92Km4bPnCKX4rtcPMzvNis+SIzMJR1SCEwpuUw==", + "cpu": [ + "arm64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/openclaw/node_modules/sqlite-vec-linux-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-linux-x64/-/sqlite-vec-linux-x64-0.1.9.tgz", + "integrity": "sha512-w3tCH8xK2finW8fQJ/m8uqKodXUZ9KAuAar2UIhz4BHILfpE0WM/MTGCRfa7RjYbrYim5Luk3guvMOGI7T7JQA==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/openclaw/node_modules/sqlite-vec-windows-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-windows-x64/-/sqlite-vec-windows-x64-0.1.9.tgz", + "integrity": "sha512-y3gEIyy/17bq2QFPQOWLE68TYWcRZkBQVA2XLrTPHNTOp55xJi/BBBmOm40tVMDMjtP+Elpk6UBUXdaq+46b0Q==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/openclaw/node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, + "node_modules/openclaw/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/openclaw/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openclaw/node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/openclaw/node_modules/tar": { + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/openclaw/node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/openclaw/node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/openclaw/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/tree-sitter-bash": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/tree-sitter-bash/-/tree-sitter-bash-0.25.1.tgz", + "integrity": "sha512-7hMytuYIMoXOq24yRulgIxthE9YmggZIOHCyPTTuJcu6EU54tYD+4G39cUb28kxC6jMf/AbPfWGLQtgPTdh3xw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/openclaw/node_modules/tslog": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.10.2.tgz", + "integrity": "sha512-XuELoRpMR+sq8fuWwX7P0bcj+PRNiicOKDEb3fGNURhxWVyykCi9BNq7c4uVz7h7P0sj8qgBsr5SWS6yBClq3g==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/fullstack-build/tslog?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/typebox": { + "version": "1.1.39", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.39.tgz", + "integrity": "sha512-vj0afVtOfLQvv0GR0VxVagYxsXN64btL7Z9XoaG0ZggH3mruMMkOO6hXdgMsjCY3shZgEvooAWVeznQVs5c43w==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/openclaw/node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openclaw/node_modules/undici": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", + "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/openclaw/node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openclaw/node_modules/web-push": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.6.7.tgz", + "integrity": "sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==", + "license": "MPL-2.0", + "dependencies": { + "asn1.js": "^5.3.0", + "http_ece": "1.2.0", + "https-proxy-agent": "^7.0.0", + "jws": "^4.0.0", + "minimist": "^1.2.5" + }, + "bin": { + "web-push": "src/cli.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/openclaw/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/openclaw/node_modules/web-tree-sitter": { + "version": "0.26.9", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.9.tgz", + "integrity": "sha512-YJwSHANl6XFgeEjB8nitgj0qZYt5gkIesJ4w2srS2wcLB4GUa4xcOkM0YaMsU6WNR53YVIkDSY7Ej4pf3IXtCA==", + "license": "MIT" + }, + "node_modules/openclaw/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/openclaw/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/openclaw/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/openclaw/node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/openclaw/node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/openclaw/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/openclaw/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/openclaw/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/openclaw/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/openclaw/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/openclaw/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/openclaw/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/openclaw/node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/agents/openclaw/openclaw-runtime/package.json b/agents/openclaw/openclaw-runtime/package.json new file mode 100644 index 00000000000..933ee5944b8 --- /dev/null +++ b/agents/openclaw/openclaw-runtime/package.json @@ -0,0 +1,14 @@ +{ + "$comment": "SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\nSPDX-License-Identifier: Apache-2.0", + "name": "nemoclaw-openclaw-runtime", + "version": "0.0.0", + "private": true, + "description": "Locked production dependency graph for the OpenClaw runtime", + "license": "Apache-2.0", + "dependencies": { + "openclaw": "2026.6.10" + }, + "engines": { + "node": ">=22.19.0" + } +} diff --git a/ci/reviewed-npm-audit.json b/ci/reviewed-npm-audit.json index 3737c93c26d..322bd237364 100644 --- a/ci/reviewed-npm-audit.json +++ b/ci/reviewed-npm-audit.json @@ -1,15 +1,10 @@ { - "schemaVersion": 1, + "schemaVersion": 2, "nodeVersion": "22.22.2", + "registryOrigin": "https://registry.npmjs.org/", "severityThreshold": "high", "artifactDirectory": "coverage/reviewed-npm-audit", "archivePackages": [ - { - "label": "OpenClaw 2026.6.10", - "packageSpec": "openclaw@2026.6.10", - "integrity": "sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug==", - "tarballUrl": "https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz" - }, { "label": "Codex ACP 0.11.1", "packageSpec": "@zed-industries/codex-acp@0.11.1", @@ -60,11 +55,20 @@ } ], "lockedGraphs": [ + { + "label": "OpenClaw 2026.6.10 locked runtime graph", + "packageSpec": "openclaw@2026.6.10", + "integrity": "sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug==", + "tarballUrl": "https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz", + "lockSha256": "a0f91c7e0b769e73c3f6119b2a6ee2dfd9bcb32b3dc69655b22696c654694d2d", + "directory": "agents/openclaw/openclaw-runtime" + }, { "label": "mcporter 0.7.3 locked runtime graph", "packageSpec": "mcporter@0.7.3", "integrity": "sha512-egoPVYqTnWb3NjRIxo+xc8OrAI0dlPrJm9pAiZx0pImuNIV5rKhGtTnIfH/Y1ldGPVu74ibj3KR5c9U/QSdQFA==", "tarballUrl": "https://registry.npmjs.org/mcporter/-/mcporter-0.7.3.tgz", + "lockSha256": "29e46eb0acd73a0b3ac3f730f7607137cc508fe62a5e890b905c26364a3b8094", "directory": "agents/openclaw/mcporter-runtime" } ] diff --git a/docs/security/openclaw-2026.6.10-dependency-review.md b/docs/security/openclaw-2026.6.10-dependency-review.md index da1d49bb2f2..fdb11c8027f 100644 --- a/docs/security/openclaw-2026.6.10-dependency-review.md +++ b/docs/security/openclaw-2026.6.10-dependency-review.md @@ -19,6 +19,8 @@ Issue #5591 is the dependency-update umbrella, and its proposed design has three - npm package: `openclaw@2026.6.10` - npm tarball: `https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz` - npm integrity: `sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug==` +- NemoClaw production lock: `agents/openclaw/openclaw-runtime/package-lock.json` (306 package entries) +- NemoClaw production lock SHA-256: `a0f91c7e0b769e73c3f6119b2a6ee2dfd9bcb32b3dc69655b22696c654694d2d` - npm publish time: `2026-06-24T03:01:21.544Z` - Codex ACP runtime helper package: `@zed-industries/codex-acp@0.11.1` - Codex ACP runtime helper npm tarball: `https://registry.npmjs.org/@zed-industries/codex-acp/-/codex-acp-0.11.1.tgz` @@ -38,7 +40,7 @@ Issue #5591 is the dependency-update umbrella, and its proposed design has three - WeChat channel plugin package: `@tencent-weixin/openclaw-weixin@2.4.3` - WeChat channel plugin npm integrity: `sha512-dPQbidUNWigC6V10vGW4i+GLH09x+6zUhafZRjuxkJ9GDu8o62WBsnUTojp4KqUH756hz+t2v9khiCRSi0dBDw==` -NemoClaw enforces the main `openclaw@2026.6.10`, `@zed-industries/codex-acp@0.11.1`, and each reviewed npm plugin registry integrity and reviewed registry tarball URL, including optional OTEL/brave plugins and messaging plugins, before install. `scripts/lib/reviewed-npm-archive.mts` is the shared implementation used by the Docker build boundaries and the messaging build applier. It queries registry metadata by exact package spec, packs only the reviewed tarball URL with `npm pack --json`, requires the downloaded tarball integrity to match the committed SRI, rejects reported archive filenames with unsafe archive paths—including values that are absolute, contain path separators, equal `.` or `..`, or resolve outside the fresh pack directory—or files that are missing or are not regular files, and returns only the verified local `.tgz` path. A production image may skip duplicate OpenClaw and locked-mcporter installation only when an official NemoClaw base carries the protected exact provenance marker described below. +NemoClaw enforces the main `openclaw@2026.6.10`, `@zed-industries/codex-acp@0.11.1`, and each reviewed npm plugin registry integrity and reviewed registry tarball URL, including optional OTEL/brave plugins and messaging plugins, before install. The current OpenClaw production path additionally validates the committed lock SHA-256, exact root identity, official registry origin, and sha512 metadata for every transitive entry, installs with `npm ci` from that lock, and then binds each installed package location to the lock's exact manifest name and version before lifecycle execution or runtime exposure. `scripts/lib/reviewed-npm-archive.mts` is the shared implementation used by the Docker build boundaries, graph audit, and messaging build applier. Archive callers query registry metadata by exact package spec, pack only the reviewed tarball URL with `npm pack --json`, require the downloaded tarball integrity to match the committed SRI, reject reported archive filenames with unsafe archive paths or missing files, and install only the verified local `.tgz` path. A production image may skip duplicate OpenClaw and locked-mcporter installation only when an official NemoClaw base carries the protected exact provenance marker described below, including the OpenClaw lock identity. ## Upstream Release Boundary @@ -66,11 +68,11 @@ npm audit --omit=dev --json Revalidated on 2026-07-03: npm audit exited `0` and reported `0` info, `0` low, `0` moderate, `0` high, and `0` critical vulnerabilities across `763` total dependencies. The audit host used Node `22.16.0` and emitted npm `EBADENGINE` warnings for packages that require newer Node `22.x` builds. Production NemoClaw images use the digest-pinned `node:22-trixie-slim` image, which currently runs Node `v22.22.2` and satisfies the `openclaw@2026.6.10` engine requirement of `>=22.19.0`. The audit remains advisory vulnerability evidence for the locked dependency graph; the audit-host warning does not describe the production runtime. -This review is an advisory snapshot for the direct OpenClaw runtime package, Codex ACP runtime helper, optional plugins, messaging plugins, and their npm dependency graphs at review time. Default PR and main CI now rematerialize those exact direct packages from SRI-verified reviewed local archives under Node `22.22.2`, install with lifecycle scripts disabled, run `npm audit --omit=dev --json`, and upload the raw reports from `coverage/reviewed-npm-audit`. The configured threshold in `ci/reviewed-npm-audit.json` is `high`. The same job independently installs and audits the committed mcporter production lock. This gate complements, but does not replace, the committed npm integrity pins and install-time archive checks. +This review is an advisory snapshot for the OpenClaw runtime, Codex ACP runtime helper, optional plugins, messaging plugins, and their npm dependency graphs at review time. Default PR and main CI now rematerialize the reviewed production graphs under Node `22.22.2`: they validate and install the committed OpenClaw and mcporter production locks, verify installed manifest identities for those locks, materialize the remaining exact packages from SRI-verified reviewed local archives, disable lifecycle scripts, run `npm audit --omit=dev --json`, and upload the raw reports from `coverage/reviewed-npm-audit`. The configured threshold in `ci/reviewed-npm-audit.json` is `high`. This gate audits the same OpenClaw graph installed by both production Docker paths. ## Transitive Dependency Graph Rationale -The OpenClaw 2026.6.10 bump does not newly introduce an unfrozen OpenClaw transitive graph. The reviewed `openclaw@2026.6.10` artifact ships `npm-shrinkwrap.json`; the previous reviewed `openclaw@2026.6.9` artifact also shipped `npm-shrinkwrap.json`. A spot check of the reviewed 2026.6.10 package found lockfile version `3`, `306` package entries, and no resolved package entries missing integrity metadata. The reviewed `@openclaw/diagnostics-otel@2026.6.10`, `@openclaw/brave-plugin@2026.6.10`, `@openclaw/discord@2026.6.10`, `@openclaw/slack@2026.6.10`, `@openclaw/whatsapp@2026.6.10`, and `@openclaw/msteams@2026.6.10` artifacts also ship `npm-shrinkwrap.json`. +The reviewed `openclaw@2026.6.10` artifact ships `npm-shrinkwrap.json`; the previous reviewed `openclaw@2026.6.9` artifact also shipped `npm-shrinkwrap.json`. NemoClaw materializes that official pinned artifact into its own review artifact with lockfile version `3`, `306` package entries, and no resolved package entries missing integrity metadata. Both production Dockerfiles install with `npm ci` from that exact closure and verify the resulting non-optional package manifests against their lock locations before exposing OpenClaw; the default audit gate validates and audits the same lock. The reviewed lock digest is repeated in the audit manifest and protected base provenance so version, integrity, registry, lock-byte, installed-identity, or symlink drift fails closed. The reviewed `@openclaw/diagnostics-otel@2026.6.10`, `@openclaw/brave-plugin@2026.6.10`, `@openclaw/discord@2026.6.10`, `@openclaw/slack@2026.6.10`, `@openclaw/whatsapp@2026.6.10`, and `@openclaw/msteams@2026.6.10` artifacts also ship `npm-shrinkwrap.json`. `@zed-industries/codex-acp@0.11.1` has no declared npm dependencies, so the committed package SRI plus reviewed tarball URL fully describes its npm install input for this release. At the time of this review, the only reviewed messaging plugin without a package-internal shrinkwrap was the existing non-OpenClaw Tencent WeChat plugin, `@tencent-weixin/openclaw-weixin@2.4.3`; the dependency bump accepted that transitive range risk while enforcing the top-level SRI and reviewed tarball URL. Current NemoClaw builds close that residual with a committed lock for the WeChat production graph. The image materializes the reviewed graph in a root-owned cache that the sandbox user cannot write, copies it into a disposable writable cache for the offline plugin install, verifies the installed managed graph against the committed lock, and removes the temporary cache in the same image layer. @@ -102,16 +104,16 @@ The SRI-verified `openclaw@2026.6.10` artifact's `package/skills/weather/SKILL.m ### Installer Integrity Transaction Boundary -`Dockerfile`, `Dockerfile.base`, optional OpenClaw plugin installs, and `src/lib/messaging/applier/build/messaging-build-applier.mts` bind reviewed npm installs to verified local archives through `scripts/lib/reviewed-npm-archive.mts`. The thin callers provide the exact package spec, committed SRI, reviewed tarball URL, and caller label. The helper verifies both `npm view` fields, packs the reviewed URL, validates the reported SRI and contained regular-file basename in a fresh directory, and returns the local archive before `npm install -g ` or `openclaw plugins install npm-pack:` runs. Runtime mcporter uses the helper's metadata-only path before retaining its committed-lock `npm ci` transaction. +The current OpenClaw production paths bind installation to `agents/openclaw/openclaw-runtime/package-lock.json`. They verify the lock digest, root package identity, top-level SRI and tarball, official registry origin, and every transitive SHA-512 integrity before `npm ci` consumes the lock. Runtime mcporter uses the same metadata-plus-committed-lock pattern. Codex ACP, optional OpenClaw plugin installs, retained legacy OpenClaw fixtures, and `src/lib/messaging/applier/build/messaging-build-applier.mts` continue to bind reviewed npm installs to verified local archives through `scripts/lib/reviewed-npm-archive.mts`. Those callers provide the exact package spec, committed SRI, reviewed tarball URL, and caller label. The helper verifies both `npm view` fields, packs the reviewed URL, validates the reported SRI and contained regular-file basename in a fresh directory, rejects reported archive filenames with unsafe archive paths or missing files, and returns only the verified local `.tgz` path before `npm install -g` or `openclaw plugins install npm-pack:` runs. -After `Dockerfile.base` completes the OpenClaw archive transaction and reviewed lifecycle, installs mcporter from the committed lock, checks both installed versions, and passes mcporter advisory and signature audits, it atomically publishes a root-owned, read-only provenance marker. The marker binds the OpenClaw package, SRI, tarball, and lifecycle recipe plus the mcporter package, SRI, tarball URL, lockfile SHA-256, and audited-install recipe. The production Dockerfile may reuse both installs only for an official NemoClaw base reference (or the resolver's local base name) when the marker is a non-symlink regular file with exact `root:root` ownership, mode `0444`, byte-for-byte content, and both installed versions match. It removes the marker before applying NemoClaw patches so a derived image cannot claim pristine-base provenance. Missing, malformed, writable, symlinked, mismatched, custom-base, stale, or incomplete provenance takes the complete reviewed install fallback; a base newer than the reviewed OpenClaw target remains a hard failure. +After `Dockerfile.base` completes the OpenClaw and mcporter lock transactions, invokes the reviewed OpenClaw lifecycle, checks both installed versions, and passes mcporter advisory and signature audits, it atomically publishes a root-owned, read-only provenance marker. The marker binds the OpenClaw package, SRI, tarball, lockfile SHA-256, and lifecycle recipe plus the mcporter package, SRI, tarball URL, lockfile SHA-256, and audited-install recipe. The production Dockerfile may reuse both installs only for an official NemoClaw base reference (or the resolver's local base name) when the marker is a non-symlink regular file with exact `root:root` ownership, mode `0444`, byte-for-byte content, and both installed versions match. It removes the marker before applying NemoClaw patches so a derived image cannot claim pristine-base provenance. Missing, malformed, writable, symlinked, mismatched, custom-base, stale, or incomplete provenance takes the complete reviewed install fallback; a base newer than the reviewed OpenClaw target remains a hard failure. -Invalid state: `npm view` returns the reviewed SRI but the downloaded artifact used for install has different bytes; `npm pack --json` reports a filename such as `../package.tgz`, `/tmp/package.tgz`, or a name containing path separators so the later install consumes a path outside the fresh pack directory; or the production image reuses OpenClaw or mcporter without every provenance, metadata, trusted-base, lock-hash, and installed-version check above. Source boundary: Dockerfile npm install and provenance blocks, `Dockerfile.base`, the committed mcporter lock, optional plugin install blocks, and `src/lib/messaging/applier/build/messaging-build-applier.mts`. Source-fix constraint: npm package installation must stay artifact-bound for reviewed pins rather than reverting to a later floating package-spec transaction, and local archive path validation must be enforced at NemoClaw's install boundary because npm's JSON filename is untrusted input. Regression tests: the integrity-pin plugin-install suite exercises registry drift, reviewed tarball URL drift, downloaded archive verification, and reviewed local-archive installation; the integrity-pin base suite exercises unsafe reported archive filenames, exact OpenClaw/mcporter provenance reuse, fifteen fallback states, marker consumption, and newer-base rejection. `test/messaging-build-applier.test.ts` verifies messaging plugins run through `npm pack --json` and install the verified archive path; `test/messaging-build-applier-integrity.test.ts` verifies the messaging plugin install fails closed when packed archive integrity drifts or the reported archive filename escapes the pack directory. Removal condition: keep this archive verification and delegated-base provenance until the repo moves the OpenClaw/plugin dependency set to a lockfile path where npm enforces the committed SRI directly and no installer code consumes raw `npm pack --json` filenames. +Invalid state: the current OpenClaw graph changes without a reviewed lock digest update; `npm view` returns the reviewed SRI but an archive consumer downloads different bytes; `npm pack --json` reports a filename such as `../package.tgz`, `/tmp/package.tgz`, or a name containing path separators so a later archive install consumes a path outside the fresh pack directory; or the production image reuses OpenClaw or mcporter without every provenance, metadata, trusted-base, lock-hash, and installed-version check above. Source boundary: Dockerfile npm install and provenance blocks, `Dockerfile.base`, the committed OpenClaw and mcporter locks, optional plugin install blocks, and `src/lib/messaging/applier/build/messaging-build-applier.mts`. Source-fix constraint: npm package installation must stay lock- or artifact-bound for reviewed pins rather than reverting to a later floating package-spec transaction, and local archive path validation must be enforced for remaining archive consumers because npm's JSON filename is untrusted input. Regression tests: `test/openclaw-locked-install.test.ts` exercises lock-byte, version, SRI, registry-origin, Docker-consumer, provenance, and CI ownership drift; the integrity-pin plugin-install suite exercises registry drift, reviewed tarball URL drift, downloaded archive verification, and reviewed local-archive installation; the integrity-pin base suite exercises unsafe reported archive filenames, exact OpenClaw/mcporter provenance reuse, fifteen fallback states, marker consumption, and newer-base rejection. `test/messaging-build-applier.test.ts` verifies messaging plugins run through `npm pack --json` and install the verified archive path; `test/messaging-build-applier-integrity.test.ts` verifies the messaging plugin install fails closed when packed archive integrity drifts or the reported archive filename escapes the pack directory. Removal condition: keep archive verification until every remaining archive consumer moves to a committed lock or another equivalently immutable artifact transaction; keep delegated-base provenance while the final image can reuse a reviewed base install. #### Reviewed npm Lifecycle Boundary Every reviewed archive install now suppresses npm lifecycle scripts. -The Codex ACP and OpenClaw core `npm install -g` transactions pass `--ignore-scripts`; optional and messaging plugin calls set both `NPM_CONFIG_IGNORE_SCRIPTS=true` and `npm_config_ignore_scripts=true` before invoking the SRI-reviewed OpenClaw plugin installer. +The current OpenClaw `npm ci`, Codex ACP archive install, and retained legacy OpenClaw fixture installs pass `--ignore-scripts`; optional and messaging plugin calls set both `NPM_CONFIG_IGNORE_SCRIPTS=true` and `npm_config_ignore_scripts=true` before invoking the SRI-reviewed OpenClaw plugin installer. The first-party local `openclaw plugins install /opt/nemoclaw` boundary receives the same environment even though its source is the image's checked-in NemoClaw tree rather than a registry archive. The reviewed `openclaw@2026.6.10` plugin installer also builds its internal npm command with `--ignore-scripts`, so the outer environment is a caller-owned fail-closed contract rather than the only protection. @@ -282,9 +284,9 @@ No real Microsoft Teams tenant proof is included in this PR. The work remains tr - The #4434 compatibility-shim disposition is explicitly accepted for this OpenClaw 2026.6.10 PR only: `test/issue-4434-error-fields.test.ts` verifies 3/3 fields are present in the NemoClaw-patched runtime output and 3/3 fields are missing in the upstream-shaped `openclaw@2026.6.10` output. On the next OpenClaw bump that emits equivalent fields upstream, remove `scripts/patch-openclaw-issue-4434-diagnostics.mts` in the same change and keep the full live assertions. - The assembled-image and rebuilt-sandbox proof residual is explicitly accepted for this OpenClaw 2026.6.10 dependency bump only. The checked-in real-distribution harness binds the SRI-verified package to every reviewed patch and audit marker; production image workflows run the build-argument guard before assembling the final images; `network-policy` exercises the resulting OpenShell policy; and `messaging-providers`, `hermes-discord`, `channels-add-remove`, and both `channels-stop-start` variants exercise rebuilt sandboxes, installed messaging runtimes, and keyless registered-provider reuse without credential replacement. No single lane combines the final production image, a live `host.openshell.internal` SSRF-negative matrix, and every keyless custom-provider rebuild, so a cross-boundary packaging or wiring regression remains possible even though each boundary fails closed independently. Do not describe this as one combined end-to-end proof. Remove this acceptance when the canonical E2E matrix gains that assembled-image cross-product, or re-evaluate it on the next OpenClaw bump before retaining the same split proof. - The literal issue #2478 Local Ollama plus Telegram inbound recovery residual is explicitly accepted for this OpenClaw 2026.6.10 dependency bump only. `issue-2478-crash-loop-recovery` proves repeated gateway kill/respawn, guard-chain restoration, `inference.local` availability, and soak stability through a hermetic compatible endpoint; `messaging-providers` separately imports the installed Telegram `runtime-api.js`, sends through `sendMessageTelegram`, and verifies token rewrite plus fake Bot API capture. This does not reproduce `nemotron-3-super:120b` on Local Ollama or originate a Telegram inbound update after the crash, so agent/channel-specific inbound restart behavior remains a residual rather than proven equivalence. Do not claim the literal deployment scenario from these split lanes. Remove this acceptance when a stable CI fixture drives a Telegram inbound update through the recovered Local Ollama sandbox, or re-evaluate it on the next OpenClaw bump. -- The transitive npm graph warning was accepted for this dependency bump because the reviewed OpenClaw runtime and `@openclaw/*` plugin artifacts ship package-internal `npm-shrinkwrap.json` files with integrity metadata, `@zed-industries/codex-acp@0.11.1` has no npm dependency tree, and the pre-existing Tencent WeChat package had only its top-level SRI enforced. Current NemoClaw closes the WeChat residual with `agents/openclaw/wechat-runtime/package-lock.json`, an offline install from the reviewed graph, and post-install verification of package versions, integrity metadata, the dependency set, and compatibility between the image's OpenClaw version and the plugin's locked peer range. +- The OpenClaw transitive npm graph residual is closed by `agents/openclaw/openclaw-runtime/package-lock.json`: production Docker and default CI consume the same digest-bound closure, while schema-3 base provenance records its identity. Current NemoClaw closes the WeChat residual separately with `agents/openclaw/wechat-runtime/package-lock.json`, an offline install from the reviewed graph, and post-install verification of package versions, integrity metadata, the dependency set, and compatibility between the image's OpenClaw version and the plugin's locked peer range. - `src/lib/messaging/channels/manifests.test.ts` remains below the shared `test-size:check` threshold and does not need extraction in this dependency bump. -- The npm audit result in this note remains a point-in-time snapshot. Default PR and main CI rematerialize the production-compatible graph from the reviewed local archives, audit it and the committed mcporter lock with `npm audit --omit=dev --json`, upload both raw reports, and fail at the configured `high` threshold. The separate `wechat-runtime-audit` gate uses Node `22.19.0` and npm `10.9.4`, installs the committed WeChat production lock with scripts disabled, fails on any low-or-higher production advisory, verifies registry signatures, exercises the reviewed archive through a copied writable cache, and uploads its evidence. Pull requests execute that WeChat audit action from the base SHA; because PR #6739's base predates the action, that PR alone may bootstrap it from signed immutable commit `HOYALIM/NemoClaw@0d2256d71d5bbba3bcaaaa4d01714fa56f22d1e2`, while every other PR fails closed if its base lacks the action. The production installer routes registry metadata lookup, archive packing, and installation through the disposable writable-cache boundary so retrieval cannot fall back to `HOME/.npm`; the trusted source cache remains read-only and the disposable copy is removed in the same image layer. +- The npm audit result in this note remains a point-in-time snapshot. Default PR and main CI install and audit the committed OpenClaw and mcporter locks plus the remaining reviewed local-archive graph with `npm audit --omit=dev --json`, upload all raw reports, and fail at the configured `high` threshold. The separate `wechat-runtime-audit` gate uses Node `22.19.0` and npm `10.9.4`, installs the committed WeChat production lock with scripts disabled, fails on any low-or-higher production advisory, verifies registry signatures, exercises the reviewed archive through a copied writable cache, and uploads its evidence. Pull requests execute that WeChat audit action from the base SHA; because PR #6739's base predates the action, that PR alone may bootstrap it from signed immutable commit `HOYALIM/NemoClaw@0d2256d71d5bbba3bcaaaa4d01714fa56f22d1e2`, while every other PR fails closed if its base lacks the action. The production installer routes registry metadata lookup, archive packing, and installation through the disposable writable-cache boundary so retrieval cannot fall back to `HOME/.npm`; the trusted source cache remains read-only and the disposable copy is removed in the same image layer. - The stale nonterminal rebuild-resume repair in `src/lib/actions/sandbox/rebuild-resume-session.ts` remains a migration compatibility shim tracked against #4533's onboard FSM/resume compatibility boundary. Its removal condition is to delete it after a session-version migration proves recreate sessions are always persisted at a resumable pre-sandbox boundary; `src/lib/actions/sandbox/rebuild-resume-session.test.ts` covers the helper directly, `test/onboard-resume-provider-recovery.test.ts` carries the onboard-suite producer-level regression for `machine.state='openclaw'`, and `src/lib/actions/sandbox/rebuild-resume-snapshot.test.ts` owns the rebuild handoff regression. - Production OpenClaw image build paths call `scripts/check-production-build-args.sh` before production `docker build` or `docker/build-push-action` use. `test/openclaw-dependency-review.test.ts` keeps that workflow contract documented. - The rebuild-reasoning cases added by this PR live in the focused `rebuild-resume-reasoning.test.ts` file; the smaller route-provenance additions remain with their `rebuild-resume-config.ts` boundary tests. diff --git a/scripts/audit-reviewed-npm-graph.mts b/scripts/audit-reviewed-npm-graph.mts index 83d43f7a3ce..f2f3a66d00f 100755 --- a/scripts/audit-reviewed-npm-graph.mts +++ b/scripts/audit-reviewed-npm-graph.mts @@ -7,7 +7,11 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; -import { packReviewedNpmArchive, verifyReviewedNpmMetadata } from "./lib/reviewed-npm-archive.mts"; +import { + packReviewedNpmArchive, + verifyInstalledNpmLock, + verifyReviewedNpmLock, +} from "./lib/reviewed-npm-archive.mts"; type Severity = "info" | "low" | "moderate" | "high" | "critical"; type ReviewedPackage = Readonly<{ @@ -16,20 +20,35 @@ type ReviewedPackage = Readonly<{ packageSpec: string; tarballUrl: string; }>; -type LockedGraph = ReviewedPackage & Readonly<{ directory: string }>; +type LockedGraph = ReviewedPackage & Readonly<{ directory: string; lockSha256: string }>; type AuditConfig = Readonly<{ archivePackages: readonly ReviewedPackage[]; artifactDirectory: string; lockedGraphs: readonly LockedGraph[]; nodeVersion: string; - schemaVersion: 1; + registryOrigin: string; + schemaVersion: 2; severityThreshold: Severity; }>; -const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const TRUSTED_REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const REPO_ROOT = path.resolve( + process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT ?? TRUSTED_REPO_ROOT, +); const CONFIG_PATH = path.join(REPO_ROOT, "ci", "reviewed-npm-audit.json"); const SEVERITIES: readonly Severity[] = ["info", "low", "moderate", "high", "critical"]; +function resolveTargetPath(relativePath: string, label: string): string { + if (!relativePath || path.isAbsolute(relativePath)) { + throw new Error(`${label} must be a nonempty target-relative path`); + } + const resolved = path.resolve(REPO_ROOT, relativePath); + if (!resolved.startsWith(`${REPO_ROOT}${path.sep}`)) { + throw new Error(`${label} escapes the target root: ${relativePath}`); + } + return resolved; +} + function run(command: string, args: readonly string[], cwd: string, allowAuditFindings = false) { const result = spawnSync(command, args, { cwd, @@ -48,7 +67,8 @@ function run(command: string, args: readonly string[], cwd: string, allowAuditFi function readConfig(): AuditConfig { const parsed = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")) as AuditConfig; if ( - parsed.schemaVersion !== 1 || + parsed.schemaVersion !== 2 || + typeof parsed.registryOrigin !== "string" || !SEVERITIES.includes(parsed.severityThreshold) || !Array.isArray(parsed.archivePackages) || !Array.isArray(parsed.lockedGraphs) @@ -159,20 +179,33 @@ function materializeArchiveGraph(packages: readonly ReviewedPackage[], tempRoot: return graphDirectory; } -function materializeLockedGraph(graph: LockedGraph, tempRoot: string): string { - verifyReviewedNpmMetadata({ +function materializeLockedGraph( + graph: LockedGraph, + tempRoot: string, + registryOrigin: string, +): string { + const source = resolveTargetPath(graph.directory, `${graph.label} directory`); + verifyReviewedNpmLock({ expectedIntegrity: graph.integrity, + expectedLockSha256: graph.lockSha256, label: graph.label, + lockfilePath: path.join(source, "package-lock.json"), packageSpec: graph.packageSpec, + registryOrigin, tarballUrl: graph.tarballUrl, }); - const source = path.join(REPO_ROOT, graph.directory); const destination = path.join(tempRoot, `locked-${path.basename(graph.directory)}`); fs.mkdirSync(destination); for (const filename of ["package.json", "package-lock.json"]) { fs.copyFileSync(path.join(source, filename), path.join(destination, filename)); } run("npm", ["ci", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund"], destination); + verifyInstalledNpmLock({ + expectedLockSha256: graph.lockSha256, + installRoot: destination, + label: graph.label, + lockfilePath: path.join(destination, "package-lock.json"), + }); return destination; } @@ -182,7 +215,10 @@ function main(): void { if (process.version !== expectedNode) { throw new Error(`reviewed npm audit requires Node ${expectedNode}; running ${process.version}`); } - const artifactDirectory = path.join(REPO_ROOT, config.artifactDirectory); + const artifactDirectory = resolveTargetPath( + process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_REPORT_DIR ?? config.artifactDirectory, + "reviewed npm audit report directory", + ); fs.rmSync(artifactDirectory, { recursive: true, force: true }); fs.mkdirSync(artifactDirectory, { recursive: true }); const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-reviewed-npm-audit-")); @@ -198,7 +234,7 @@ function main(): void { ...config.lockedGraphs.map((graph, index) => ({ label: graph.label, report: auditGraph( - materializeLockedGraph(graph, tempRoot), + materializeLockedGraph(graph, tempRoot, config.registryOrigin), path.join(artifactDirectory, `locked-graph-${index + 1}.json`), ), })), diff --git a/scripts/lib/reviewed-npm-archive.mts b/scripts/lib/reviewed-npm-archive.mts index 88a18687ff1..ad66dd39ec7 100755 --- a/scripts/lib/reviewed-npm-archive.mts +++ b/scripts/lib/reviewed-npm-archive.mts @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; import { existsSync, lstatSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { isAbsolute, join, resolve, sep } from "node:path"; @@ -31,6 +32,20 @@ export type ReviewedNpmCacheRequest = Readonly<{ tempDirectory?: string; }>; +export type ReviewedNpmLockRequest = ReviewedNpmArchiveRequest & + Readonly<{ + expectedLockSha256: string; + lockfilePath: string; + registryOrigin: string; + }>; + +export type ReviewedInstalledNpmLockRequest = Readonly<{ + expectedLockSha256: string; + installRoot: string; + label: string; + lockfilePath: string; +}>; + export type ReviewedNpmMetadata = Readonly<{ integrity: string; tarballUrl: string; @@ -196,10 +211,7 @@ function normalizeRegistryOrigin(value: string): string { return parsed.origin; } -function readReviewedLockPackages( - lockfilePath: string, - registryOrigin: string, -): readonly ReviewedNpmArchiveRequest[] { +function readReviewedLock(lockfilePath: string): Record> { let lock: unknown; try { lock = JSON.parse(readFileSync(lockfilePath, "utf-8")); @@ -211,32 +223,66 @@ function readReviewedLockPackages( } const lockRecord = lock as Record; if (lockRecord.lockfileVersion !== 3) { - throw new Error("reviewed npm cache requires lockfileVersion 3"); + throw new Error("reviewed npm lock requires lockfileVersion 3"); } const packages = lockRecord.packages; if (typeof packages !== "object" || packages === null || Array.isArray(packages)) { throw new Error("reviewed npm lockfile is missing its packages map"); } + return packages as Record>; +} + +function parseExactPackageSpec(packageSpec: string): { name: string; version: string } { + if (!EXACT_NPM_PACKAGE_SPEC.test(packageSpec)) { + throw new Error(`reviewed npm lock must use an exact npm package spec: ${packageSpec}`); + } + const separator = packageSpec.lastIndexOf("@"); + return { name: packageSpec.slice(0, separator), version: packageSpec.slice(separator + 1) }; +} +function packageNameFromLockLocation(location: string): string { + const marker = "node_modules/"; + const nestedMarkerIndex = location.lastIndexOf(`/${marker}`); + const markerIndex = + nestedMarkerIndex >= 0 ? nestedMarkerIndex + 1 : location.startsWith(marker) ? 0 : -1; + const packageName = markerIndex >= 0 ? location.slice(markerIndex + marker.length) : ""; + if (!packageName) { + throw new Error(`reviewed npm lock has an unsupported package location: ${location}`); + } + return packageName; +} + +function verifyReviewedLockDigest( + lockfilePath: string, + expectedLockSha256: string, + label: string, +): void { + if (!/^[0-9a-f]{64}$/.test(expectedLockSha256)) { + throw new Error(`${label} must use a committed lowercase SHA-256 lock identity`); + } + const actualLockSha256 = createHash("sha256").update(readFileSync(lockfilePath)).digest("hex"); + if (actualLockSha256 !== expectedLockSha256) { + throw new Error( + `${label} lock SHA-256 mismatch\nExpected: ${expectedLockSha256}\nActual: ${actualLockSha256}`, + ); + } +} + +function readReviewedLockPackages( + packages: Readonly>>, + lockfilePath: string, + registryOrigin: string, +): readonly ReviewedNpmArchiveRequest[] { const reviewed: ReviewedNpmArchiveRequest[] = []; - const identities = new Set(); + const identities = new Map(); for (const [location, value] of Object.entries(packages)) { if (location === "") continue; - const marker = "node_modules/"; - const nestedMarkerIndex = location.lastIndexOf(`/${marker}`); - const markerIndex = location.startsWith(marker) - ? 0 - : nestedMarkerIndex >= 0 - ? nestedMarkerIndex + 1 - : -1; - const packageName = markerIndex >= 0 ? location.slice(markerIndex + marker.length) : ""; - if (!packageName) { - throw new Error(`reviewed npm lock has an unsupported package location: ${location}`); - } if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error(`reviewed npm lock has an invalid package record: ${location}`); } const record = value as Record; + const locationName = packageNameFromLockLocation(location); + const packageName = typeof record.name === "string" ? record.name : locationName; const version = typeof record.version === "string" ? record.version : ""; const packageSpec = `${packageName}@${version}`; const expectedIntegrity = typeof record.integrity === "string" ? record.integrity : ""; @@ -260,21 +306,138 @@ function readReviewedLockPackages( ) { throw new Error(`reviewed npm lock package must use the reviewed registry: ${location}`); } - if (identities.has(packageSpec)) { - throw new Error(`reviewed npm lock repeats package identity: ${packageSpec}`); + const prior = identities.get(packageSpec); + if (prior) { + if (prior.expectedIntegrity !== expectedIntegrity || prior.tarballUrl !== tarballUrl) { + throw new Error(`reviewed npm lock has conflicting package identity: ${packageSpec}`); + } + continue; } - identities.add(packageSpec); - reviewed.push({ + const request = { expectedIntegrity, label: `locked npm package ${packageSpec}`, packageSpec, tarballUrl, - }); + }; + identities.set(packageSpec, request); + reviewed.push(request); + } + if (reviewed.length === 0) { + throw new Error(`reviewed npm lock contains no packages: ${lockfilePath}`); } - if (reviewed.length === 0) throw new Error("reviewed npm lock contains no packages"); return reviewed; } +export function verifyReviewedNpmLock( + request: ReviewedNpmLockRequest, + npmRunner: NpmRunner = runNpm, +): readonly string[] { + requireReviewedRequest(request); + verifyReviewedLockDigest(request.lockfilePath, request.expectedLockSha256, request.label); + const registryOrigin = normalizeRegistryOrigin(request.registryOrigin); + const packages = readReviewedLock(request.lockfilePath); + const { name, version } = parseExactPackageSpec(request.packageSpec); + const root = packages[""]; + if (!root || typeof root !== "object" || Array.isArray(root)) { + throw new Error("reviewed npm lock is missing its root package record"); + } + const rootDependencies = root.dependencies; + if ( + typeof rootDependencies !== "object" || + rootDependencies === null || + Array.isArray(rootDependencies) || + Object.keys(rootDependencies).length !== 1 || + (rootDependencies as Record)[name] !== version + ) { + throw new Error(`reviewed npm lock root must depend only on ${request.packageSpec}`); + } + + const topLevel = packages[`node_modules/${name}`]; + if (!topLevel || typeof topLevel !== "object" || Array.isArray(topLevel)) { + throw new Error(`reviewed npm lock is missing ${request.packageSpec}`); + } + if (topLevel.version !== version) { + throw new Error( + `reviewed npm lock version mismatch for ${name}: expected ${version}, found ${String(topLevel.version ?? "missing")}`, + ); + } + if (topLevel.integrity !== request.expectedIntegrity) { + throw new Error(`reviewed npm lock integrity mismatch for ${request.packageSpec}`); + } + if (topLevel.resolved !== request.tarballUrl) { + throw new Error(`reviewed npm lock tarball URL mismatch for ${request.packageSpec}`); + } + + const reviewed = readReviewedLockPackages(packages, request.lockfilePath, registryOrigin); + verifyReviewedNpmMetadata(request, npmRunner); + return reviewed.map(({ packageSpec }) => packageSpec); +} + +export function verifyInstalledNpmLock( + request: ReviewedInstalledNpmLockRequest, +): readonly string[] { + verifyReviewedLockDigest(request.lockfilePath, request.expectedLockSha256, request.label); + const packages = readReviewedLock(request.lockfilePath); + const installRoot = resolve(request.installRoot); + const nodeModulesRoot = resolve(installRoot, "node_modules"); + const verified: string[] = []; + + for (const [location, record] of Object.entries(packages)) { + if (location === "") continue; + const locationName = packageNameFromLockLocation(location); + const expectedName = typeof record.name === "string" ? record.name : locationName; + const expectedVersion = typeof record.version === "string" ? record.version : ""; + const packageSpec = `${expectedName}@${expectedVersion}`; + if (!EXACT_NPM_PACKAGE_SPEC.test(packageSpec)) { + throw new Error(`${request.label} has an invalid locked package identity: ${packageSpec}`); + } + const packageDirectory = resolve(installRoot, location); + if (!packageDirectory.startsWith(`${nodeModulesRoot}${sep}`)) { + throw new Error(`${request.label} has an unsafe installed package path: ${location}`); + } + let packageEntry; + try { + packageEntry = lstatSync(packageDirectory); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + if (record.optional === true) continue; + throw new Error(`${request.label} is missing installed package: ${packageSpec}`); + } + throw error; + } + if (!packageEntry.isDirectory() || packageEntry.isSymbolicLink()) { + throw new Error( + `${request.label} installed package must be a non-symlink directory: ${location}`, + ); + } + + const manifestPath = join(packageDirectory, "package.json"); + let manifest: unknown; + try { + const manifestEntry = lstatSync(manifestPath); + if (!manifestEntry.isFile() || manifestEntry.isSymbolicLink()) { + throw new Error("manifest must be a non-symlink regular file"); + } + manifest = JSON.parse(readFileSync(manifestPath, "utf-8")); + } catch (error) { + throw new Error( + `${request.label} installed package manifest is unreadable: ${location}: ${String(error)}`, + ); + } + if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) { + throw new Error(`${request.label} installed package manifest is invalid: ${location}`); + } + const installed = manifest as Record; + if (installed.name !== expectedName || installed.version !== expectedVersion) { + throw new Error( + `${request.label} installed package identity mismatch at ${location}: expected ${packageSpec}, found ${String(installed.name ?? "missing")}@${String(installed.version ?? "missing")}`, + ); + } + verified.push(packageSpec); + } + return verified; +} + export function verifyReviewedNpmCache( request: ReviewedNpmCacheRequest, npmRunner: NpmRunner = runNpm, @@ -292,7 +455,11 @@ export function verifyReviewedNpmCache( } const registryOrigin = normalizeRegistryOrigin(request.registryOrigin); - const packages = readReviewedLockPackages(request.lockfilePath, registryOrigin); + const packages = readReviewedLockPackages( + readReviewedLock(request.lockfilePath), + request.lockfilePath, + registryOrigin, + ); const env = { ...process.env, ...request.env, @@ -325,17 +492,30 @@ export function verifyReviewedNpmCache( type ArchiveCliOptions = ReviewedNpmArchiveRequest & Readonly<{ mode: "archive"; verifyOnly: boolean }>; type CacheCliOptions = ReviewedNpmCacheRequest & Readonly<{ mode: "cache" }>; -type CliOptions = ArchiveCliOptions | CacheCliOptions; +type LockCliOptions = ReviewedNpmLockRequest & Readonly<{ mode: "lock" }>; +type InstalledLockCliOptions = ReviewedInstalledNpmLockRequest & + Readonly<{ mode: "installed-lock" }>; +type CliOptions = ArchiveCliOptions | CacheCliOptions | LockCliOptions | InstalledLockCliOptions; function parseCliOptions(argv: readonly string[]): CliOptions { const values = new Map(); let verifyOnly = false; + let verifyLock = false; + let verifyInstalledLock = false; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--verify-only") { verifyOnly = true; continue; } + if (arg === "--verify-lock") { + verifyLock = true; + continue; + } + if (arg === "--verify-installed-lock") { + verifyInstalledLock = true; + continue; + } if (!arg?.startsWith("--")) throw new Error(`Unknown argument: ${arg ?? ""}`); const value = argv[index + 1]; if (!value || value.startsWith("--")) throw new Error(`${arg} requires a value`); @@ -347,9 +527,11 @@ function parseCliOptions(argv: readonly string[]): CliOptions { if (!value) throw new Error(`${name} is required`); return value; }; - if (values.has("--lockfile") || values.has("--cache") || values.has("--registry-origin")) { + if (values.has("--cache")) { if ( verifyOnly || + verifyLock || + verifyInstalledLock || values.has("--package-spec") || values.has("--integrity") || values.has("--tarball-url") || @@ -366,6 +548,40 @@ function parseCliOptions(argv: readonly string[]): CliOptions { tempDirectory: values.get("--temp-directory"), }; } + if (verifyInstalledLock) { + if (verifyOnly || verifyLock || values.has("--cache") || values.has("--registry-origin")) { + throw new Error("installed npm lock verification cannot be combined with other modes"); + } + return { + expectedLockSha256: required("--lock-sha256"), + installRoot: required("--install-root"), + label: required("--label"), + lockfilePath: required("--lockfile"), + mode: "installed-lock", + }; + } + if (verifyLock) { + if (verifyOnly || verifyInstalledLock || values.has("--cache")) { + throw new Error("reviewed npm lock verification cannot be combined with other modes"); + } + return { + expectedIntegrity: required("--integrity"), + expectedLockSha256: required("--lock-sha256"), + label: required("--label"), + lockfilePath: required("--lockfile"), + mode: "lock", + npmExecutable: process.env.NEMOCLAW_REVIEWED_NPM_EXECUTABLE, + packageSpec: required("--package-spec"), + registryOrigin: required("--registry-origin"), + tarballUrl: required("--tarball-url"), + tempDirectory: values.get("--temp-directory"), + }; + } + if (values.has("--lockfile") || values.has("--registry-origin") || values.has("--install-root")) { + throw new Error( + "--lockfile, --registry-origin, and --install-root require a matching lock mode", + ); + } return { expectedIntegrity: required("--integrity"), label: required("--label"), @@ -388,6 +604,12 @@ if (isMainModule()) { if (options.mode === "cache") { const verified = verifyReviewedNpmCache(options); process.stdout.write(`Verified ${verified.length} locked npm cache archives\n`); + } else if (options.mode === "installed-lock") { + const verified = verifyInstalledNpmLock(options); + process.stdout.write(`Verified ${verified.length} installed npm package identities\n`); + } else if (options.mode === "lock") { + const verified = verifyReviewedNpmLock(options); + process.stdout.write(`Verified ${verified.length} locked npm packages\n`); } else if (options.verifyOnly) { verifyReviewedNpmMetadata(options); } else { diff --git a/src/lib/sandbox/build-context.ts b/src/lib/sandbox/build-context.ts index 5c34a282d4f..f90f1dad05a 100644 --- a/src/lib/sandbox/build-context.ts +++ b/src/lib/sandbox/build-context.ts @@ -41,7 +41,7 @@ function normalizeReadModesForDockerCopy(rootDir: string): void { } function stageOpenClawRuntimeGraphs(rootDir: string, buildCtx: string): void { - for (const runtimeName of ["mcporter-runtime", "wechat-runtime"]) { + for (const runtimeName of ["mcporter-runtime", "openclaw-runtime", "wechat-runtime"]) { const sourceDir = path.join(rootDir, "agents", "openclaw", runtimeName); const stagedDir = path.join(buildCtx, "agents", "openclaw", runtimeName); fs.mkdirSync(stagedDir, { recursive: true }); diff --git a/test/openclaw-dependency-review.test.ts b/test/openclaw-dependency-review.test.ts index a5f6a400ca4..5cbbde78201 100644 --- a/test/openclaw-dependency-review.test.ts +++ b/test/openclaw-dependency-review.test.ts @@ -140,7 +140,9 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => { expect(review).toContain("install the verified archive path"); expect(review).toContain("contained regular-file basename in a fresh directory"); expect(review).toContain("unsafe reported archive filenames"); - expect(review).toContain("no installer code consumes raw `npm pack --json` filenames"); + expect(review).toContain( + "keep archive verification until every remaining archive consumer moves", + ); expect(review).toContain("The #4434 compatibility-shim disposition is explicitly accepted"); expect(review).toContain( "The assembled-image and rebuilt-sandbox proof residual is explicitly accepted", @@ -239,7 +241,7 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => { expect(review).toContain("configured threshold in `ci/reviewed-npm-audit.json` is `high`"); expect(review).toContain("Transitive Dependency Graph Rationale"); expect(review).toContain( - "The OpenClaw 2026.6.10 bump does not newly introduce an unfrozen OpenClaw transitive graph", + "Both production Dockerfiles install with `npm ci` from that exact closure", ); expect(review).toContain( "The reviewed `openclaw@2026.6.10` artifact ships `npm-shrinkwrap.json`", @@ -318,7 +320,7 @@ messaging_build_applier=${JSON.stringify(MESSAGING_BUILD_APPLIER)} reviewed_archive_helper=scripts/lib/reviewed-npm-archive.mts boundary_marker_count="$(grep -hF 'Reviewed-archive invariants (#5896):' Dockerfile Dockerfile.base "$messaging_build_applier" | wc -l | tr -d ' ')" -test "$boundary_marker_count" -eq 5 +test "$boundary_marker_count" -eq 4 check_contains() { haystack="$1" @@ -369,7 +371,10 @@ for dockerfile in Dockerfile Dockerfile.base; do check_not_contains "$openclaw_block" 'REGISTRY_INTEGRITY=$(npm view' "$dockerfile inline integrity lookup" check_not_contains "$openclaw_block" 'pack_reviewed_npm_tarball' "$dockerfile inline pack helper" check_contains "$openclaw_block" 'openclaw-base-provenance-v1' "$dockerfile base provenance path" - check_contains "$openclaw_block" 'recipe=ignore-scripts+reviewed-lifecycle-v1' "$dockerfile base provenance recipe" + check_contains "$openclaw_block" 'OPENCLAW_INSTALL_RECIPE=ignore-scripts+reviewed-lifecycle-v1' "$dockerfile legacy provenance recipe" + check_contains "$openclaw_block" 'OPENCLAW_INSTALL_RECIPE=locked-ci+reviewed-lifecycle-v2' "$dockerfile locked provenance recipe" + check_contains "$openclaw_block" 'lock-sha256=' "$dockerfile OpenClaw provenance lock hash" + check_contains "$openclaw_block" 'recipe=\${OPENCLAW_INSTALL_RECIPE}' "$dockerfile selected provenance recipe" check_contains "$openclaw_block" 'mcporter-package=mcporter@' "$dockerfile mcporter provenance package" check_contains "$openclaw_block" 'mcporter-integrity=' "$dockerfile mcporter provenance integrity" check_contains "$openclaw_block" 'mcporter-lock-sha256=' "$dockerfile mcporter provenance lock hash" diff --git a/test/openclaw-integrity-pin-suite.ts b/test/openclaw-integrity-pin-suite.ts index fe3b997ffb1..8ad74059e27 100644 --- a/test/openclaw-integrity-pin-suite.ts +++ b/test/openclaw-integrity-pin-suite.ts @@ -44,6 +44,16 @@ const PINNED_OPENCLAW_VERSION = "2026.6.10"; const PINNED_OPENCLAW_INTEGRITY = "sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug=="; const PINNED_OPENCLAW_TARBALL = "https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz"; +const OPENCLAW_LOCKFILE = path.join( + REPO_ROOT, + "agents", + "openclaw", + "openclaw-runtime", + "package-lock.json", +); +const PINNED_OPENCLAW_LOCK_SHA256 = createHash("sha256") + .update(fs.readFileSync(OPENCLAW_LOCKFILE)) + .digest("hex"); const PINNED_CODEX_ACP_VERSION = "0.11.1"; const PINNED_CODEX_ACP_TARBALL = "https://registry.npmjs.org/@zed-industries/codex-acp/-/codex-acp-0.11.1.tgz"; @@ -99,11 +109,12 @@ function openClawBaseProvenance( tarball = PINNED_OPENCLAW_TARBALL, ): string { return [ - "schema=2", + "schema=3", `package=openclaw@${version}`, `integrity=${integrity}`, `tarball=${tarball}`, - "recipe=ignore-scripts+reviewed-lifecycle-v1", + `lock-sha256=${PINNED_OPENCLAW_LOCK_SHA256}`, + "recipe=locked-ci+reviewed-lifecycle-v2", `mcporter-package=mcporter@${PINNED_MCPORTER_VERSION}`, `mcporter-integrity=${PINNED_MCPORTER_INTEGRITY}`, `mcporter-tarball=${PINNED_MCPORTER_TARBALL}`, @@ -154,6 +165,9 @@ function runInstallBlock( baseProvenance?: string | null; baseProvenanceMetadata?: string; baseProvenanceSymlink?: boolean; + tamperOpenClawLock?: boolean; + failOpenClawNpmCi?: boolean; + failOpenClawPostinstall?: boolean; } = {}, ) { const { @@ -174,16 +188,25 @@ function runInstallBlock( baseProvenance = null, baseProvenanceMetadata = "0:0:444", baseProvenanceSymlink = false, + tamperOpenClawLock = false, + failOpenClawNpmCi = false, + failOpenClawPostinstall = false, } = options; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openclaw-integrity-")); const blueprint = path.join(tmp, "blueprint.yaml"); const log = path.join(tmp, "calls.log"); const provenancePath = path.join(tmp, "openclaw-base-provenance-v1"); + const openclawRuntime = path.join(tmp, "openclaw-runtime"); + const openclawGlobalModule = path.join(tmp, "global", "openclaw"); + const openclawBin = path.join(tmp, "bin", "openclaw"); const mcporterRuntime = path.join(tmp, "mcporter-runtime"); const mcporterBin = path.join(tmp, "bin", "mcporter"); const reviewedNpmExecutable = path.join(tmp, "bin", "reviewed-npm-fixture"); fs.mkdirSync(path.dirname(mcporterBin), { recursive: true }); + fs.mkdirSync(openclawRuntime, { recursive: true }); fs.mkdirSync(mcporterRuntime, { recursive: true }); + fs.copyFileSync(OPENCLAW_LOCKFILE, path.join(openclawRuntime, "package-lock.json")); + if (tamperOpenClawLock) fs.appendFileSync(path.join(openclawRuntime, "package-lock.json"), "\n"); fs.copyFileSync(MCPORTER_LOCKFILE, path.join(mcporterRuntime, "package-lock.json")); fs.writeFileSync(blueprint, fs.readFileSync(BLUEPRINT, "utf-8")); fs.writeFileSync( @@ -242,6 +265,8 @@ function runInstallBlock( `BASE_IMAGE=${JSON.stringify(baseImage)}`, `openclaw_provenance_path=${JSON.stringify(provenancePath)}`, `openclaw_provenance_metadata=${JSON.stringify(baseProvenanceMetadata)}`, + `openclaw_runtime=${JSON.stringify(openclawRuntime)}`, + `openclaw_global_module=${JSON.stringify(openclawGlobalModule)}`, `OPENCLAW_2026_6_10_INTEGRITY=${JSON.stringify(committedIntegrity)}`, `OPENCLAW_2026_6_10_TARBALL=${JSON.stringify(PINNED_OPENCLAW_TARBALL)}`, `NEMOCLAW_E2E_FIXTURE_LEGACY_OPENCLAW=${allowLegacyFixture ? "1" : "0"}`, @@ -257,8 +282,11 @@ function runInstallBlock( "export NODE_OPTIONS=", `installed_openclaw_version=${JSON.stringify(installedOpenClawVersion)}`, `installed_mcporter_version=${JSON.stringify(installedMcporterVersion)}`, + `fail_openclaw_npm_ci=${failOpenClawNpmCi ? "1" : "0"}`, + `fail_openclaw_postinstall=${failOpenClawPostinstall ? "1" : "0"}`, "node() {", - ' if [ "${1:-}" = "/usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs" ]; then printf "node %s\\n" "$*" >> "$call_log"; return 0; fi', + ' if printf "%s\\n" "$*" | grep -q -- "--verify-installed-lock"; then printf "node %s\\n" "$*" >> "$call_log"; return 0; fi', + ' if [ "${1:-}" = "$openclaw_global_module/scripts/postinstall-bundled-plugins.mjs" ] || [ "${1:-}" = "$openclaw_runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs" ]; then printf "node %s\\n" "$*" >> "$call_log"; [ "$fail_openclaw_postinstall" = "0" ] || return 43; return 0; fi', ' "$real_node" "$@"', "}", `openclaw() { if [ "\${1:-}" = "--version" ]; then printf 'openclaw %s\\n' "$installed_openclaw_version"; else return 127; fi; }`, @@ -270,7 +298,9 @@ function runInstallBlock( "}", "npm() {", ' printf "npm %s\\n" "$*" >> "$call_log";', - ' [ "${1:-}" != "--prefix" ] || [ "${3:-}" != "ci" ] || installed_mcporter_version="$MCPORTER_VERSION"', + ' if [ "${1:-}" = "--prefix" ] && [ "${3:-}" = "ci" ]; then', + ' if [ "${2:-}" = "$openclaw_runtime" ]; then [ "$fail_openclaw_npm_ci" = "0" ] || return 42; installed_openclaw_version="$OPENCLAW_VERSION"; else installed_mcporter_version="$MCPORTER_VERSION"; fi', + " fi", ' if [ "${1:-}" = "view" ] && [ "${3:-}" = "version" ]; then printf "%s\\n" "$OPENCLAW_VERSION"; return 0; fi', ` if [ "\${1:-}" = "view" ] && [ "\${2:-}" = "@zed-industries/codex-acp@${PINNED_CODEX_ACP_VERSION}" ] && [ "\${3:-}" = "dist.integrity" ]; then printf "%s\\n" ${JSON.stringify(codexAcpRegistryIntegrity)}; return 0; fi`, ` if [ "\${1:-}" = "view" ] && [ "\${2:-}" = "@zed-industries/codex-acp@${PINNED_CODEX_ACP_VERSION}" ] && [ "\${3:-}" = "dist.tarball" ]; then printf "%s\\n" ${JSON.stringify(codexAcpRegistryTarball)}; return 0; fi`, @@ -302,6 +332,10 @@ function runInstallBlock( .replaceAll("/opt/nemoclaw-blueprint/blueprint.yaml", blueprint) .replaceAll("/tmp/blueprint.yaml", blueprint) .replaceAll(OPENCLAW_BASE_PROVENANCE_PATH, provenancePath) + .replaceAll("/usr/local/lib/nemoclaw/openclaw-runtime", openclawRuntime) + .replaceAll("/usr/local/lib/node_modules/openclaw", openclawGlobalModule) + .replaceAll("/usr/local/lib/node_modules", path.dirname(openclawGlobalModule)) + .replaceAll("/usr/local/bin/openclaw", openclawBin) .replaceAll("/usr/local/lib/nemoclaw/mcporter-runtime", mcporterRuntime) .replaceAll("/usr/local/bin/mcporter", mcporterBin) .replaceAll("/scripts/lib/reviewed-npm-archive.mts", REVIEWED_NPM_ARCHIVE_HELPER), @@ -313,8 +347,27 @@ function runInstallBlock( const provenanceExists = fs.existsSync(provenancePath); const provenanceContent = provenanceExists ? fs.readFileSync(provenancePath, "utf-8") : null; const provenanceMode = provenanceExists ? fs.statSync(provenancePath).mode & 0o777 : null; + const pathExistsWithoutFollowingSymlinks = (target: string) => { + try { + fs.lstatSync(target); + return true; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return false; + throw error; + } + }; + const openclawGlobalModuleExists = pathExistsWithoutFollowingSymlinks(openclawGlobalModule); + const openclawBinExists = pathExistsWithoutFollowingSymlinks(openclawBin); fs.rmSync(tmp, { recursive: true, force: true }); - return { result, calls, provenanceExists, provenanceContent, provenanceMode }; + return { + result, + calls, + provenanceExists, + provenanceContent, + provenanceMode, + openclawGlobalModuleExists, + openclawBinExists, + }; } function runProductionBuildArgGuard( @@ -738,16 +791,18 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes }, ); - expect(production.result.status).toBe(0); - expect(codexAcp.result.status).toBe(0); - expect(base.result.status).toBe(0); + expect(production.result.status, production.result.stderr).toBe(0); + expect(codexAcp.result.status, codexAcp.result.stderr).toBe(0); + if (base.result.status !== 0) { + throw new Error(`base install failed: ${base.result.stderr}${base.result.stdout}`); + } expect(production.calls).toContain( `npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.integrity`, ); expect(production.calls).toContain( `npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.tarball`, ); - expect(production.calls).toContain( + expect(production.calls).not.toContain( `npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`, ); expect(codexAcp.calls).toContain( @@ -757,13 +812,8 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes `npm view @zed-industries/codex-acp@${PINNED_CODEX_ACP_VERSION} dist.tarball`, ); expect(codexAcp.calls).toContain(`npm pack ${PINNED_CODEX_ACP_TARBALL} --pack-destination`); - expect(production.calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", - ); - expect(production.calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); - expect(production.calls).toContain(`openclaw-${PINNED_OPENCLAW_VERSION}.tgz`); + expect(production.calls).toContain("openclaw-runtime ci --ignore-scripts --omit=dev"); + expect(production.calls).toContain("postinstall-bundled-plugins.mjs"); expect(codexAcp.calls).toContain( "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", ); @@ -771,12 +821,9 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(base.calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} version`); expect(base.calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.integrity`); expect(base.calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.tarball`); - expect(base.calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); - expect(base.calls).toContain("npm install -g --ignore-scripts "); - expect(base.calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); - expect(base.calls).toContain(`openclaw-${PINNED_OPENCLAW_VERSION}.tgz`); + expect(base.calls).not.toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); + expect(base.calls).toContain("openclaw-runtime ci --ignore-scripts --omit=dev"); + expect(base.calls).toContain("postinstall-bundled-plugins.mjs"); expect(base.provenanceContent).toBe(openClawBaseProvenance()); expect(base.provenanceMode).toBe(0o444); }); @@ -824,7 +871,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes ["missing marker", { baseProvenance: null }], [ "wrong schema", - { baseProvenance: openClawBaseProvenance().replace("schema=2", "schema=1") }, + { baseProvenance: openClawBaseProvenance().replace("schema=3", "schema=2") }, ], [ "wrong version", @@ -857,7 +904,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "wrong lifecycle recipe", { baseProvenance: openClawBaseProvenance().replace( - "recipe=ignore-scripts+reviewed-lifecycle-v1", + "recipe=locked-ci+reviewed-lifecycle-v2", "recipe=ignore-scripts-only-v1", ), }, @@ -934,7 +981,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "custom base reference", { baseProvenance: openClawBaseProvenance(), baseImage: "registry.example/base:custom" }, ], - ])("falls back to the reviewed archive for %s", (_label, overrides) => { + ])("falls back to the reviewed lock for %s", (_label, overrides) => { const { result, calls, provenanceExists } = runInstallBlock( extractRunBlock( DOCKERFILE, @@ -954,13 +1001,9 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(result.stdout).toContain("lacks exact reviewed provenance"); expect(calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.integrity`); expect(calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.tarball`); - expect(calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); - expect(calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", - ); - expect(calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); + expect(calls).not.toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); + expect(calls).toContain("openclaw-runtime ci --ignore-scripts --omit=dev"); + expect(calls).toContain("postinstall-bundled-plugins.mjs"); expect(provenanceExists).toBe(false); }); @@ -997,10 +1040,12 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "# Patch OpenClaw media fetch", ), { - openclawVersion: PINNED_OPENCLAW_VERSION, - committedIntegrity: PINNED_OPENCLAW_INTEGRITY, - registryIntegrity: PINNED_OPENCLAW_INTEGRITY, - packFilename: "../openclaw-2026.6.10.tgz", + openclawVersion: LEGACY_GATEWAY_UPGRADE_OPENCLAW_VERSION, + registryIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + registryTarball: LEGACY_GATEWAY_UPGRADE_OPENCLAW_TARBALL, + packIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + packFilename: "../openclaw-2026.4.24.tgz", + allowLegacyFixture: true, }, ); const codexAcp = runInstallBlock( @@ -1023,10 +1068,12 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "# Baseline health check.", ), { - openclawVersion: PINNED_OPENCLAW_VERSION, - committedIntegrity: PINNED_OPENCLAW_INTEGRITY, - registryIntegrity: PINNED_OPENCLAW_INTEGRITY, - packFilename: "../openclaw-2026.6.10.tgz", + openclawVersion: LEGACY_GATEWAY_UPGRADE_OPENCLAW_VERSION, + registryIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + registryTarball: LEGACY_GATEWAY_UPGRADE_OPENCLAW_TARBALL, + packIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + packFilename: "../openclaw-2026.4.24.tgz", + allowLegacyFixture: true, }, ); const optionalPlugin = runOptionalOpenClawPluginBlock({ @@ -1037,7 +1084,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes { label: "production Dockerfile", outcome: production, - unsafeFilename: "../openclaw-2026.6.10.tgz", + unsafeFilename: "../openclaw-2026.4.24.tgz", blockedCommand: "npm install -g", }, { @@ -1049,7 +1096,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes { label: "base Dockerfile", outcome: base, - unsafeFilename: "../openclaw-2026.6.10.tgz", + unsafeFilename: "../openclaw-2026.4.24.tgz", blockedCommand: "npm install -g", }, { @@ -1077,18 +1124,22 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "# Baseline health check.", ), { - openclawVersion: PINNED_OPENCLAW_VERSION, - committedIntegrity: PINNED_OPENCLAW_INTEGRITY, - registryIntegrity: PINNED_OPENCLAW_INTEGRITY, + openclawVersion: LEGACY_GATEWAY_UPGRADE_OPENCLAW_VERSION, + registryIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + registryTarball: LEGACY_GATEWAY_UPGRADE_OPENCLAW_TARBALL, + packIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, packFilename: null, + allowLegacyFixture: true, }, ); - const diagnostic = `npm pack openclaw@${PINNED_OPENCLAW_VERSION} did not report filename and integrity`; + const diagnostic = `npm pack openclaw@${LEGACY_GATEWAY_UPGRADE_OPENCLAW_VERSION} did not report filename and integrity`; expect(result.status).not.toBe(0); expect(result.stderr).toContain(diagnostic); expect(result.stdout).not.toContain(diagnostic); - expect(calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); + expect(calls).toContain( + `npm pack ${LEGACY_GATEWAY_UPGRADE_OPENCLAW_TARBALL} --pack-destination`, + ); expect(calls).not.toContain("npm install -g"); }); } @@ -1175,9 +1226,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(fixtureBase.calls).not.toContain("postinstall-bundled-plugins.mjs"); expect(gatewayFixtureBase.result.status).toBe(0); expect(gatewayFixtureBase.calls).toContain("npm install -g --ignore-scripts "); - expect(gatewayFixtureBase.calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); + expect(gatewayFixtureBase.calls).toContain("postinstall-bundled-plugins.mjs"); }); it("guards production Docker build args from legacy OpenClaw fixture inputs", () => { @@ -1319,7 +1368,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes ); const output = `${result.stdout}${result.stderr}`; - expect(result.status, block.label).not.toBe(0); + expect(result.status, `${block.label}: ${output}\n${calls}`).not.toBe(0); expect(output, block.label).toContain( `OpenClaw ${PINNED_OPENCLAW_VERSION} npm integrity mismatch`, ); @@ -1332,7 +1381,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes } }); - it("fails closed before npm install when the downloaded OpenClaw tarball integrity drifts", () => { + it("fails closed before npm ci when the reviewed OpenClaw lock bytes drift", () => { const { result, calls } = runInstallBlock( extractRunBlock( DOCKERFILE, @@ -1343,21 +1392,77 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes openclawVersion: PINNED_OPENCLAW_VERSION, committedIntegrity: PINNED_OPENCLAW_INTEGRITY, registryIntegrity: PINNED_OPENCLAW_INTEGRITY, - packIntegrity: "sha512-downloaded-drift", + tamperOpenClawLock: true, }, ); const output = `${result.stdout}${result.stderr}`; expect(result.status).not.toBe(0); - expect(output).toContain( - `OpenClaw ${PINNED_OPENCLAW_VERSION} downloaded tarball integrity mismatch`, - ); - expect(output).toContain(`Expected: ${PINNED_OPENCLAW_INTEGRITY}`); - expect(output).toContain("Actual: sha512-downloaded-drift"); - expect(calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); + expect(output).toContain("OpenClaw lock SHA-256 mismatch"); + expect(calls).not.toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.integrity`); + expect(calls).not.toContain("openclaw-runtime ci"); expect(calls).not.toContain("npm install -g"); }); + it.each([ + [ + "production Dockerfile", + DOCKERFILE, + "# OPENCLAW_VERSION is the NemoClaw runtime build target", + "# Patch OpenClaw media fetch", + ], + [ + "base Dockerfile", + DOCKERFILE_BASE, + "# Install OpenClaw CLI + PyYAML.", + "# Baseline health check.", + ], + ])("leaves no runtime exposure when npm ci fails in %s", (_label, file, start, end) => { + const outcome = runInstallBlock(extractRunBlock(file, start, end), { + openclawVersion: PINNED_OPENCLAW_VERSION, + committedIntegrity: PINNED_OPENCLAW_INTEGRITY, + registryIntegrity: PINNED_OPENCLAW_INTEGRITY, + failOpenClawNpmCi: true, + }); + + expect(outcome.result.status).not.toBe(0); + expect(outcome.calls).toContain("openclaw-runtime ci --ignore-scripts --omit=dev"); + expect(outcome.calls).not.toContain("--verify-installed-lock"); + expect(outcome.calls).not.toContain("postinstall-bundled-plugins.mjs"); + expect(outcome.openclawGlobalModuleExists).toBe(false); + expect(outcome.openclawBinExists).toBe(false); + expect(outcome.provenanceExists).toBe(false); + }); + + it.each([ + [ + "production Dockerfile", + DOCKERFILE, + "# OPENCLAW_VERSION is the NemoClaw runtime build target", + "# Patch OpenClaw media fetch", + ], + [ + "base Dockerfile", + DOCKERFILE_BASE, + "# Install OpenClaw CLI + PyYAML.", + "# Baseline health check.", + ], + ])("leaves no runtime exposure or provenance when postinstall fails in %s", (_label, file, start, end) => { + const outcome = runInstallBlock(extractRunBlock(file, start, end), { + openclawVersion: PINNED_OPENCLAW_VERSION, + committedIntegrity: PINNED_OPENCLAW_INTEGRITY, + registryIntegrity: PINNED_OPENCLAW_INTEGRITY, + failOpenClawPostinstall: true, + }); + + expect(outcome.result.status).not.toBe(0); + expect(outcome.calls).toContain("--verify-installed-lock"); + expect(outcome.calls).toContain("postinstall-bundled-plugins.mjs"); + expect(outcome.openclawGlobalModuleExists).toBe(false); + expect(outcome.openclawBinExists).toBe(false); + expect(outcome.provenanceExists).toBe(false); + }); + it("fails closed before npm install for unpinned production Dockerfile overrides", () => { const { result, calls } = runInstallBlock( extractRunBlock( diff --git a/test/openclaw-locked-install.test.ts b/test/openclaw-locked-install.test.ts new file mode 100644 index 00000000000..f211e9a8cde --- /dev/null +++ b/test/openclaw-locked-install.test.ts @@ -0,0 +1,322 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + type ReviewedNpmArchiveRequest, + verifyInstalledNpmLock, + verifyReviewedNpmLock, +} from "../scripts/lib/reviewed-npm-archive.mts"; + +const REPO_ROOT = path.join(import.meta.dirname, ".."); +const RUNTIME_DIRECTORY = path.join(REPO_ROOT, "agents", "openclaw", "openclaw-runtime"); +const LOCKFILE = path.join(RUNTIME_DIRECTORY, "package-lock.json"); +const PACKAGE_SPEC = "openclaw@2026.6.10"; +const INTEGRITY = + "sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug=="; +const TARBALL = "https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz"; +const LOCK_SHA256 = "a0f91c7e0b769e73c3f6119b2a6ee2dfd9bcb32b3dc69655b22696c654694d2d"; +const roots: string[] = []; + +function sha256(file: string): string { + return createHash("sha256").update(fs.readFileSync(file)).digest("hex"); +} + +function lockRequest(lockfilePath = LOCKFILE, expectedLockSha256 = LOCK_SHA256) { + return { + expectedIntegrity: INTEGRITY, + expectedLockSha256, + label: "OpenClaw 2026.6.10 locked runtime graph", + lockfilePath, + packageSpec: PACKAGE_SPEC, + registryOrigin: "https://registry.npmjs.org/", + tarballUrl: TARBALL, + }; +} + +function reviewedMetadata(args: readonly string[], request: ReviewedNpmArchiveRequest): string { + return args[2] === "dist.integrity" ? request.expectedIntegrity : request.tarballUrl; +} + +function mutatedLock(mutate: (lock: any) => void): string { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openclaw-lock-test-")); + roots.push(root); + const target = path.join(root, "package-lock.json"); + const lock = JSON.parse(fs.readFileSync(LOCKFILE, "utf-8")); + mutate(lock); + fs.writeFileSync(target, `${JSON.stringify(lock, null, 2)}\n`); + return target; +} + +function installedFixture({ + actualName = "chalk", + actualVersion = "5.6.2", + lockedName, + danglingSymlink = false, + manifestSymlink = false, + omit = false, + optional = false, + symlink = false, +}: { + actualName?: string; + actualVersion?: string; + lockedName?: string; + danglingSymlink?: boolean; + manifestSymlink?: boolean; + omit?: boolean; + optional?: boolean; + symlink?: boolean; +} = {}) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-installed-lock-test-")); + roots.push(root); + const installRoot = path.join(root, "runtime"); + const lockfilePath = path.join(root, "package-lock.json"); + const packageDirectory = path.join(installRoot, "node_modules", "chalk"); + const lock = { + lockfileVersion: 3, + packages: { + "": { dependencies: { chalk: "5.6.2" } }, + "node_modules/chalk": { + integrity: `sha512-${"D".repeat(88)}`, + ...(lockedName ? { name: lockedName } : {}), + optional, + resolved: "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + version: "5.6.2", + }, + }, + }; + fs.writeFileSync(lockfilePath, `${JSON.stringify(lock, null, 2)}\n`); + if (!omit) { + if (symlink) { + const target = path.join(root, "substituted-package"); + if (!danglingSymlink) { + fs.mkdirSync(target, { recursive: true }); + fs.writeFileSync( + path.join(target, "package.json"), + JSON.stringify({ name: actualName, version: actualVersion }), + ); + } + fs.mkdirSync(path.dirname(packageDirectory), { recursive: true }); + fs.symlinkSync(target, packageDirectory); + } else { + fs.mkdirSync(packageDirectory, { recursive: true }); + const manifest = JSON.stringify({ name: actualName, version: actualVersion }); + const manifestPath = path.join(packageDirectory, "package.json"); + if (manifestSymlink) { + const target = path.join(root, "substituted-package.json"); + fs.writeFileSync(target, manifest); + fs.symlinkSync(target, manifestPath); + } else { + fs.writeFileSync(manifestPath, manifest); + } + } + } + return { + expectedLockSha256: sha256(lockfilePath), + installRoot, + label: "test locked graph", + lockfilePath, + }; +} + +afterEach(() => { + for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); +}); + +describe("locked OpenClaw production installation (#5896)", () => { + it("binds the reviewed root artifact to the complete committed closure", () => { + const verified = verifyReviewedNpmLock(lockRequest(), reviewedMetadata); + expect(verified).toHaveLength(305); + expect(verified).toContain(PACKAGE_SPEC); + expect(sha256(LOCKFILE)).toBe(LOCK_SHA256); + expect(JSON.parse(fs.readFileSync(LOCKFILE, "utf-8"))).toMatchObject({ + packages: { "": { dependencies: { openclaw: "2026.6.10" } } }, + }); + }); + + it("rejects any lock byte tamper before registry metadata is consulted", () => { + const lockfilePath = mutatedLock((lock) => { + lock.packages["node_modules/openclaw/node_modules/chalk"].integrity = + `sha512-${"A".repeat(88)}`; + }); + let npmCalled = false; + expect(() => + verifyReviewedNpmLock(lockRequest(lockfilePath), () => { + npmCalled = true; + return ""; + }), + ).toThrow("lock SHA-256 mismatch"); + expect(npmCalled).toBe(false); + }); + + it.each([ + { + expected: "root must depend only on openclaw@2026.6.10", + mutate: (lock: any) => { + lock.packages[""].dependencies.openclaw = "2026.6.11"; + }, + name: "root version drift", + }, + { + expected: "lock integrity mismatch for openclaw@2026.6.10", + mutate: (lock: any) => { + lock.packages["node_modules/openclaw"].integrity = `sha512-${"B".repeat(88)}`; + }, + name: "top-level integrity drift", + }, + { + expected: "must use a committed sha512 npm integrity value", + mutate: (lock: any) => { + delete lock.packages["node_modules/openclaw/node_modules/chalk"].integrity; + }, + name: "missing transitive integrity", + }, + { + expected: "must use the reviewed registry", + mutate: (lock: any) => { + lock.packages["node_modules/openclaw/node_modules/chalk"].resolved = + "https://packages.invalid/chalk-5.6.2.tgz"; + }, + name: "malicious transitive registry substitution", + }, + { + expected: "conflicting package identity: safe-buffer@5.1.2", + mutate: (lock: any) => { + lock.packages[ + "node_modules/openclaw/node_modules/string_decoder/node_modules/safe-buffer" + ].integrity = `sha512-${"C".repeat(88)}`; + }, + name: "conflicting duplicate package identity", + }, + ])("rejects $name even with a test-only matching lock digest", ({ expected, mutate }) => { + const lockfilePath = mutatedLock(mutate); + expect(() => + verifyReviewedNpmLock(lockRequest(lockfilePath, sha256(lockfilePath)), reviewedMetadata), + ).toThrow(expected); + }); + + it("binds installed package manifests to lock locations and versions", () => { + expect(verifyInstalledNpmLock(installedFixture())).toEqual(["chalk@5.6.2"]); + }); + + it("binds npm aliases to the canonical package name recorded in the lock", () => { + expect( + verifyInstalledNpmLock( + installedFixture({ actualName: "@scope/canonical", lockedName: "@scope/canonical" }), + ), + ).toEqual(["@scope/canonical@5.6.2"]); + }); + + it("rejects a same-registry tarball with a substituted package manifest", () => { + expect(() => + verifyInstalledNpmLock(installedFixture({ actualName: "is-odd", actualVersion: "3.0.1" })), + ).toThrow("expected chalk@5.6.2, found is-odd@3.0.1"); + }); + + it("fails closed on missing required packages and symlinked package roots", () => { + expect(() => verifyInstalledNpmLock(installedFixture({ omit: true }))).toThrow( + "missing installed package: chalk@5.6.2", + ); + expect(() => verifyInstalledNpmLock(installedFixture({ symlink: true }))).toThrow( + "installed package must be a non-symlink directory", + ); + expect(() => + verifyInstalledNpmLock( + installedFixture({ danglingSymlink: true, optional: true, symlink: true }), + ), + ).toThrow("installed package must be a non-symlink directory"); + }); + + it("rejects symlinked package manifests", () => { + expect(() => verifyInstalledNpmLock(installedFixture({ manifestSymlink: true }))).toThrow( + "manifest must be a non-symlink regular file", + ); + }); + + it("allows npm to omit an incompatible optional package", () => { + expect(verifyInstalledNpmLock(installedFixture({ omit: true, optional: true }))).toEqual([]); + }); + + it.each([ + "Dockerfile", + "Dockerfile.base", + ])("invokes the locked installer before exposing OpenClaw in %s", (dockerfileName) => { + const contents = fs.readFileSync(path.join(REPO_ROOT, dockerfileName), "utf-8"); + const verifyIndex = contents.indexOf( + "node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts --verify-lock", + ); + const installIndex = contents.indexOf( + "npm --prefix /usr/local/lib/nemoclaw/openclaw-runtime ci", + verifyIndex, + ); + const installedIdentityIndex = contents.indexOf("--verify-installed-lock", installIndex); + const postinstallIndex = contents.indexOf( + "/usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", + installedIdentityIndex, + ); + const linkIndex = contents.indexOf( + "ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw", + postinstallIndex, + ); + const binLinkIndex = contents.indexOf( + "ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/.bin/openclaw", + linkIndex, + ); + const branchEnd = contents.indexOf("else \\", installIndex); + const currentInstallBranch = contents.slice(verifyIndex, branchEnd); + + expect(contents).toContain( + "COPY agents/openclaw/openclaw-runtime/package-lock.json /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json", + ); + expect(verifyIndex).toBeGreaterThanOrEqual(0); + expect(installIndex).toBeGreaterThan(verifyIndex); + expect(installedIdentityIndex).toBeGreaterThan(installIndex); + expect(postinstallIndex).toBeGreaterThan(installedIdentityIndex); + expect(linkIndex).toBeGreaterThan(postinstallIndex); + expect(binLinkIndex).toBeGreaterThan(linkIndex); + expect(branchEnd).toBeGreaterThan(installIndex); + expect(currentInstallBranch).toContain(`--lock-sha256 \"$OPENCLAW_LOCK_SHA256\"`); + expect(currentInstallBranch).not.toContain("npm install -g"); + expect(contents).toContain("'schema=3'"); + expect(contents).toContain('"lock-sha256=${OPENCLAW_LOCK_SHA256}"'); + expect(contents).toContain("locked-ci+reviewed-lifecycle-v2"); + }); + + it("audits the same lock and rebuilds the base when its graph changes", () => { + const audit = JSON.parse( + fs.readFileSync(path.join(REPO_ROOT, "ci", "reviewed-npm-audit.json"), "utf-8"), + ); + expect(audit.archivePackages).not.toEqual( + expect.arrayContaining([expect.objectContaining({ packageSpec: PACKAGE_SPEC })]), + ); + expect(audit.lockedGraphs).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + directory: "agents/openclaw/openclaw-runtime", + lockSha256: LOCK_SHA256, + packageSpec: PACKAGE_SPEC, + }), + ]), + ); + + const baseWorkflow = fs.readFileSync( + path.join(REPO_ROOT, ".github", "workflows", "base-image.yaml"), + "utf-8", + ); + expect(baseWorkflow).toContain('"agents/openclaw/openclaw-runtime/package.json"'); + expect(baseWorkflow).toContain('"agents/openclaw/openclaw-runtime/package-lock.json"'); + expect(baseWorkflow).toContain('"scripts/lib/reviewed-npm-archive.mts"'); + + const baseResolver = fs.readFileSync( + path.join(REPO_ROOT, ".github", "actions", "resolve-sandbox-base-image", "action.yaml"), + "utf-8", + ); + expect(baseResolver).toContain("agents/openclaw/openclaw-runtime/package.json"); + expect(baseResolver).toContain("agents/openclaw/openclaw-runtime/package-lock.json"); + expect(baseResolver).toContain("scripts/lib/reviewed-npm-archive.mts"); + }); +}); diff --git a/test/reviewed-npm-archive.test.ts b/test/reviewed-npm-archive.test.ts index 9040fa37891..6af09f1ddaa 100644 --- a/test/reviewed-npm-archive.test.ts +++ b/test/reviewed-npm-archive.test.ts @@ -201,6 +201,40 @@ describe("reviewed npm archive", () => { } }); + it("uses a lock alias's canonical package identity for cache verification", () => { + const reviewed = cacheRequest(); + const lockfilePath = path.join(reviewed.tempDirectory as string, "alias-lock.json"); + fs.writeFileSync( + lockfilePath, + `${JSON.stringify( + { + lockfileVersion: 3, + packages: { + "": {}, + "node_modules/legacy-name": { + integrity: INTEGRITY, + name: "@example/reviewed", + resolved: TARBALL_URL, + version: "1.2.3", + }, + }, + }, + null, + 2, + )}\n`, + ); + const calls: Array<{ args: readonly string[]; request: ReviewedNpmArchiveRequest }> = []; + + expect( + verifyReviewedNpmCache({ ...reviewed, lockfilePath }, cachedArchiveRunner(calls)), + ).toEqual([PACKAGE_SPEC]); + expect(calls.map(({ request }) => request.packageSpec)).toEqual([ + PACKAGE_SPEC, + PACKAGE_SPEC, + PACKAGE_SPEC, + ]); + }); + it("rejects an off-origin locked archive before npm can read the cache", () => { const reviewed = cacheRequest(); const lock = JSON.parse(fs.readFileSync(WECHAT_LOCK, "utf-8")); diff --git a/test/sandbox-build-context.test.ts b/test/sandbox-build-context.test.ts index 0914808b3e4..69d029fd340 100644 --- a/test/sandbox-build-context.test.ts +++ b/test/sandbox-build-context.test.ts @@ -35,7 +35,7 @@ describe("sandbox build context staging", () => { writeFixture("Dockerfile"); writeFixture("tsconfig.runtime-preloads.json", "{}\n"); - for (const runtimeName of ["mcporter-runtime", "wechat-runtime"]) { + for (const runtimeName of ["mcporter-runtime", "openclaw-runtime", "wechat-runtime"]) { for (const fileName of ["package.json", "package-lock.json"]) { writeFixture( path.join("agents", "openclaw", runtimeName, fileName), @@ -165,7 +165,7 @@ describe("sandbox build context staging", () => { } function expectStagedOpenClawRuntimeGraphs(buildCtx: string, sourceRoot: string) { - for (const runtimeName of ["mcporter-runtime", "wechat-runtime"]) { + for (const runtimeName of ["mcporter-runtime", "openclaw-runtime", "wechat-runtime"]) { const runtimeDir = path.join(buildCtx, "agents", "openclaw", runtimeName); expect(fs.readdirSync(runtimeDir).sort()).toEqual(["package-lock.json", "package.json"]); for (const fileName of ["package.json", "package-lock.json"]) { From 2c3e15198795b7ca1ecc6df5801c7a1f515f9979 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Tue, 14 Jul 2026 04:40:58 -0700 Subject: [PATCH 02/16] fix(ci): trust reviewed npm audit boundary Signed-off-by: Ho Lim --- .github/workflows/pr.yaml | 58 +++++++++++- ci/source-shape-test-budget.json | 45 ++++++++++ test/openclaw-locked-install.test.ts | 108 ++++++++++++++++------ test/reviewed-npm-audit-workflow.test.ts | 110 +++++++++++++++++++++++ 4 files changed, 293 insertions(+), 28 deletions(-) create mode 100644 test/reviewed-npm-audit-workflow.test.ts diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 03e4bf761e2..32a305658ab 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -284,8 +284,64 @@ jobs: with: persist-credentials: false + - name: Checkout trusted reviewed npm audit + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ github.event.pull_request.base.sha }} + path: .trusted-reviewed-npm-audit + persist-credentials: false + sparse-checkout: | + .github/actions/ci-reviewed-npm-audit + scripts/audit-reviewed-npm-graph.mts + scripts/lib/reviewed-npm-archive.mts + sparse-checkout-cone-mode: false + + - name: Detect trusted reviewed npm audit schema + id: trusted-reviewed-npm-audit + shell: bash + run: | + if grep -Fq 'NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT' \ + .trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit/action.yaml \ + && grep -Fq 'parsed.schemaVersion !== 2' \ + .trusted-reviewed-npm-audit/scripts/audit-reviewed-npm-graph.mts; then + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + fi + + # One-time bootstrap for this PR only. The action, driver, and helper are + # pinned to its signed first commit; every later PR must use its base SHA. + - name: Checkout pinned bootstrap reviewed npm audit + if: ${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && github.event.pull_request.number == 6830 && github.event.pull_request.head.repo.full_name == 'HOYALIM/NemoClaw' }} + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: HOYALIM/NemoClaw + ref: f2e670fd059e2032034b8c03ee6d016755c3d2d6 + path: .trusted-reviewed-npm-audit-bootstrap + persist-credentials: false + sparse-checkout: | + .github/actions/ci-reviewed-npm-audit + scripts/audit-reviewed-npm-graph.mts + scripts/lib/reviewed-npm-archive.mts + sparse-checkout-cone-mode: false + + - name: Reject unavailable trusted reviewed npm audit + if: ${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && (github.event.pull_request.number != 6830 || github.event.pull_request.head.repo.full_name != 'HOYALIM/NemoClaw') }} + shell: bash + run: | + echo "::error::Trusted base SHA does not contain the schema-2 reviewed npm audit" + exit 1 + - name: Audit reviewed production npm graphs - uses: ./.github/actions/ci-reviewed-npm-audit + if: ${{ steps.trusted-reviewed-npm-audit.outputs.available == 'true' }} + uses: ./.trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit + with: + target-root: ${{ github.workspace }} + report-dir: artifacts/reviewed-npm-audit + + - name: Audit reviewed production npm graphs (pinned bootstrap) + if: ${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && github.event.pull_request.number == 6830 && github.event.pull_request.head.repo.full_name == 'HOYALIM/NemoClaw' }} + uses: ./.trusted-reviewed-npm-audit-bootstrap/.github/actions/ci-reviewed-npm-audit with: target-root: ${{ github.workspace }} report-dir: artifacts/reviewed-npm-audit diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index 34b4796718c..bf5b221ecdf 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -316,6 +316,41 @@ "test": "cross-checks the allowlist against every production archive install boundary", "category": "security" }, + { + "file": "test/openclaw-locked-install.test.ts", + "test": "audits the same lock and rebuilds the base when its graph changes", + "category": "security" + }, + { + "file": "test/openclaw-locked-install.test.ts", + "test": "fails closed on missing required packages and symlinked package roots", + "category": "security" + }, + { + "file": "test/openclaw-locked-install.test.ts", + "test": "invokes the locked installer before exposing OpenClaw in %s", + "category": "compatibility" + }, + { + "file": "test/openclaw-locked-install.test.ts", + "test": "rejects $name even with a test-only matching lock digest", + "category": "security" + }, + { + "file": "test/openclaw-locked-install.test.ts", + "test": "rejects a same-registry tarball with a substituted package manifest", + "category": "security" + }, + { + "file": "test/openclaw-locked-install.test.ts", + "test": "rejects any lock byte tamper before registry metadata is consulted", + "category": "security" + }, + { + "file": "test/openclaw-locked-install.test.ts", + "test": "rejects symlinked package manifests", + "category": "security" + }, { "file": "test/platform-vitest-main-workflow.test.ts", "test": "keeps the WSL suite unprivileged with explicit root-only contracts", @@ -466,6 +501,16 @@ "test": "stages the public NVIDIA key for the Model Router's NVIDIA credential", "category": "security" }, + { + "file": "test/reviewed-npm-audit-workflow.test.ts", + "test": "executes the trusted driver and helper against explicit target inputs", + "category": "security" + }, + { + "file": "test/reviewed-npm-audit-workflow.test.ts", + "test": "runs PR audits from trusted code and keeps the main audit on the checked-in action", + "category": "security" + }, { "file": "test/release-latest-tag-workflow.test.ts", "test": "binds latest promotion to the exact GitHub-verified tag object", diff --git a/test/openclaw-locked-install.test.ts b/test/openclaw-locked-install.test.ts index f211e9a8cde..d8d20d106b4 100644 --- a/test/openclaw-locked-install.test.ts +++ b/test/openclaw-locked-install.test.ts @@ -52,6 +52,56 @@ function mutatedLock(mutate: (lock: any) => void): string { return target; } +type InstalledFixtureLayout = + | "dangling-package-symlink" + | "manifest-symlink" + | "omitted" + | "package-symlink" + | "regular"; + +type InstalledFixtureWriter = (args: { + readonly actualName: string; + readonly actualVersion: string; + readonly packageDirectory: string; + readonly root: string; +}) => void; + +function writePackageManifest( + packageDirectory: string, + actualName: string, + actualVersion: string, +): void { + fs.mkdirSync(packageDirectory, { recursive: true }); + fs.writeFileSync( + path.join(packageDirectory, "package.json"), + JSON.stringify({ name: actualName, version: actualVersion }), + ); +} + +const INSTALLED_FIXTURE_WRITERS: Readonly> = + { + "dangling-package-symlink": ({ packageDirectory, root }) => { + fs.mkdirSync(path.dirname(packageDirectory), { recursive: true }); + fs.symlinkSync(path.join(root, "substituted-package"), packageDirectory); + }, + "manifest-symlink": ({ actualName, actualVersion, packageDirectory, root }) => { + fs.mkdirSync(packageDirectory, { recursive: true }); + const target = path.join(root, "substituted-package.json"); + fs.writeFileSync(target, JSON.stringify({ name: actualName, version: actualVersion })); + fs.symlinkSync(target, path.join(packageDirectory, "package.json")); + }, + omitted: () => undefined, + "package-symlink": ({ actualName, actualVersion, packageDirectory, root }) => { + const target = path.join(root, "substituted-package"); + writePackageManifest(target, actualName, actualVersion); + fs.mkdirSync(path.dirname(packageDirectory), { recursive: true }); + fs.symlinkSync(target, packageDirectory); + }, + regular: ({ actualName, actualVersion, packageDirectory }) => { + writePackageManifest(packageDirectory, actualName, actualVersion); + }, + }; + function installedFixture({ actualName = "chalk", actualVersion = "5.6.2", @@ -90,31 +140,21 @@ function installedFixture({ }, }; fs.writeFileSync(lockfilePath, `${JSON.stringify(lock, null, 2)}\n`); - if (!omit) { - if (symlink) { - const target = path.join(root, "substituted-package"); - if (!danglingSymlink) { - fs.mkdirSync(target, { recursive: true }); - fs.writeFileSync( - path.join(target, "package.json"), - JSON.stringify({ name: actualName, version: actualVersion }), - ); - } - fs.mkdirSync(path.dirname(packageDirectory), { recursive: true }); - fs.symlinkSync(target, packageDirectory); - } else { - fs.mkdirSync(packageDirectory, { recursive: true }); - const manifest = JSON.stringify({ name: actualName, version: actualVersion }); - const manifestPath = path.join(packageDirectory, "package.json"); - if (manifestSymlink) { - const target = path.join(root, "substituted-package.json"); - fs.writeFileSync(target, manifest); - fs.symlinkSync(target, manifestPath); - } else { - fs.writeFileSync(manifestPath, manifest); - } - } - } + const layout: InstalledFixtureLayout = omit + ? "omitted" + : symlink + ? danglingSymlink + ? "dangling-package-symlink" + : "package-symlink" + : manifestSymlink + ? "manifest-symlink" + : "regular"; + INSTALLED_FIXTURE_WRITERS[layout]({ + actualName, + actualVersion, + packageDirectory, + root, + }); return { expectedLockSha256: sha256(lockfilePath), installRoot, @@ -138,6 +178,7 @@ describe("locked OpenClaw production installation (#5896)", () => { }); }); + // source-shape-contract: security -- The committed production lock digest must fail before any registry-controlled metadata is consulted it("rejects any lock byte tamper before registry metadata is consulted", () => { const lockfilePath = mutatedLock((lock) => { lock.packages["node_modules/openclaw/node_modules/chalk"].integrity = @@ -153,6 +194,7 @@ describe("locked OpenClaw production installation (#5896)", () => { expect(npmCalled).toBe(false); }); + // source-shape-contract: security -- Mutating the shipped lock proves every reviewed transitive identity remains bound to committed production bytes it.each([ { expected: "root must depend only on openclaw@2026.6.10", @@ -206,17 +248,22 @@ describe("locked OpenClaw production installation (#5896)", () => { it("binds npm aliases to the canonical package name recorded in the lock", () => { expect( verifyInstalledNpmLock( - installedFixture({ actualName: "@scope/canonical", lockedName: "@scope/canonical" }), + installedFixture({ + actualName: "@scope/canonical", + lockedName: "@scope/canonical", + }), ), ).toEqual(["@scope/canonical@5.6.2"]); }); + // source-shape-contract: security -- The installed production graph must reject package identity substitution after a reviewed tarball is extracted it("rejects a same-registry tarball with a substituted package manifest", () => { expect(() => verifyInstalledNpmLock(installedFixture({ actualName: "is-odd", actualVersion: "3.0.1" })), ).toThrow("expected chalk@5.6.2, found is-odd@3.0.1"); }); + // source-shape-contract: security -- Production lock verification must fail closed when required package locations are absent or redirected through symlinks it("fails closed on missing required packages and symlinked package roots", () => { expect(() => verifyInstalledNpmLock(installedFixture({ omit: true }))).toThrow( "missing installed package: chalk@5.6.2", @@ -226,11 +273,16 @@ describe("locked OpenClaw production installation (#5896)", () => { ); expect(() => verifyInstalledNpmLock( - installedFixture({ danglingSymlink: true, optional: true, symlink: true }), + installedFixture({ + danglingSymlink: true, + optional: true, + symlink: true, + }), ), ).toThrow("installed package must be a non-symlink directory"); }); + // source-shape-contract: security -- Installed production manifests must remain regular files beneath their reviewed package locations it("rejects symlinked package manifests", () => { expect(() => verifyInstalledNpmLock(installedFixture({ manifestSymlink: true }))).toThrow( "manifest must be a non-symlink regular file", @@ -241,6 +293,7 @@ describe("locked OpenClaw production installation (#5896)", () => { expect(verifyInstalledNpmLock(installedFixture({ omit: true, optional: true }))).toEqual([]); }); + // source-shape-contract: compatibility -- Both shipped Dockerfiles must preserve the reviewed lock verification and installation sequence it.each([ "Dockerfile", "Dockerfile.base", @@ -286,6 +339,7 @@ describe("locked OpenClaw production installation (#5896)", () => { expect(contents).toContain("locked-ci+reviewed-lifecycle-v2"); }); + // source-shape-contract: security -- The shipped audit and base-image rebuild inputs must share the exact committed production lock authority it("audits the same lock and rebuilds the base when its graph changes", () => { const audit = JSON.parse( fs.readFileSync(path.join(REPO_ROOT, "ci", "reviewed-npm-audit.json"), "utf-8"), diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts new file mode 100644 index 00000000000..2508ae8bcb9 --- /dev/null +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { readYaml } from "./helpers/e2e-workflow-contract"; + +type WorkflowStep = { + readonly env?: Record; + readonly id?: string; + readonly if?: string; + readonly name?: string; + readonly run?: string; + readonly uses?: string; + readonly with?: Record; +}; + +type WorkflowJob = { + readonly needs?: string | readonly string[]; + readonly steps?: readonly WorkflowStep[]; +}; + +type Workflow = { + readonly jobs: Record; +}; + +const REPO_ROOT = path.join(import.meta.dirname, ".."); +const BOOTSTRAP_SHA = "f2e670fd059e2032034b8c03ee6d016755c3d2d6"; +const BOOTSTRAP_IF = + "${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && github.event.pull_request.number == 6830 && github.event.pull_request.head.repo.full_name == 'HOYALIM/NemoClaw' }}"; + +function requiredStep(job: WorkflowJob, name: string): WorkflowStep { + const step = job.steps?.find((candidate) => candidate.name === name); + expect(step, `Missing workflow step: ${name}`).toBeDefined(); + return step as WorkflowStep; +} + +describe("trusted reviewed npm audit workflow (#5896)", () => { + // source-shape-contract: security -- PR dependency audit code must come from the base SHA or the one-time signed bootstrap + it("runs PR audits from trusted code and keeps the main audit on the checked-in action", () => { + const pr = readYaml(".github/workflows/pr.yaml"); + const main = readYaml(".github/workflows/main.yaml"); + const prJob = pr.jobs["reviewed-npm-audit"]; + const mainJob = main.jobs["reviewed-npm-audit"]; + + const trustedCheckout = requiredStep(prJob, "Checkout trusted reviewed npm audit"); + expect(trustedCheckout.with).toMatchObject({ + ref: "${{ github.event.pull_request.base.sha }}", + path: ".trusted-reviewed-npm-audit", + "persist-credentials": false, + "sparse-checkout-cone-mode": false, + }); + const sparseCheckout = String(trustedCheckout.with?.["sparse-checkout"]); + expect(sparseCheckout).toContain(".github/actions/ci-reviewed-npm-audit"); + expect(sparseCheckout).toContain("scripts/audit-reviewed-npm-graph.mts"); + expect(sparseCheckout).toContain("scripts/lib/reviewed-npm-archive.mts"); + + const detection = requiredStep(prJob, "Detect trusted reviewed npm audit schema"); + expect(detection.id).toBe("trusted-reviewed-npm-audit"); + expect(detection.run).toContain("parsed.schemaVersion !== 2"); + + const bootstrap = requiredStep(prJob, "Checkout pinned bootstrap reviewed npm audit"); + expect(bootstrap.if).toBe(BOOTSTRAP_IF); + expect(bootstrap.with).toMatchObject({ + repository: "HOYALIM/NemoClaw", + ref: BOOTSTRAP_SHA, + path: ".trusted-reviewed-npm-audit-bootstrap", + "persist-credentials": false, + }); + expect(requiredStep(prJob, "Audit reviewed production npm graphs")).toMatchObject({ + if: "${{ steps.trusted-reviewed-npm-audit.outputs.available == 'true' }}", + uses: "./.trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit", + with: { + "target-root": "${{ github.workspace }}", + "report-dir": "artifacts/reviewed-npm-audit", + }, + }); + expect( + requiredStep(prJob, "Audit reviewed production npm graphs (pinned bootstrap)"), + ).toMatchObject({ + if: BOOTSTRAP_IF, + uses: "./.trusted-reviewed-npm-audit-bootstrap/.github/actions/ci-reviewed-npm-audit", + }); + expect(requiredStep(mainJob, "Audit reviewed production npm graphs")).toMatchObject({ + uses: "./.github/actions/ci-reviewed-npm-audit", + with: { + "target-root": "${{ github.workspace }}", + "report-dir": "artifacts/reviewed-npm-audit", + }, + }); + }); + + // source-shape-contract: security -- The trusted composite action must execute only its bundled driver while treating the PR checkout as explicit data + it("executes the trusted driver and helper against explicit target inputs", () => { + const action = fs.readFileSync( + path.join(REPO_ROOT, ".github", "actions", "ci-reviewed-npm-audit", "action.yaml"), + "utf8", + ); + + expect(action).toContain('node-version: "22.22.2"'); + expect(action).toContain("npm install --global npm@10.9.4"); + expect(action).toContain("NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT"); + expect(action).toContain("NEMOCLAW_REVIEWED_NPM_AUDIT_REPORT_DIR"); + expect(action).toContain( + 'node --experimental-strip-types "$GITHUB_ACTION_PATH/../../../scripts/audit-reviewed-npm-graph.mts"', + ); + expect(action).not.toContain("run: node --experimental-strip-types scripts/"); + }); +}); From e975d24cf1a0759610dadbd51b9b7acee0fa20d1 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Tue, 14 Jul 2026 06:56:12 -0700 Subject: [PATCH 03/16] fix(ci): harden reviewed npm audit boundaries Signed-off-by: Ho Lim --- scripts/audit-reviewed-npm-graph.mts | 54 +++++++++++++++++++----- scripts/lib/reviewed-npm-archive.mts | 8 +++- test/openclaw-locked-install.test.ts | 7 +++ test/reviewed-npm-audit-workflow.test.ts | 2 + test/reviewed-npm-audit.test.ts | 31 +++++++++++++- 5 files changed, 89 insertions(+), 13 deletions(-) diff --git a/scripts/audit-reviewed-npm-graph.mts b/scripts/audit-reviewed-npm-graph.mts index f2f3a66d00f..f87ac21dbf9 100755 --- a/scripts/audit-reviewed-npm-graph.mts +++ b/scripts/audit-reviewed-npm-graph.mts @@ -32,23 +32,49 @@ type AuditConfig = Readonly<{ }>; const TRUSTED_REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const REPO_ROOT = path.resolve( - process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT ?? TRUSTED_REPO_ROOT, +const REPO_ROOT = fs.realpathSync( + path.resolve(process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT ?? TRUSTED_REPO_ROOT), +); +const CONFIG_PATH = resolveTargetPath( + "ci/reviewed-npm-audit.json", + "reviewed npm audit configuration", ); -const CONFIG_PATH = path.join(REPO_ROOT, "ci", "reviewed-npm-audit.json"); const SEVERITIES: readonly Severity[] = ["info", "low", "moderate", "high", "critical"]; -function resolveTargetPath(relativePath: string, label: string): string { +export function resolvePathWithinTargetRoot( + targetRoot: string, + relativePath: string, + label: string, +): string { if (!relativePath || path.isAbsolute(relativePath)) { throw new Error(`${label} must be a nonempty target-relative path`); } - const resolved = path.resolve(REPO_ROOT, relativePath); - if (!resolved.startsWith(`${REPO_ROOT}${path.sep}`)) { + const canonicalRoot = fs.realpathSync(path.resolve(targetRoot)); + const resolved = path.resolve(canonicalRoot, relativePath); + if (!resolved.startsWith(`${canonicalRoot}${path.sep}`)) { throw new Error(`${label} escapes the target root: ${relativePath}`); } + let current = canonicalRoot; + for (const component of path.relative(canonicalRoot, resolved).split(path.sep)) { + current = path.join(current, component); + let stat: fs.Stats; + try { + stat = fs.lstatSync(current); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") break; + throw error; + } + if (stat.isSymbolicLink()) { + throw new Error(`${label} contains a symbolic-link component: ${relativePath}`); + } + } return resolved; } +function resolveTargetPath(relativePath: string, label: string): string { + return resolvePathWithinTargetRoot(REPO_ROOT, relativePath, label); +} + function run(command: string, args: readonly string[], cwd: string, allowAuditFindings = false) { const result = spawnSync(command, args, { cwd, @@ -184,21 +210,27 @@ function materializeLockedGraph( tempRoot: string, registryOrigin: string, ): string { - const source = resolveTargetPath(graph.directory, `${graph.label} directory`); + const sourcePackage = resolveTargetPath( + path.join(graph.directory, "package.json"), + `${graph.label} package manifest`, + ); + const sourceLock = resolveTargetPath( + path.join(graph.directory, "package-lock.json"), + `${graph.label} lockfile`, + ); verifyReviewedNpmLock({ expectedIntegrity: graph.integrity, expectedLockSha256: graph.lockSha256, label: graph.label, - lockfilePath: path.join(source, "package-lock.json"), + lockfilePath: sourceLock, packageSpec: graph.packageSpec, registryOrigin, tarballUrl: graph.tarballUrl, }); const destination = path.join(tempRoot, `locked-${path.basename(graph.directory)}`); fs.mkdirSync(destination); - for (const filename of ["package.json", "package-lock.json"]) { - fs.copyFileSync(path.join(source, filename), path.join(destination, filename)); - } + fs.copyFileSync(sourcePackage, path.join(destination, "package.json")); + fs.copyFileSync(sourceLock, path.join(destination, "package-lock.json")); run("npm", ["ci", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund"], destination); verifyInstalledNpmLock({ expectedLockSha256: graph.lockSha256, diff --git a/scripts/lib/reviewed-npm-archive.mts b/scripts/lib/reviewed-npm-archive.mts index ad66dd39ec7..a81a8fb24b1 100755 --- a/scripts/lib/reviewed-npm-archive.mts +++ b/scripts/lib/reviewed-npm-archive.mts @@ -342,12 +342,18 @@ export function verifyReviewedNpmLock( throw new Error("reviewed npm lock is missing its root package record"); } const rootDependencies = root.dependencies; + const rootOptionalDependencies = root.optionalDependencies; if ( typeof rootDependencies !== "object" || rootDependencies === null || Array.isArray(rootDependencies) || Object.keys(rootDependencies).length !== 1 || - (rootDependencies as Record)[name] !== version + (rootDependencies as Record)[name] !== version || + (rootOptionalDependencies !== undefined && + (typeof rootOptionalDependencies !== "object" || + rootOptionalDependencies === null || + Array.isArray(rootOptionalDependencies) || + Object.keys(rootOptionalDependencies).length > 0)) ) { throw new Error(`reviewed npm lock root must depend only on ${request.packageSpec}`); } diff --git a/test/openclaw-locked-install.test.ts b/test/openclaw-locked-install.test.ts index d8d20d106b4..380015946de 100644 --- a/test/openclaw-locked-install.test.ts +++ b/test/openclaw-locked-install.test.ts @@ -203,6 +203,13 @@ describe("locked OpenClaw production installation (#5896)", () => { }, name: "root version drift", }, + { + expected: "root must depend only on openclaw@2026.6.10", + mutate: (lock: any) => { + lock.packages[""].optionalDependencies = { "left-pad": "1.3.0" }; + }, + name: "root optional dependency injection", + }, { expected: "lock integrity mismatch for openclaw@2026.6.10", mutate: (lock: any) => { diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index 2508ae8bcb9..57d7759a68a 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -27,6 +27,8 @@ type Workflow = { const REPO_ROOT = path.join(import.meta.dirname, ".."); const BOOTSTRAP_SHA = "f2e670fd059e2032034b8c03ee6d016755c3d2d6"; +// Removal condition: delete the PR-6830 fork bootstrap after this PR merges and +// the base branch contains the schema-v2 reviewed npm audit action. const BOOTSTRAP_IF = "${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && github.event.pull_request.number == 6830 && github.event.pull_request.head.repo.full_name == 'HOYALIM/NemoClaw' }}"; diff --git a/test/reviewed-npm-audit.test.ts b/test/reviewed-npm-audit.test.ts index 2fbdee52641..5b18aeb4f6c 100644 --- a/test/reviewed-npm-audit.test.ts +++ b/test/reviewed-npm-audit.test.ts @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import fs from "node:fs"; +import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; import { exceedsAuditThreshold, parseAuditReport, + resolvePathWithinTargetRoot, vulnerabilityCounts, } from "../scripts/audit-reviewed-npm-graph.mts"; @@ -16,6 +18,11 @@ const CONFIG = JSON.parse( ) as { severityThreshold: "info" | "low" | "moderate" | "high" | "critical"; }; +const roots: string[] = []; + +afterEach(() => { + for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); +}); describe("reviewed npm audit gate", () => { it("fails at high or critical findings while retaining lower severities", () => { @@ -63,4 +70,26 @@ describe("reviewed npm audit gate", () => { parseAuditReport({ status: 0, stderr: "", stdout: JSON.stringify(report) }), ).toThrow(/vulnerability report|vulnerability count/); }); + + it.each(["direct", "ancestor"])("rejects a %s symlink in an audit target", (layout) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-reviewed-audit-path-")); + roots.push(root); + const outside = path.join(root, "outside"); + const targetRoot = path.join(root, "repo"); + fs.mkdirSync(outside); + fs.mkdirSync(targetRoot); + + if (layout === "direct") { + fs.writeFileSync(path.join(outside, "config.json"), "{}"); + fs.mkdirSync(path.join(targetRoot, "ci")); + fs.symlinkSync(path.join(outside, "config.json"), path.join(targetRoot, "ci", "config.json")); + } else { + fs.symlinkSync(outside, path.join(targetRoot, "artifacts")); + } + + const relative = layout === "direct" ? "ci/config.json" : "artifacts/audit"; + expect(() => resolvePathWithinTargetRoot(targetRoot, relative, "audit target")).toThrow( + "contains a symbolic-link component", + ); + }); }); From b86d283547a481c91c23204d2903f2f80bf3b031 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Tue, 14 Jul 2026 07:27:06 -0700 Subject: [PATCH 04/16] test(ci): keep audit path cases linear Signed-off-by: Ho Lim --- test/reviewed-npm-audit.test.ts | 42 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/test/reviewed-npm-audit.test.ts b/test/reviewed-npm-audit.test.ts index 5b18aeb4f6c..be2ed960b3b 100644 --- a/test/reviewed-npm-audit.test.ts +++ b/test/reviewed-npm-audit.test.ts @@ -20,6 +20,16 @@ const CONFIG = JSON.parse( }; const roots: string[] = []; +function createAuditPathRoot(): { outside: string; targetRoot: string } { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-reviewed-audit-path-")); + roots.push(root); + const outside = path.join(root, "outside"); + const targetRoot = path.join(root, "repo"); + fs.mkdirSync(outside); + fs.mkdirSync(targetRoot); + return { outside, targetRoot }; +} + afterEach(() => { for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); }); @@ -71,25 +81,23 @@ describe("reviewed npm audit gate", () => { ).toThrow(/vulnerability report|vulnerability count/); }); - it.each(["direct", "ancestor"])("rejects a %s symlink in an audit target", (layout) => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-reviewed-audit-path-")); - roots.push(root); - const outside = path.join(root, "outside"); - const targetRoot = path.join(root, "repo"); - fs.mkdirSync(outside); - fs.mkdirSync(targetRoot); - - if (layout === "direct") { - fs.writeFileSync(path.join(outside, "config.json"), "{}"); - fs.mkdirSync(path.join(targetRoot, "ci")); - fs.symlinkSync(path.join(outside, "config.json"), path.join(targetRoot, "ci", "config.json")); - } else { - fs.symlinkSync(outside, path.join(targetRoot, "artifacts")); - } + it("rejects a direct symlink in an audit target", () => { + const { outside, targetRoot } = createAuditPathRoot(); + fs.writeFileSync(path.join(outside, "config.json"), "{}"); + fs.mkdirSync(path.join(targetRoot, "ci")); + fs.symlinkSync(path.join(outside, "config.json"), path.join(targetRoot, "ci", "config.json")); - const relative = layout === "direct" ? "ci/config.json" : "artifacts/audit"; - expect(() => resolvePathWithinTargetRoot(targetRoot, relative, "audit target")).toThrow( + expect(() => resolvePathWithinTargetRoot(targetRoot, "ci/config.json", "audit target")).toThrow( "contains a symbolic-link component", ); }); + + it("rejects an ancestor symlink in an audit target", () => { + const { outside, targetRoot } = createAuditPathRoot(); + fs.symlinkSync(outside, path.join(targetRoot, "artifacts")); + + expect(() => + resolvePathWithinTargetRoot(targetRoot, "artifacts/audit", "audit target"), + ).toThrow("contains a symbolic-link component"); + }); }); From cba06154cd57620ab1440cbfd1b3f5f6adba535e Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Tue, 14 Jul 2026 13:11:01 -0700 Subject: [PATCH 05/16] fix(images): close reviewed manifest races Signed-off-by: Ho Lim --- scripts/lib/reviewed-npm-archive.mts | 30 +++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/scripts/lib/reviewed-npm-archive.mts b/scripts/lib/reviewed-npm-archive.mts index a81a8fb24b1..4c329f92b9b 100755 --- a/scripts/lib/reviewed-npm-archive.mts +++ b/scripts/lib/reviewed-npm-archive.mts @@ -4,7 +4,16 @@ import { spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; -import { existsSync, lstatSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { + closeSync, + existsSync, + fstatSync, + lstatSync, + mkdtempSync, + openSync, + readFileSync, + rmSync, +} from "node:fs"; import { tmpdir } from "node:os"; import { isAbsolute, join, resolve, sep } from "node:path"; import { pathToFileURL } from "node:url"; @@ -419,16 +428,27 @@ export function verifyInstalledNpmLock( const manifestPath = join(packageDirectory, "package.json"); let manifest: unknown; + let manifestDescriptor: number | undefined; try { - const manifestEntry = lstatSync(manifestPath); - if (!manifestEntry.isFile() || manifestEntry.isSymbolicLink()) { + manifestDescriptor = openSync(manifestPath, "r"); + const openedManifestEntry = fstatSync(manifestDescriptor); + const pathManifestEntry = lstatSync(manifestPath); + if ( + !openedManifestEntry.isFile() || + !pathManifestEntry.isFile() || + pathManifestEntry.isSymbolicLink() || + openedManifestEntry.dev !== pathManifestEntry.dev || + openedManifestEntry.ino !== pathManifestEntry.ino + ) { throw new Error("manifest must be a non-symlink regular file"); } - manifest = JSON.parse(readFileSync(manifestPath, "utf-8")); + manifest = JSON.parse(readFileSync(manifestDescriptor, "utf-8")); } catch (error) { throw new Error( `${request.label} installed package manifest is unreadable: ${location}: ${String(error)}`, ); + } finally { + if (manifestDescriptor !== undefined) closeSync(manifestDescriptor); } if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) { throw new Error(`${request.label} installed package manifest is invalid: ${location}`); @@ -567,7 +587,7 @@ function parseCliOptions(argv: readonly string[]): CliOptions { }; } if (verifyLock) { - if (verifyOnly || verifyInstalledLock || values.has("--cache")) { + if (verifyOnly || values.has("--cache")) { throw new Error("reviewed npm lock verification cannot be combined with other modes"); } return { From fe37ccec8f303aca0db0ec6460235e4006d8a4e6 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Tue, 14 Jul 2026 18:13:02 -0700 Subject: [PATCH 06/16] test(images): align locked OpenClaw contracts Signed-off-by: Ho Lim --- ci/reviewed-npm-lifecycle-allowlist.json | 2 +- test/fetch-guard-patch-regression.test.ts | 56 +++++++++++++++-------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/ci/reviewed-npm-lifecycle-allowlist.json b/ci/reviewed-npm-lifecycle-allowlist.json index 323ad5e83b4..feba4db1a4c 100644 --- a/ci/reviewed-npm-lifecycle-allowlist.json +++ b/ci/reviewed-npm-lifecycle-allowlist.json @@ -25,7 +25,7 @@ "packageSpec": "openclaw@2026.6.10", "event": "postinstall", "manifestCommand": "node scripts/postinstall-bundled-plugins.mjs", - "explicitCommand": "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs" + "explicitCommand": "node /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs" } ] } diff --git a/test/fetch-guard-patch-regression.test.ts b/test/fetch-guard-patch-regression.test.ts index 22eaca757a2..6371669a6de 100644 --- a/test/fetch-guard-patch-regression.test.ts +++ b/test/fetch-guard-patch-regression.test.ts @@ -130,11 +130,20 @@ function readDockerfileOpenClawTarball(): string { ); } +function readDockerfileOpenClawLockSha256(): string { + return readRequiredMatch( + DOCKERFILE, + /OPENCLAW_LOCK_SHA256=([a-f0-9]{64});/, + "OpenClaw runtime lock digest", + ); +} + function runOpenClawUpgradeBlock(currentVersion: string) { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openclaw-upgrade-")); const blueprint = path.join(tmp, "blueprint.yaml"); const log = path.join(tmp, "calls.log"); const openclawInstall = path.join(tmp, "openclaw-global"); + const openclawRuntime = path.join(tmp, "openclaw-runtime"); const openclawShim = path.join(tmp, "openclaw-bin"); const mcporterInstall = path.join(tmp, "mcporter-runtime"); const mcporterShim = path.join(tmp, "mcporter-bin"); @@ -144,6 +153,7 @@ function runOpenClawUpgradeBlock(currentVersion: string) { const expectedMcporterVersion = readDockerfileMcporterVersion(); const openclawIntegrity = readDockerfileOpenClawIntegrity(); const openclawTarball = readDockerfileOpenClawTarball(); + const openclawLockSha256 = readDockerfileOpenClawLockSha256(); const mcporterIntegrity = readDockerfileMcporterIntegrity(); const mcporterTarball = readRequiredMatch( DOCKERFILE, @@ -152,9 +162,11 @@ function runOpenClawUpgradeBlock(currentVersion: string) { ); fs.writeFileSync(blueprint, `min_openclaw_version: "${readBlueprintMinOpenClawVersion()}"\n`); fs.mkdirSync(openclawInstall, { recursive: true }); + fs.mkdirSync(openclawRuntime, { recursive: true }); fs.mkdirSync(mcporterInstall, { recursive: true }); fs.mkdirSync(reviewedArchiveDir); fs.writeFileSync(path.join(mcporterInstall, "package-lock.json"), "{}"); + fs.writeFileSync(path.join(openclawRuntime, "package-lock.json"), "{}"); fs.writeFileSync(openclawShim, ""); fs.writeFileSync(mcporterShim, ""); fs.writeFileSync(reviewedArchive, "fake reviewed OpenClaw archive"); @@ -163,6 +175,7 @@ function runOpenClawUpgradeBlock(currentVersion: string) { "# Patch OpenClaw media fetch", ) .replaceAll("/opt/nemoclaw-blueprint/blueprint.yaml", blueprint) + .replaceAll("/usr/local/lib/nemoclaw/openclaw-runtime", openclawRuntime) .replaceAll("/usr/local/lib/node_modules/openclaw", openclawInstall) .replaceAll("/usr/local/bin/openclaw", openclawShim) .replaceAll("/usr/local/lib/node_modules/mcporter", mcporterInstall) @@ -173,18 +186,24 @@ function runOpenClawUpgradeBlock(currentVersion: string) { "set -euo pipefail", `call_log=${JSON.stringify(log)}`, `real_node=${JSON.stringify(process.execPath)}`, - `postinstall_path=${JSON.stringify(path.join(openclawInstall, "scripts/postinstall-bundled-plugins.mjs"))}`, + `postinstall_path=${JSON.stringify(path.join(openclawRuntime, "node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs"))}`, + `openclaw_lock=${JSON.stringify(path.join(openclawRuntime, "package-lock.json"))}`, `reviewed_archive=${JSON.stringify(reviewedArchive)}`, `OPENCLAW_VERSION=${JSON.stringify(openclawVersion)}`, `BASE_IMAGE=${JSON.stringify("registry.example/nemoclaw-test-base:latest")}`, `MCPORTER_VERSION=${JSON.stringify(expectedMcporterVersion)}`, `OPENCLAW_2026_6_10_INTEGRITY=${JSON.stringify(openclawIntegrity)}`, `OPENCLAW_2026_6_10_TARBALL=${JSON.stringify(openclawTarball)}`, + `OPENCLAW_LOCK_SHA256=${JSON.stringify(openclawLockSha256)}`, `MCPORTER_0_7_3_INTEGRITY=${JSON.stringify(mcporterIntegrity)}`, `MCPORTER_0_7_3_TARBALL=${JSON.stringify(mcporterTarball)}`, "node() {", ' if [ "${1:-}" = "$postinstall_path" ]; then printf "node %s\\n" "$*" >> "$call_log"; return 0; fi', ' if [ "${2:-}" = "/scripts/lib/reviewed-npm-archive.mts" ]; then', + ' if [ "${3:-}" = "--verify-lock" ] || [ "${3:-}" = "--verify-installed-lock" ]; then', + ' printf "node %s\\n" "$*" >> "$call_log";', + " return 0;", + " fi", ' if [ "${3:-}" = "--verify-only" ]; then', ' [ "$#" -eq 11 ] && [ "${4:-}" = "--package-spec" ] && [ "${5:-}" = "mcporter@${MCPORTER_VERSION}" ] || return 91;', ' [ "${6:-}" = "--integrity" ] && [ "${7:-}" = "$MCPORTER_0_7_3_INTEGRITY" ] || return 92;', @@ -201,6 +220,13 @@ function runOpenClawUpgradeBlock(currentVersion: string) { " fi", ' "$real_node" "$@"', "}", + "sha256sum() {", + ' if [ "${1:-}" = "$openclaw_lock" ]; then', + ' printf "%s %s\\n" "$OPENCLAW_LOCK_SHA256" "$1";', + " return 0;", + " fi", + ' command sha256sum "$@"', + "}", `openclaw() { if [ "\${1:-}" = "--version" ]; then printf 'openclaw ${currentVersion}\\n'; else return 127; fi; }`, `mcporter() { if [ "\${1:-}" = "--version" ]; then printf '${expectedMcporterVersion}\\n'; else return 127; fi; }`, "npm() {", @@ -327,38 +353,28 @@ describe("fetch-guard patch regression guard", () => { expect(fs.existsSync(inspectMarker)).toBe(true); }); - it("installs the reviewed archive for stale and same-version OpenClaw bases", () => { + it("installs the reviewed locked graph for stale and same-version OpenClaw bases", () => { const stale = runOpenClawUpgradeBlock("2026.3.11"); expect(stale.result.status).toBe(0); expect(stale.result.stdout).toContain( `Base image OpenClaw 2026.3.11 lacks exact reviewed provenance; installing ${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}`, ); - expect(stale.calls).toContain( - `npm pack https://registry.npmjs.org/openclaw/-/openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz --pack-destination`, - ); - expect(stale.calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", - ); + expect(stale.calls).toContain("--verify-lock"); + expect(stale.calls).toContain("npm --prefix"); + expect(stale.calls).toContain("ci --ignore-scripts --omit=dev"); + expect(stale.calls).toContain("--verify-installed-lock"); expect(stale.calls).toContain("postinstall-bundled-plugins.mjs"); - expect(stale.calls).toContain( - `openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz`, - ); const current = runOpenClawUpgradeBlock(CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION); expect(current.result.status).toBe(0); expect(current.result.stdout).toContain( `Base image OpenClaw ${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION} lacks exact reviewed provenance; installing ${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}`, ); - expect(current.calls).toContain( - `npm pack https://registry.npmjs.org/openclaw/-/openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz --pack-destination`, - ); - expect(current.calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", - ); + expect(current.calls).toContain("--verify-lock"); + expect(current.calls).toContain("npm --prefix"); + expect(current.calls).toContain("ci --ignore-scripts --omit=dev"); + expect(current.calls).toContain("--verify-installed-lock"); expect(current.calls).toContain("postinstall-bundled-plugins.mjs"); - expect(current.calls).toContain( - `openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz`, - ); const newer = runOpenClawUpgradeBlock("2026.6.11"); expect(newer.result.status).toBe(1); From 57c97bf5dc0bf2489ec494d4637977be3986afb8 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Sat, 18 Jul 2026 14:12:05 -0700 Subject: [PATCH 07/16] fix(ci): keep reviewed npm policy trusted Signed-off-by: Ho Lim --- .github/workflows/pr.yaml | 7 ++++-- scripts/audit-reviewed-npm-graph.mts | 13 ++++++++---- test/reviewed-npm-audit-workflow.test.ts | 10 ++++++++- test/reviewed-npm-audit.test.ts | 27 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 32a305658ab..4fec804d66c 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -292,6 +292,7 @@ jobs: persist-credentials: false sparse-checkout: | .github/actions/ci-reviewed-npm-audit + ci/reviewed-npm-audit.json scripts/audit-reviewed-npm-graph.mts scripts/lib/reviewed-npm-archive.mts sparse-checkout-cone-mode: false @@ -302,8 +303,9 @@ jobs: run: | if grep -Fq 'NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT' \ .trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit/action.yaml \ - && grep -Fq 'parsed.schemaVersion !== 2' \ - .trusted-reviewed-npm-audit/scripts/audit-reviewed-npm-graph.mts; then + && grep -Fq 'resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)' \ + .trusted-reviewed-npm-audit/scripts/audit-reviewed-npm-graph.mts \ + && [ -f .trusted-reviewed-npm-audit/ci/reviewed-npm-audit.json ]; then echo "available=true" >> "$GITHUB_OUTPUT" else echo "available=false" >> "$GITHUB_OUTPUT" @@ -321,6 +323,7 @@ jobs: persist-credentials: false sparse-checkout: | .github/actions/ci-reviewed-npm-audit + ci/reviewed-npm-audit.json scripts/audit-reviewed-npm-graph.mts scripts/lib/reviewed-npm-archive.mts sparse-checkout-cone-mode: false diff --git a/scripts/audit-reviewed-npm-graph.mts b/scripts/audit-reviewed-npm-graph.mts index f87ac21dbf9..aaec0f0bdfb 100755 --- a/scripts/audit-reviewed-npm-graph.mts +++ b/scripts/audit-reviewed-npm-graph.mts @@ -35,10 +35,7 @@ const TRUSTED_REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.ur const REPO_ROOT = fs.realpathSync( path.resolve(process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT ?? TRUSTED_REPO_ROOT), ); -const CONFIG_PATH = resolveTargetPath( - "ci/reviewed-npm-audit.json", - "reviewed npm audit configuration", -); +const CONFIG_PATH = resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT); const SEVERITIES: readonly Severity[] = ["info", "low", "moderate", "high", "critical"]; export function resolvePathWithinTargetRoot( @@ -71,6 +68,14 @@ export function resolvePathWithinTargetRoot( return resolved; } +export function resolveTrustedAuditConfigPath(trustedRoot: string): string { + return resolvePathWithinTargetRoot( + trustedRoot, + "ci/reviewed-npm-audit.json", + "trusted reviewed npm audit configuration", + ); +} + function resolveTargetPath(relativePath: string, label: string): string { return resolvePathWithinTargetRoot(REPO_ROOT, relativePath, label); } diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index 57d7759a68a..168aad66fe9 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -55,12 +55,14 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { }); const sparseCheckout = String(trustedCheckout.with?.["sparse-checkout"]); expect(sparseCheckout).toContain(".github/actions/ci-reviewed-npm-audit"); + expect(sparseCheckout).toContain("ci/reviewed-npm-audit.json"); expect(sparseCheckout).toContain("scripts/audit-reviewed-npm-graph.mts"); expect(sparseCheckout).toContain("scripts/lib/reviewed-npm-archive.mts"); const detection = requiredStep(prJob, "Detect trusted reviewed npm audit schema"); expect(detection.id).toBe("trusted-reviewed-npm-audit"); - expect(detection.run).toContain("parsed.schemaVersion !== 2"); + expect(detection.run).toContain("resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)"); + expect(detection.run).toContain(".trusted-reviewed-npm-audit/ci/reviewed-npm-audit.json"); const bootstrap = requiredStep(prJob, "Checkout pinned bootstrap reviewed npm audit"); expect(bootstrap.if).toBe(BOOTSTRAP_IF); @@ -99,6 +101,10 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { path.join(REPO_ROOT, ".github", "actions", "ci-reviewed-npm-audit", "action.yaml"), "utf8", ); + const driver = fs.readFileSync( + path.join(REPO_ROOT, "scripts", "audit-reviewed-npm-graph.mts"), + "utf8", + ); expect(action).toContain('node-version: "22.22.2"'); expect(action).toContain("npm install --global npm@10.9.4"); @@ -108,5 +114,7 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { 'node --experimental-strip-types "$GITHUB_ACTION_PATH/../../../scripts/audit-reviewed-npm-graph.mts"', ); expect(action).not.toContain("run: node --experimental-strip-types scripts/"); + expect(driver).toContain("resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)"); + expect(driver).not.toContain('resolveTargetPath(\n "ci/reviewed-npm-audit.json"'); }); }); diff --git a/test/reviewed-npm-audit.test.ts b/test/reviewed-npm-audit.test.ts index be2ed960b3b..37d15879053 100644 --- a/test/reviewed-npm-audit.test.ts +++ b/test/reviewed-npm-audit.test.ts @@ -9,6 +9,7 @@ import { exceedsAuditThreshold, parseAuditReport, resolvePathWithinTargetRoot, + resolveTrustedAuditConfigPath, vulnerabilityCounts, } from "../scripts/audit-reviewed-npm-graph.mts"; @@ -100,4 +101,30 @@ describe("reviewed npm audit gate", () => { resolvePathWithinTargetRoot(targetRoot, "artifacts/audit", "audit target"), ).toThrow("contains a symbolic-link component"); }); + + it("loads audit policy from the trusted checkout instead of the PR target", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-reviewed-audit-policy-")); + roots.push(root); + const trustedRoot = path.join(root, "trusted"); + const targetRoot = path.join(root, "target"); + fs.mkdirSync(path.join(trustedRoot, "ci"), { recursive: true }); + fs.mkdirSync(path.join(targetRoot, "ci"), { recursive: true }); + fs.writeFileSync( + path.join(trustedRoot, "ci", "reviewed-npm-audit.json"), + JSON.stringify({ lockedGraphs: [{ label: "required" }], severityThreshold: "high" }), + ); + fs.writeFileSync( + path.join(targetRoot, "ci", "reviewed-npm-audit.json"), + JSON.stringify({ lockedGraphs: [], severityThreshold: "critical" }), + ); + + const policyPath = resolveTrustedAuditConfigPath(trustedRoot); + expect(policyPath).toBe( + path.join(fs.realpathSync(trustedRoot), "ci", "reviewed-npm-audit.json"), + ); + expect(JSON.parse(fs.readFileSync(policyPath, "utf8"))).toMatchObject({ + lockedGraphs: [{ label: "required" }], + severityThreshold: "high", + }); + }); }); From ef99fd8b9130d0b13bf7ec12edb7f77f7dfc5353 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Sat, 18 Jul 2026 14:13:54 -0700 Subject: [PATCH 08/16] ci: pin trusted audit policy bootstrap Signed-off-by: Ho Lim --- .github/workflows/pr.yaml | 2 +- test/reviewed-npm-audit-workflow.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 4fec804d66c..8fd9a173669 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -318,7 +318,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: HOYALIM/NemoClaw - ref: f2e670fd059e2032034b8c03ee6d016755c3d2d6 + ref: 57c97bf5dc0bf2489ec494d4637977be3986afb8 path: .trusted-reviewed-npm-audit-bootstrap persist-credentials: false sparse-checkout: | diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index 168aad66fe9..b0ab478bf1f 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -26,7 +26,7 @@ type Workflow = { }; const REPO_ROOT = path.join(import.meta.dirname, ".."); -const BOOTSTRAP_SHA = "f2e670fd059e2032034b8c03ee6d016755c3d2d6"; +const BOOTSTRAP_SHA = "57c97bf5dc0bf2489ec494d4637977be3986afb8"; // Removal condition: delete the PR-6830 fork bootstrap after this PR merges and // the base branch contains the schema-v2 reviewed npm audit action. const BOOTSTRAP_IF = From 5ac30d16343ed5366e76c36564541cefe64ca1ed Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Sat, 18 Jul 2026 15:16:40 -0700 Subject: [PATCH 09/16] test(ci): cover unavailable trusted audit Signed-off-by: Ho Lim --- test/reviewed-npm-audit-workflow.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index b0ab478bf1f..b648120a414 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -31,6 +31,8 @@ const BOOTSTRAP_SHA = "57c97bf5dc0bf2489ec494d4637977be3986afb8"; // the base branch contains the schema-v2 reviewed npm audit action. const BOOTSTRAP_IF = "${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && github.event.pull_request.number == 6830 && github.event.pull_request.head.repo.full_name == 'HOYALIM/NemoClaw' }}"; +const REJECT_UNAVAILABLE_IF = + "${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && (github.event.pull_request.number != 6830 || github.event.pull_request.head.repo.full_name != 'HOYALIM/NemoClaw') }}"; function requiredStep(job: WorkflowJob, name: string): WorkflowStep { const step = job.steps?.find((candidate) => candidate.name === name); @@ -72,6 +74,9 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { path: ".trusted-reviewed-npm-audit-bootstrap", "persist-credentials": false, }); + const rejectUnavailable = requiredStep(prJob, "Reject unavailable trusted reviewed npm audit"); + expect(rejectUnavailable.if).toBe(REJECT_UNAVAILABLE_IF); + expect(rejectUnavailable.run).toContain("exit 1"); expect(requiredStep(prJob, "Audit reviewed production npm graphs")).toMatchObject({ if: "${{ steps.trusted-reviewed-npm-audit.outputs.available == 'true' }}", uses: "./.trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit", From 0c7dd29394d2c4db660c4d09f3654c0789e200d0 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sat, 25 Jul 2026 05:33:37 -0700 Subject: [PATCH 10/16] fix(security): lock remediated OpenClaw runtime graph Signed-off-by: Apurv Kumaria --- Dockerfile | 71 +++- Dockerfile.base | 75 ++-- .../openclaw-runtime/package-lock.json | 332 +++++++++++------- agents/openclaw/openclaw-runtime/package.json | 4 +- ci/reviewed-npm-audit.json | 13 +- scripts/audit-reviewed-npm-graph.mts | 264 ++++++++++++-- scripts/lib/openclaw-npm-remediation.mts | 130 ++++++- scripts/lib/reviewed-npm-archive.mts | 5 + test/openclaw-integrity-pin-suite.ts | 209 +++++++---- test/openclaw-locked-install.test.ts | 34 +- test/openclaw-npm-remediation.test.ts | 102 ++++++ test/reviewed-npm-audit-workflow.test.ts | 71 +++- test/sandbox-build-context.test.ts | 4 +- 13 files changed, 1006 insertions(+), 308 deletions(-) diff --git a/Dockerfile b/Dockerfile index 47b4769c6d9..b7b66580c3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,8 @@ RUN ln -s /opt/nemoclaw/node_modules /opt/nemoclaw-root/node_modules \ # related payloads without invalidating earlier final-image work. FROM scratch AS openclaw-dependency-payload +COPY agents/openclaw/openclaw-runtime/package.json /usr/local/lib/nemoclaw/openclaw-runtime/package.json +COPY agents/openclaw/openclaw-runtime/package-lock.json /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json COPY agents/openclaw/mcporter-runtime/package.json /usr/local/lib/nemoclaw/mcporter-runtime/package.json COPY agents/openclaw/mcporter-runtime/package-lock.json /usr/local/lib/nemoclaw/mcporter-runtime/package-lock.json COPY agents/openclaw/wechat-runtime/package.json /usr/local/lib/nemoclaw/wechat-runtime/package.json @@ -375,8 +377,17 @@ RUN set -eu; \ if [ -z "$EXPECTED_INTEGRITY" ]; then \ echo "ERROR: OpenClaw ${OPENCLAW_VERSION} has no committed npm integrity pin" >&2; exit 1; \ fi; \ + OPENCLAW_LOCK_SHA256=none-legacy-fixture; \ OPENCLAW_RECIPE='ignore-scripts+reviewed-lifecycle-v1'; \ - if [ "$OPENCLAW_VERSION" = "2026.3.11" ]; then OPENCLAW_RECIPE='ignore-scripts+reviewed-lifecycle+transitive-remediation-v1'; fi; \ + if [ "$OPENCLAW_VERSION" = "2026.7.1" ]; then \ + OPENCLAW_LOCK_SHA256=82489f62febb12da52833c0b1f7f6969f7e21a098c565ef1f91342b1e5e32d88; \ + ACTUAL_OPENCLAW_LOCK_SHA256="$(sha256sum /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json | awk '{print $1}')"; \ + [ "$ACTUAL_OPENCLAW_LOCK_SHA256" = "$OPENCLAW_LOCK_SHA256" ] \ + || { echo "ERROR: OpenClaw lock SHA-256 mismatch (expected $OPENCLAW_LOCK_SHA256, found $ACTUAL_OPENCLAW_LOCK_SHA256)" >&2; exit 1; }; \ + OPENCLAW_RECIPE='locked-ci+reviewed-lifecycle-v2'; \ + elif [ "$OPENCLAW_VERSION" = "2026.3.11" ]; then \ + OPENCLAW_RECIPE='ignore-scripts+reviewed-lifecycle+transitive-remediation-v1'; \ + fi; \ MCPORTER_EXPECTED_INTEGRITY=""; \ MCPORTER_EXPECTED_TARBALL=""; \ if [ "$MCPORTER_VERSION" = "0.7.3" ]; then MCPORTER_EXPECTED_INTEGRITY="$MCPORTER_0_7_3_INTEGRITY"; MCPORTER_EXPECTED_TARBALL="$MCPORTER_0_7_3_TARBALL"; fi; \ @@ -404,6 +415,7 @@ RUN set -eu; \ "package=openclaw@${OPENCLAW_VERSION}" \ "integrity=${EXPECTED_INTEGRITY}" \ "tarball=${EXPECTED_TARBALL}" \ + "lock-sha256=${OPENCLAW_LOCK_SHA256}" \ "recipe=${OPENCLAW_RECIPE}" \ "mcporter-package=mcporter@${MCPORTER_VERSION}" \ "mcporter-integrity=${MCPORTER_EXPECTED_INTEGRITY}" \ @@ -437,28 +449,49 @@ RUN set -eu; \ echo "ERROR: Base image has OpenClaw $CUR_VER, which is newer than reviewed target $OPENCLAW_VERSION" >&2; exit 1; \ else \ echo "INFO: Base image OpenClaw $CUR_VER lacks exact reviewed provenance; installing $OPENCLAW_VERSION"; \ - OPENCLAW_SOURCE_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ - --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ - --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")"; \ - OPENCLAW_PACK_PATH="$OPENCLAW_SOURCE_PACK_PATH"; \ - OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ - if [ "$OPENCLAW_VERSION" = "2026.3.11" ]; then \ - OPENCLAW_REMEDIATION_JSON="$(node --experimental-strip-types /scripts/lib/openclaw-npm-remediation.mts \ - --archive "$OPENCLAW_SOURCE_PACK_PATH" --package-spec "openclaw@${OPENCLAW_VERSION}" \ - --working-directory "$OPENCLAW_PACK_DIR")"; \ - OPENCLAW_PACK_PATH="$(node -e 'const value = JSON.parse(process.argv[1]); if (!value.remediated || typeof value.archivePath !== "string") process.exit(1); process.stdout.write(value.archivePath)' "$OPENCLAW_REMEDIATION_JSON")"; \ - fi; \ # npm 10's atomic-move install can hit EROFS on overlayfs when the prior # install spans image layers. Removing it first also prevents unreviewed # files from surviving a same-version reinstall. rm -rf /usr/local/lib/node_modules/openclaw /usr/local/bin/openclaw; \ - npm install -g --no-audit --no-fund --no-progress --ignore-scripts "$OPENCLAW_PACK_PATH"; \ - case "$OPENCLAW_VERSION" in \ - 2026.4.24|2026.7.1) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ - 2026.3.11) ;; \ - *) echo "ERROR: OpenClaw ${OPENCLAW_VERSION} has no reviewed lifecycle policy" >&2; exit 1 ;; \ - esac; \ - rm -rf "$OPENCLAW_PACK_DIR"; \ + if [ "$OPENCLAW_VERSION" = "2026.7.1" ]; then \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts --verify-lock \ + --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --registry-origin https://registry.npmjs.org/ \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}"; \ + npm --prefix /usr/local/lib/nemoclaw/openclaw-runtime ci \ + --ignore-scripts --omit=dev --no-audit --no-fund --no-progress \ + --userconfig /dev/null --registry https://registry.npmjs.org/; \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --verify-installed-lock --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --install-root /usr/local/lib/nemoclaw/openclaw-runtime \ + --label "OpenClaw ${OPENCLAW_VERSION}"; \ + node /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs; \ + mkdir -p /usr/local/lib/node_modules; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw /usr/local/lib/node_modules/openclaw; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/.bin/openclaw /usr/local/bin/openclaw; \ + else \ + OPENCLAW_SOURCE_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")"; \ + OPENCLAW_PACK_PATH="$OPENCLAW_SOURCE_PACK_PATH"; \ + OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ + if [ "$OPENCLAW_VERSION" = "2026.3.11" ]; then \ + OPENCLAW_REMEDIATION_JSON="$(node --experimental-strip-types /scripts/lib/openclaw-npm-remediation.mts \ + --archive "$OPENCLAW_SOURCE_PACK_PATH" --package-spec "openclaw@${OPENCLAW_VERSION}" \ + --working-directory "$OPENCLAW_PACK_DIR")"; \ + OPENCLAW_PACK_PATH="$(node -e 'const value = JSON.parse(process.argv[1]); if (!value.remediated || typeof value.archivePath !== "string") process.exit(1); process.stdout.write(value.archivePath)' "$OPENCLAW_REMEDIATION_JSON")"; \ + fi; \ + npm install -g --no-audit --no-fund --no-progress --ignore-scripts "$OPENCLAW_PACK_PATH"; \ + case "$OPENCLAW_VERSION" in \ + 2026.4.24) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ + 2026.3.11) ;; \ + *) echo "ERROR: OpenClaw ${OPENCLAW_VERSION} has no reviewed lifecycle policy" >&2; exit 1 ;; \ + esac; \ + rm -rf "$OPENCLAW_PACK_DIR"; \ + fi; \ fi; \ case "$OPENCLAW_VERSION" in \ 2026.3.11) npm ls -g --depth=1 openclaw tar >/dev/null ;; \ diff --git a/Dockerfile.base b/Dockerfile.base index a98b219ac4a..efc4a8b7701 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -378,6 +378,8 @@ ARG OPENCLAW_2026_4_24_TARBALL=https://registry.npmjs.org/openclaw/-/openclaw-20 ARG MCPORTER_VERSION=0.7.3 ARG MCPORTER_0_7_3_INTEGRITY=sha512-egoPVYqTnWb3NjRIxo+xc8OrAI0dlPrJm9pAiZx0pImuNIV5rKhGtTnIfH/Y1ldGPVu74ibj3KR5c9U/QSdQFA== ARG MCPORTER_0_7_3_TARBALL=https://registry.npmjs.org/mcporter/-/mcporter-0.7.3.tgz +COPY agents/openclaw/openclaw-runtime/package.json /usr/local/lib/nemoclaw/openclaw-runtime/package.json +COPY agents/openclaw/openclaw-runtime/package-lock.json /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json COPY agents/openclaw/mcporter-runtime/package.json /usr/local/lib/nemoclaw/mcporter-runtime/package.json COPY agents/openclaw/mcporter-runtime/package-lock.json /usr/local/lib/nemoclaw/mcporter-runtime/package-lock.json COPY ci/npm-audit-exceptions.json /scripts/npm-audit-exceptions.json @@ -438,30 +440,58 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep if [ -z "$EXPECTED_INTEGRITY" ]; then \ echo "Error: OpenClaw ${OPENCLAW_VERSION} has no committed npm integrity pin"; exit 1; \ fi; \ - OPENCLAW_SOURCE_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ - --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ - --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")"; \ - if [ -z "$OPENCLAW_SOURCE_PACK_PATH" ] || [ ! -f "$OPENCLAW_SOURCE_PACK_PATH" ] || [ -L "$OPENCLAW_SOURCE_PACK_PATH" ]; then \ - echo "Error: reviewed OpenClaw archive path is empty or invalid"; exit 1; \ - fi; \ - OPENCLAW_PACK_PATH="$OPENCLAW_SOURCE_PACK_PATH"; \ - OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ + OPENCLAW_LOCK_SHA256=none-legacy-fixture; \ OPENCLAW_RECIPE='ignore-scripts+reviewed-lifecycle-v1'; \ - if [ "$OPENCLAW_VERSION" = "2026.3.11" ]; then \ - OPENCLAW_REMEDIATION_JSON="$(node --experimental-strip-types /scripts/lib/openclaw-npm-remediation.mts \ - --archive "$OPENCLAW_SOURCE_PACK_PATH" --package-spec "openclaw@${OPENCLAW_VERSION}" \ - --working-directory "$OPENCLAW_PACK_DIR")"; \ - OPENCLAW_PACK_PATH="$(node -e 'const value = JSON.parse(process.argv[1]); if (!value.remediated || typeof value.archivePath !== "string") process.exit(1); process.stdout.write(value.archivePath)' "$OPENCLAW_REMEDIATION_JSON")"; \ - OPENCLAW_RECIPE='ignore-scripts+reviewed-lifecycle+transitive-remediation-v1'; \ + if [ "$OPENCLAW_VERSION" = "2026.7.1" ]; then \ + OPENCLAW_LOCK_SHA256=82489f62febb12da52833c0b1f7f6969f7e21a098c565ef1f91342b1e5e32d88; \ + ACTUAL_OPENCLAW_LOCK_SHA256="$(sha256sum /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json | awk '{print $1}')"; \ + [ "$ACTUAL_OPENCLAW_LOCK_SHA256" = "$OPENCLAW_LOCK_SHA256" ] \ + || { echo "Error: OpenClaw lock SHA-256 mismatch (expected $OPENCLAW_LOCK_SHA256, found $ACTUAL_OPENCLAW_LOCK_SHA256)"; exit 1; }; \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts --verify-lock \ + --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --registry-origin https://registry.npmjs.org/ \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}"; \ + rm -rf /usr/local/lib/node_modules/openclaw /usr/local/bin/openclaw; \ + npm --prefix /usr/local/lib/nemoclaw/openclaw-runtime ci \ + --ignore-scripts --omit=dev --no-audit --no-fund --no-progress \ + --userconfig /dev/null --registry https://registry.npmjs.org/; \ + node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --verify-installed-lock --lock-sha256 "$OPENCLAW_LOCK_SHA256" \ + --lockfile /usr/local/lib/nemoclaw/openclaw-runtime/package-lock.json \ + --install-root /usr/local/lib/nemoclaw/openclaw-runtime \ + --label "OpenClaw ${OPENCLAW_VERSION}"; \ + node /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs; \ + mkdir -p /usr/local/lib/node_modules; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw /usr/local/lib/node_modules/openclaw; \ + ln -s /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/.bin/openclaw /usr/local/bin/openclaw; \ + OPENCLAW_RECIPE='locked-ci+reviewed-lifecycle-v2'; \ + else \ + OPENCLAW_SOURCE_PACK_PATH="$(node --experimental-strip-types /scripts/lib/reviewed-npm-archive.mts \ + --package-spec "openclaw@${OPENCLAW_VERSION}" --integrity "$EXPECTED_INTEGRITY" \ + --tarball-url "$EXPECTED_TARBALL" --label "OpenClaw ${OPENCLAW_VERSION}")"; \ + if [ -z "$OPENCLAW_SOURCE_PACK_PATH" ] || [ ! -f "$OPENCLAW_SOURCE_PACK_PATH" ] || [ -L "$OPENCLAW_SOURCE_PACK_PATH" ]; then \ + echo "Error: reviewed OpenClaw archive path is empty or invalid"; exit 1; \ + fi; \ + OPENCLAW_PACK_PATH="$OPENCLAW_SOURCE_PACK_PATH"; \ + OPENCLAW_PACK_DIR="$(dirname "$OPENCLAW_PACK_PATH")"; \ + if [ "$OPENCLAW_VERSION" = "2026.3.11" ]; then \ + OPENCLAW_REMEDIATION_JSON="$(node --experimental-strip-types /scripts/lib/openclaw-npm-remediation.mts \ + --archive "$OPENCLAW_SOURCE_PACK_PATH" --package-spec "openclaw@${OPENCLAW_VERSION}" \ + --working-directory "$OPENCLAW_PACK_DIR")"; \ + OPENCLAW_PACK_PATH="$(node -e 'const value = JSON.parse(process.argv[1]); if (!value.remediated || typeof value.archivePath !== "string") process.exit(1); process.stdout.write(value.archivePath)' "$OPENCLAW_REMEDIATION_JSON")"; \ + OPENCLAW_RECIPE='ignore-scripts+reviewed-lifecycle+transitive-remediation-v1'; \ + fi; \ + npm install -g --ignore-scripts "$OPENCLAW_PACK_PATH"; \ + case "$OPENCLAW_VERSION" in \ + 2026.4.24) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ + 2026.3.11) ;; \ + *) echo "Error: OpenClaw ${OPENCLAW_VERSION} has no reviewed lifecycle policy"; exit 1 ;; \ + esac; \ + rm -rf "$OPENCLAW_PACK_DIR"; \ fi; \ - npm install -g --ignore-scripts "$OPENCLAW_PACK_PATH" \ - && case "$OPENCLAW_VERSION" in \ - 2026.4.24|2026.7.1) node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs ;; \ - 2026.3.11) ;; \ - *) echo "Error: OpenClaw ${OPENCLAW_VERSION} has no reviewed lifecycle policy"; exit 1 ;; \ - esac \ - && rm -rf "$OPENCLAW_PACK_DIR" \ - && OPENCLAW_INSTALLED_VERSION="$(openclaw --version 2>/dev/null | awk '{print $2}')" \ + OPENCLAW_INSTALLED_VERSION="$(openclaw --version 2>/dev/null | awk '{print $2}')" \ && if [ "$OPENCLAW_INSTALLED_VERSION" != "$OPENCLAW_VERSION" ]; then \ echo "Error: Installed OpenClaw ${OPENCLAW_INSTALLED_VERSION:-unknown} does not match reviewed target ${OPENCLAW_VERSION}"; exit 1; \ fi \ @@ -506,6 +536,7 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep "package=openclaw@${OPENCLAW_VERSION}" \ "integrity=${EXPECTED_INTEGRITY}" \ "tarball=${EXPECTED_TARBALL}" \ + "lock-sha256=${OPENCLAW_LOCK_SHA256}" \ "recipe=${OPENCLAW_RECIPE}" \ "mcporter-package=mcporter@${MCPORTER_VERSION}" \ "mcporter-integrity=${MCPORTER_EXPECTED_INTEGRITY}" \ diff --git a/agents/openclaw/openclaw-runtime/package-lock.json b/agents/openclaw/openclaw-runtime/package-lock.json index a4aa84e5713..7cb2f69b89f 100644 --- a/agents/openclaw/openclaw-runtime/package-lock.json +++ b/agents/openclaw/openclaw-runtime/package-lock.json @@ -9,46 +9,47 @@ "version": "0.0.0", "license": "Apache-2.0", "dependencies": { - "openclaw": "2026.6.10" + "openclaw": "2026.7.1" }, "engines": { - "node": ">=22.19.0" + "node": ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0" } }, "node_modules/openclaw": { - "version": "2026.6.10", - "resolved": "https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz", - "integrity": "sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug==", + "version": "2026.7.1", + "resolved": "https://registry.npmjs.org/openclaw/-/openclaw-2026.7.1.tgz", + "integrity": "sha512-ge/Xss99CHAjPL/ikmH/UFoiOrjcxDB4sW3y9mhyCD+dYW3wzV7TKbAVdkrXFgAG2d2BjpJofP97zUZ+umxo8g==", "hasInstallScript": true, - "hasShrinkwrap": true, "license": "MIT", "dependencies": { - "@agentclientprotocol/sdk": "0.22.1", - "@anthropic-ai/sdk": "0.100.1", - "@clack/core": "1.3.1", - "@clack/prompts": "1.4.0", - "@earendil-works/pi-tui": "0.78.0", - "@google/genai": "2.7.0", + "@agentclientprotocol/sdk": "1.1.0", + "@anthropic-ai/sdk": "0.109.1", + "@clack/core": "1.4.2", + "@clack/prompts": "1.6.0", + "@earendil-works/pi-tui": "0.80.3", + "@google/genai": "2.10.0", "@grammyjs/runner": "2.0.3", "@grammyjs/transformer-throttler": "1.2.1", "@homebridge/ciao": "1.3.9", "@lydell/node-pty": "1.2.0-beta.12", - "@mistralai/mistralai": "2.2.5", + "@mistralai/mistralai": "2.4.0", "@modelcontextprotocol/sdk": "1.29.0", "@mozilla/readability": "0.6.0", - "@openclaw/fs-safe": "0.3.0", + "@openclaw/ai": "2026.7.1", + "@openclaw/fs-safe": "0.4.1", "@openclaw/proxyline": "0.3.3", + "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "chokidar": "5.0.0", "clawpdf": "0.3.0", - "commander": "14.0.3", + "commander": "15.0.0", "croner": "10.0.1", "diff": "9.0.0", "dotenv": "17.4.2", "express": "5.2.1", "file-type": "22.0.1", "glob": "13.0.6", - "grammy": "1.43.0", + "grammy": "1.44.0", "highlight.js": "11.11.1", "hosted-git-info": "10.1.1", "ignore": "7.0.5", @@ -59,21 +60,21 @@ "linkedom": "0.18.12", "minimatch": "10.2.5", "node-edge-tts": "1.2.10", - "openai": "6.39.1", + "openai": "6.45.0", "partial-json": "0.1.7", - "playwright-core": "1.60.0", + "playwright-core": "1.61.1", "proper-lockfile": "4.1.2", "qrcode": "1.5.4", - "quickjs-wasi": "3.0.0", + "quickjs-wasi": "3.0.2", "rastermill": "0.3.1", - "tar": "7.5.16", + "tar": "7.5.19", "tree-sitter-bash": "0.25.1", "tslog": "4.10.2", - "typebox": "1.1.39", + "typebox": "1.3.3", "typescript": "6.0.3", "undici": "8.5.0", "web-push": "3.6.7", - "web-tree-sitter": "0.26.9", + "web-tree-sitter": "0.26.10", "ws": "8.21.0", "yaml": "2.9.0", "zod": "4.4.3" @@ -82,25 +83,25 @@ "openclaw": "openclaw.mjs" }, "engines": { - "node": ">=22.19.0" + "node": ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0" }, "optionalDependencies": { "sqlite-vec": "0.1.9" } }, "node_modules/openclaw/node_modules/@agentclientprotocol/sdk": { - "version": "0.22.1", - "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.22.1.tgz", - "integrity": "sha512-DfqXtl/8gO9NImq094MTaCXEU2vkhh6v7q/kT+9UjZxUqj8hYaya2OjLVIqn16MzNHcXEpShTR2RIauLSYeDQQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.1.0.tgz", + "integrity": "sha512-NT2KqphUJ3w6EksUL51ZhJgIYgq/ZLGcBPkyMKgRSO5PMVwe9DnKKX+Htnvk6KHh6dUuh34UHK4gKp+4te1Mdg==", "license": "Apache-2.0", "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "node_modules/openclaw/node_modules/@anthropic-ai/sdk": { - "version": "0.100.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.100.1.tgz", - "integrity": "sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==", + "version": "0.109.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.109.1.tgz", + "integrity": "sha512-q9OnEKLr5H9nxSuXdgDgJhxfYMiE+AaUEBze2Gk91UcaaLnsN+Lx5fbCYywiqurU/APLdwv23x03Wm6WN3EBsg==", "license": "MIT", "dependencies": { "json-schema-to-ts": "^3.1.1", @@ -138,9 +139,9 @@ } }, "node_modules/openclaw/node_modules/@clack/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.3.1.tgz", - "integrity": "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.2.tgz", + "integrity": "sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==", "license": "MIT", "dependencies": { "fast-wrap-ansi": "^0.2.0", @@ -151,12 +152,12 @@ } }, "node_modules/openclaw/node_modules/@clack/prompts": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.4.0.tgz", - "integrity": "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.6.0.tgz", + "integrity": "sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==", "license": "MIT", "dependencies": { - "@clack/core": "1.3.1", + "@clack/core": "1.4.2", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" @@ -166,22 +167,22 @@ } }, "node_modules/openclaw/node_modules/@earendil-works/pi-tui": { - "version": "0.78.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.78.0.tgz", - "integrity": "sha512-3a705FnsVVUhAyceShNB3kS2rpxcxLcx+hqB0u6MMMpHwQGbW+m++MqA6r7eOzq/8FLx5e3vDh38h/SVTk2qzw==", + "version": "0.80.3", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.3.tgz", + "integrity": "sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==", "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", - "marked": "15.0.12" + "marked": "18.0.5" }, "engines": { "node": ">=22.19.0" } }, "node_modules/openclaw/node_modules/@google/genai": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.7.0.tgz", - "integrity": "sha512-tv0DRtcndt2oEhBYy+5mA0TaXH98+L1Gt0AP9unBfH7DP20KhB7+O3QqAN1Lz+laMARGTHS7BFQSNpLbl4gm1g==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.10.0.tgz", + "integrity": "sha512-e4cFxj3tiuMtsgOT4G9c1hXyGJhg7/Buj7VVeBacRY3fRtkRZZ59Q3nuVp2xbq8BGQXLXCDB253qMhklMOeUDg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -233,9 +234,9 @@ } }, "node_modules/openclaw/node_modules/@grammyjs/types": { - "version": "3.27.3", - "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", - "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.28.0.tgz", + "integrity": "sha512-4JvXCdxRZHCje0M4gHzLwtB4bLno3WD28xd8CNfk4POWIu73BFnSvGeW6OQ5gPem4eYTEwkD9yDaXssixl6tMQ==", "license": "MIT" }, "node_modules/openclaw/node_modules/@homebridge/ciao": { @@ -370,14 +371,23 @@ ] }, "node_modules/openclaw/node_modules/@mistralai/mistralai": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.5.tgz", - "integrity": "sha512-ATbWzKkNzNAZ+gtw9MI/c/ULTMG80tKUiRNIbQFfg4OP0uEZZpTfXZeBCNfs5Dq0uqMQ/tQWc4o6RRJQtMrpDA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.4.0.tgz", + "integrity": "sha512-t6hCx242MTGolB76CI+17jDtPIe/bzLsMdUTMMoMn9Qo1h02N2G5jQYHmKDGU3X//OgR2wvngTD7tO6tPp5poQ==", "license": "Apache-2.0", "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } } }, "node_modules/openclaw/node_modules/@modelcontextprotocol/sdk": { @@ -429,17 +439,34 @@ "node": ">=14.0.0" } }, + "node_modules/openclaw/node_modules/@openclaw/ai": { + "version": "2026.7.1", + "resolved": "https://registry.npmjs.org/@openclaw/ai/-/ai-2026.7.1.tgz", + "integrity": "sha512-FsKy5DXSHf4qyN8Huoz/10HZRgoEwLF4uk8UWaCafaIler+q5Fsl51HcrIqIrEe0S38OT7LOaxnR++MOshAlmw==", + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.109.1", + "@google/genai": "2.10.0", + "@mistralai/mistralai": "2.4.0", + "openai": "6.45.0", + "partial-json": "0.1.7", + "typebox": "1.3.3" + }, + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/openclaw/node_modules/@openclaw/fs-safe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.3.0.tgz", - "integrity": "sha512-uIBE441CIt1kIURoP9qRGKZ8LkGyfD9ZzeESjwAd29ZPWtghws/5GR3Pjb67jKdcJHP1I6roNXcvnhzAU7lHlA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.4.1.tgz", + "integrity": "sha512-hQi+BxO10KdRFlYUot1syC+hTaUnGeQNdqX5kwkKJig8CFq1tKsYJLPm+zkiiGsSKOprPAquQl/txejEhpKPgg==", "license": "MIT", "engines": { - "node": ">=20.11" + "node": ">=22" }, "optionalDependencies": { "jszip": "^3.10.1", - "tar": "7.5.13" + "tar": "7.5.19" } }, "node_modules/openclaw/node_modules/@openclaw/proxyline": { @@ -454,6 +481,15 @@ "undici": ">=8.3.0 <9" } }, + "node_modules/openclaw/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/openclaw/node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -553,20 +589,14 @@ "license": "MIT" }, "node_modules/openclaw/node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, - "node_modules/openclaw/node_modules/@types/retry": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", - "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", - "license": "MIT" - }, "node_modules/openclaw/node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -709,27 +739,40 @@ } }, "node_modules/openclaw/node_modules/bn.js": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", - "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "version": "4.12.4", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.4.tgz", + "integrity": "sha512-njR1b+ixG2ufvL9Zn9JGneW+b5GV6jqpYyPPpg4QVt723b5kJPGUczkUyWEH9BwEA74UakJZ43I4FDLBF7ci0g==", "license": "MIT" }, "node_modules/openclaw/node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -751,9 +794,9 @@ "license": "MIT" }, "node_modules/openclaw/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -902,12 +945,12 @@ "license": "MIT" }, "node_modules/openclaw/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.12.0" } }, "node_modules/openclaw/node_modules/content-disposition": { @@ -1401,9 +1444,9 @@ } }, "node_modules/openclaw/node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "funding": [ { "type": "github", @@ -1643,9 +1686,9 @@ } }, "node_modules/openclaw/node_modules/google-auth-library": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.7.0.tgz", - "integrity": "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", + "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -1687,12 +1730,12 @@ "license": "ISC" }, "node_modules/openclaw/node_modules/grammy": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.43.0.tgz", - "integrity": "sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.44.0.tgz", + "integrity": "sha512-gGVykS5+c5f1tPV97LuU6IDRMawE2NzpwM9pNz58HQ35IZXDnYL3VOLvNzYognPSeBIOSzQXRu5w96V0aY8y8A==", "license": "MIT", "dependencies": { - "@grammyjs/types": "3.27.3", + "@grammyjs/types": "3.28.0", "abort-controller": "^3.0.0", "debug": "^4.4.3", "node-fetch": "^2.7.0" @@ -2123,15 +2166,15 @@ } }, "node_modules/openclaw/node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/openclaw/node_modules/math-intrinsics": { @@ -2256,9 +2299,9 @@ } }, "node_modules/openclaw/node_modules/node-addon-api": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", - "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz", + "integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" @@ -2372,18 +2415,27 @@ } }, "node_modules/openclaw/node_modules/openai": { - "version": "6.39.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.39.1.tgz", - "integrity": "sha512-z3dO9fEWOXBzlXynVb/xZ/tujzUjFWQWn3C0n0mw6Vo0zJTbEkaN4b2cLWjhJ6haJQx8LlREoafHRl+Gu/Hl+A==", + "version": "6.45.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.45.0.tgz", + "integrity": "sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==", "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, "ws": { "optional": true }, @@ -2432,6 +2484,12 @@ "node": ">=8" } }, + "node_modules/openclaw/node_modules/p-retry/node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, "node_modules/openclaw/node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -2516,9 +2574,9 @@ } }, "node_modules/openclaw/node_modules/playwright-core": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", - "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -2698,18 +2756,22 @@ } }, "node_modules/openclaw/node_modules/quickjs-wasi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.0.0.tgz", - "integrity": "sha512-X7ouKC4ZVf9bXQ8rsE7+L6TeBbesejAJH61x16xRaGAQGfBHHRcniWgzJZZVtHc8rS9yVsY+Tvk8/usAosg4bg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.0.2.tgz", + "integrity": "sha512-SyfPzlrfz67/kv0SogmQgW4c2I1klkLcbvj9Y2gc1h7+VylmvuGevFljLXibGKajKJKiJV29d4S6FQLA6Sc80A==", "license": "MIT" }, "node_modules/openclaw/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/openclaw/node_modules/rastermill": { @@ -2933,14 +2995,14 @@ } }, "node_modules/openclaw/node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -3191,9 +3253,9 @@ } }, "node_modules/openclaw/node_modules/tar": { - "version": "7.5.16", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", - "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "version": "7.5.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", + "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -3314,9 +3376,9 @@ } }, "node_modules/openclaw/node_modules/typebox": { - "version": "1.1.39", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.39.tgz", - "integrity": "sha512-vj0afVtOfLQvv0GR0VxVagYxsXN64btL7Z9XoaG0ZggH3mruMMkOO6hXdgMsjCY3shZgEvooAWVeznQVs5c43w==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.3.tgz", + "integrity": "sha512-URXGUE31PJDQC+PtRMJeLdF4kmmOdFoVPikPCtV2oOIhUpNpppEdIz7W8bH8cFYPYHdDpaRvqwdegMTmHliudg==", "license": "MIT" }, "node_modules/openclaw/node_modules/typescript": { @@ -3360,9 +3422,9 @@ } }, "node_modules/openclaw/node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "license": "MIT" }, "node_modules/openclaw/node_modules/unpipe": { @@ -3418,9 +3480,9 @@ } }, "node_modules/openclaw/node_modules/web-tree-sitter": { - "version": "0.26.9", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.9.tgz", - "integrity": "sha512-YJwSHANl6XFgeEjB8nitgj0qZYt5gkIesJ4w2srS2wcLB4GUa4xcOkM0YaMsU6WNR53YVIkDSY7Ej4pf3IXtCA==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.10.tgz", + "integrity": "sha512-vengBGYS7FpAerkR3o04oBL4L8MkVmjawK50AFBu7v0HZBkmF9ZavPGKoXLSSmRhp7T/YgsJ7joAS3yAxHPEqQ==", "license": "MIT" }, "node_modules/openclaw/node_modules/webidl-conversions": { @@ -3538,9 +3600,9 @@ } }, "node_modules/openclaw/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", diff --git a/agents/openclaw/openclaw-runtime/package.json b/agents/openclaw/openclaw-runtime/package.json index 933ee5944b8..d651034e53a 100644 --- a/agents/openclaw/openclaw-runtime/package.json +++ b/agents/openclaw/openclaw-runtime/package.json @@ -6,9 +6,9 @@ "description": "Locked production dependency graph for the OpenClaw runtime", "license": "Apache-2.0", "dependencies": { - "openclaw": "2026.6.10" + "openclaw": "2026.7.1" }, "engines": { - "node": ">=22.19.0" + "node": ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0" } } diff --git a/ci/reviewed-npm-audit.json b/ci/reviewed-npm-audit.json index d6ccb358d38..d57d87bc81e 100644 --- a/ci/reviewed-npm-audit.json +++ b/ci/reviewed-npm-audit.json @@ -1,6 +1,7 @@ { "schemaVersion": 2, "nodeVersion": "22.23.1", + "registryOrigin": "https://registry.npmjs.org/", "severityThreshold": "high", "exceptionFile": "ci/npm-audit-exceptions.json", "archiveGraphId": "reviewed-archive-graph", @@ -62,13 +63,23 @@ } ], "lockedGraphs": [ + { + "id": "openclaw-runtime", + "label": "OpenClaw 2026.7.1 locked runtime graph", + "packageSpec": "openclaw@2026.7.1", + "integrity": "sha512-ge/Xss99CHAjPL/ikmH/UFoiOrjcxDB4sW3y9mhyCD+dYW3wzV7TKbAVdkrXFgAG2d2BjpJofP97zUZ+umxo8g==", + "tarballUrl": "https://registry.npmjs.org/openclaw/-/openclaw-2026.7.1.tgz", + "directory": "agents/openclaw/openclaw-runtime", + "lockSha256": "82489f62febb12da52833c0b1f7f6969f7e21a098c565ef1f91342b1e5e32d88" + }, { "id": "mcporter-runtime", "label": "mcporter 0.7.3 locked runtime graph", "packageSpec": "mcporter@0.7.3", "integrity": "sha512-egoPVYqTnWb3NjRIxo+xc8OrAI0dlPrJm9pAiZx0pImuNIV5rKhGtTnIfH/Y1ldGPVu74ibj3KR5c9U/QSdQFA==", "tarballUrl": "https://registry.npmjs.org/mcporter/-/mcporter-0.7.3.tgz", - "directory": "agents/openclaw/mcporter-runtime" + "directory": "agents/openclaw/mcporter-runtime", + "lockSha256": "c31959d7950903f7477ca2e143b3f1f4adfd10f1961fe97db40cd72f62b84830" } ] } diff --git a/scripts/audit-reviewed-npm-graph.mts b/scripts/audit-reviewed-npm-graph.mts index 3d4bd80e329..ade794d16ba 100755 --- a/scripts/audit-reviewed-npm-graph.mts +++ b/scripts/audit-reviewed-npm-graph.mts @@ -8,7 +8,12 @@ import os from "node:os"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { remediateReviewedOpenClawPluginArchive } from "./lib/openclaw-npm-remediation.mts"; -import { packReviewedNpmArchive, verifyReviewedNpmMetadata } from "./lib/reviewed-npm-archive.mts"; +import { + packReviewedNpmArchive, + verifyInstalledNpmLock, + verifyReviewedNpmLock, + verifyReviewedNpmMetadata, +} from "./lib/reviewed-npm-archive.mts"; import { assertExceptionGraphs, readAuditExceptionRegistry, @@ -22,7 +27,8 @@ type ReviewedPackage = Readonly<{ packageSpec: string; tarballUrl: string; }>; -type LockedGraph = ReviewedPackage & Readonly<{ directory: string; id: string }>; +type LockedGraph = ReviewedPackage & + Readonly<{ directory: string; id: string; lockSha256: string }>; type AuditConfig = Readonly<{ archivePackages: readonly ReviewedPackage[]; archiveGraphId: string; @@ -30,26 +36,71 @@ type AuditConfig = Readonly<{ exceptionFile: string; lockedGraphs: readonly LockedGraph[]; nodeVersion: string; + registryOrigin: string; schemaVersion: 2; severityThreshold: Severity; }>; -const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const CONFIG_PATH = path.join(REPO_ROOT, "ci", "reviewed-npm-audit.json"); +const TRUSTED_REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const TARGET_REPO_ROOT = fs.realpathSync( + path.resolve(process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT ?? TRUSTED_REPO_ROOT), +); +const CONFIG_PATH = resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT); const SEVERITIES: readonly Severity[] = ["info", "low", "moderate", "high", "critical"]; +const OPENCLAW_DOMEXCEPTION_ALIAS = { + actualName: "@nolyfill/domexception", + aliasPackagePath: "node_modules/openclaw/node_modules/node-domexception", + actualPackagePath: "node_modules/openclaw/node_modules/@nolyfill/domexception", + integrity: + "sha512-tlc/FcYIv5i8RYsl2iDil4A0gOihaas1R5jPcIC4Zw3GhjKsVilw90aHcVlhZPTBLGBzd379S+VcnsDjd9ChiA==", + requesterPackagePath: "node_modules/openclaw/node_modules/fetch-blob", + requestedRange: "^1.0.0", + resolved: "https://registry.npmjs.org/@nolyfill/domexception/-/domexception-1.0.28.tgz", + version: "1.0.28", +} as const; -function repositoryPath(relativePath: string, label: string): string { - const resolved = path.resolve(REPO_ROOT, relativePath); - if ( - !relativePath || - path.isAbsolute(relativePath) || - !resolved.startsWith(`${REPO_ROOT}${path.sep}`) - ) { - throw new Error(`${label} must stay inside the repository`); +export function resolvePathWithinRoot(root: string, relativePath: string, label: string): string { + if (!relativePath || path.isAbsolute(relativePath)) { + throw new Error(`${label} must be a nonempty relative path`); + } + const canonicalRoot = fs.realpathSync(path.resolve(root)); + const resolved = path.resolve(canonicalRoot, relativePath); + if (!resolved.startsWith(`${canonicalRoot}${path.sep}`)) { + throw new Error(`${label} escapes its repository root: ${relativePath}`); + } + let current = canonicalRoot; + for (const component of path.relative(canonicalRoot, resolved).split(path.sep)) { + current = path.join(current, component); + let stat: fs.Stats; + try { + stat = fs.lstatSync(current); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") break; + throw error; + } + if (stat.isSymbolicLink()) { + throw new Error(`${label} contains a symbolic-link component: ${relativePath}`); + } } return resolved; } +export function resolveTrustedAuditConfigPath(trustedRoot: string): string { + return resolvePathWithinRoot( + trustedRoot, + "ci/reviewed-npm-audit.json", + "trusted reviewed npm audit configuration", + ); +} + +function trustedRepositoryPath(relativePath: string, label: string): string { + return resolvePathWithinRoot(TRUSTED_REPO_ROOT, relativePath, label); +} + +function targetRepositoryPath(relativePath: string, label: string): string { + return resolvePathWithinRoot(TARGET_REPO_ROOT, relativePath, label); +} + function run(command: string, args: readonly string[], cwd: string) { const result = spawnSync(command, args, { cwd, @@ -74,10 +125,18 @@ function readConfig(): AuditConfig { !parsed.archiveGraphId || typeof parsed.exceptionFile !== "string" || !parsed.exceptionFile || + typeof parsed.registryOrigin !== "string" || + !parsed.registryOrigin || !Array.isArray(parsed.archivePackages) || !Array.isArray(parsed.lockedGraphs) || parsed.lockedGraphs.some( - (graph) => typeof graph.id !== "string" || !graph.id || typeof graph.directory !== "string", + (graph) => + typeof graph.id !== "string" || + !graph.id || + typeof graph.directory !== "string" || + !graph.directory || + typeof graph.lockSha256 !== "string" || + !/^[0-9a-f]{64}$/.test(graph.lockSha256), ) ) { throw new Error("ci/reviewed-npm-audit.json is invalid"); @@ -121,31 +180,170 @@ function materializeArchiveGraph(packages: readonly ReviewedPackage[], tempRoot: return graphDirectory; } -function materializeLockedGraph(graph: LockedGraph, tempRoot: string): string { - verifyReviewedNpmMetadata({ +function materializeLockedGraph( + graph: LockedGraph, + tempRoot: string, + registryOrigin: string, +): string { + const sourcePackage = targetRepositoryPath( + path.join(graph.directory, "package.json"), + `${graph.label} package manifest`, + ); + const sourceLock = targetRepositoryPath( + path.join(graph.directory, "package-lock.json"), + `${graph.label} lockfile`, + ); + verifyReviewedNpmLock({ expectedIntegrity: graph.integrity, + expectedLockSha256: graph.lockSha256, label: graph.label, + lockfilePath: sourceLock, packageSpec: graph.packageSpec, + registryOrigin, tarballUrl: graph.tarballUrl, }); - const source = repositoryPath(graph.directory, `${graph.label} directory`); const destination = path.join(tempRoot, `locked-${path.basename(graph.directory)}`); fs.mkdirSync(destination); - for (const filename of ["package.json", "package-lock.json"]) { - fs.copyFileSync(path.join(source, filename), path.join(destination, filename)); - } + fs.copyFileSync(sourcePackage, path.join(destination, "package.json")); + fs.copyFileSync(sourceLock, path.join(destination, "package-lock.json")); run("npm", ["ci", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund"], destination); + verifyInstalledNpmLock({ + expectedLockSha256: graph.lockSha256, + installRoot: destination, + label: graph.label, + lockfilePath: path.join(destination, "package-lock.json"), + }); return destination; } +function readJsonObject(file: string, label: string): Record { + const parsed = JSON.parse(fs.readFileSync(file, "utf-8")) as unknown; + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error(`${label} must be a JSON object`); + } + return parsed as Record; +} + +function assertRegularFile(file: string, label: string): void { + const stat = fs.lstatSync(file); + if (!stat.isFile() || stat.isSymbolicLink()) { + throw new Error(`${label} must be a regular file`); + } +} + +export function normalizeOpenClawSignatureAlias(directory: string): void { + const { + actualName, + actualPackagePath, + aliasPackagePath, + integrity, + requesterPackagePath, + requestedRange, + resolved, + version, + } = OPENCLAW_DOMEXCEPTION_ALIAS; + const lockfile = path.join(directory, "package-lock.json"); + const aliasDirectory = path.join(directory, aliasPackagePath); + const actualDirectory = path.join(directory, actualPackagePath); + const aliasManifestFile = path.join(aliasDirectory, "package.json"); + const requesterManifestFile = path.join(directory, requesterPackagePath, "package.json"); + for (const [file, label] of [ + [lockfile, "OpenClaw signature-audit lock"], + [aliasManifestFile, "OpenClaw aliased package manifest"], + [requesterManifestFile, "OpenClaw alias requester manifest"], + ] as const) { + assertRegularFile(file, label); + } + if (fs.existsSync(actualDirectory)) { + throw new Error(`OpenClaw signature-audit destination already exists: ${actualPackagePath}`); + } + + const lock = readJsonObject(lockfile, "OpenClaw signature-audit lock"); + const packages = lock.packages as Record | undefined; + const aliasEntry = packages?.[aliasPackagePath]; + const requesterEntry = packages?.[requesterPackagePath]; + if ( + !packages || + !aliasEntry || + aliasEntry.name !== actualName || + aliasEntry.version !== version || + aliasEntry.resolved !== resolved || + aliasEntry.integrity !== integrity || + packages[actualPackagePath] || + requesterEntry?.dependencies?.["node-domexception"] !== requestedRange || + requesterEntry.dependencies[actualName] !== undefined + ) { + throw new Error("OpenClaw signature-audit alias lock identity drifted"); + } + const aliasManifest = readJsonObject(aliasManifestFile, "OpenClaw aliased package manifest"); + const requesterManifest = readJsonObject( + requesterManifestFile, + "OpenClaw alias requester manifest", + ); + if ( + aliasManifest.name !== actualName || + aliasManifest.version !== version || + requesterManifest.dependencies?.["node-domexception"] !== requestedRange || + requesterManifest.dependencies?.[actualName] !== undefined + ) { + throw new Error("OpenClaw signature-audit installed alias identity drifted"); + } + + packages[actualPackagePath] = aliasEntry; + delete packages[aliasPackagePath]; + delete requesterEntry.dependencies["node-domexception"]; + requesterEntry.dependencies[actualName] = version; + delete requesterManifest.dependencies["node-domexception"]; + requesterManifest.dependencies[actualName] = version; + fs.mkdirSync(path.dirname(actualDirectory), { recursive: true }); + fs.renameSync(aliasDirectory, actualDirectory); + fs.writeFileSync(lockfile, `${JSON.stringify(lock, null, 2)}\n`); + fs.writeFileSync(requesterManifestFile, `${JSON.stringify(requesterManifest, null, 2)}\n`); +} + +function auditLockedGraph( + graph: LockedGraph, + index: number, + config: AuditConfig, + tempRoot: string, + exceptionFile: string, + artifactDirectory: string, + npmVersion: string, +) { + const directory = materializeLockedGraph(graph, tempRoot, config.registryOrigin); + const result = runReviewedNpmAudit({ + directory, + exceptionFile, + graph: graph.id, + provenance: { + label: graph.label, + nodeVersion: process.version, + npmVersion, + packageSpecs: [graph.packageSpec], + }, + reportFile: path.join(artifactDirectory, `locked-graph-${index + 1}.json`), + resultFile: path.join(artifactDirectory, `locked-graph-${index + 1}-policy.json`), + threshold: config.severityThreshold, + throwOnBlock: false, + }); + if (graph.id === "openclaw-runtime") { + normalizeOpenClawSignatureAlias(directory); + } + run("npm", ["audit", "signatures", "--omit=dev"], directory); + return result; +} + function main(): void { const config = readConfig(); const expectedNode = `v${config.nodeVersion}`; if (process.version !== expectedNode) { throw new Error(`reviewed npm audit requires Node ${expectedNode}; running ${process.version}`); } - const artifactDirectory = repositoryPath(config.artifactDirectory, "audit artifact directory"); - const exceptionFile = repositoryPath(config.exceptionFile, "npm audit exception file"); + const artifactDirectory = targetRepositoryPath( + process.env.NEMOCLAW_REVIEWED_NPM_AUDIT_REPORT_DIR ?? config.artifactDirectory, + "audit artifact directory", + ); + const exceptionFile = trustedRepositoryPath(config.exceptionFile, "npm audit exception file"); const exceptionRegistry = readAuditExceptionRegistry(exceptionFile); assertExceptionGraphs( exceptionRegistry.policy, @@ -153,7 +351,7 @@ function main(): void { ); fs.rmSync(artifactDirectory, { recursive: true, force: true }); fs.mkdirSync(artifactDirectory, { recursive: true }); - const npmVersion = run("npm", ["--version"], REPO_ROOT).stdout.trim(); + const npmVersion = run("npm", ["--version"], TRUSTED_REPO_ROOT).stdout.trim(); const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-reviewed-npm-audit-")); try { const reports = [ @@ -177,21 +375,15 @@ function main(): void { }, ...config.lockedGraphs.map((graph, index) => ({ label: graph.label, - result: runReviewedNpmAudit({ - directory: materializeLockedGraph(graph, tempRoot), + result: auditLockedGraph( + graph, + index, + config, + tempRoot, exceptionFile, - graph: graph.id, - provenance: { - label: graph.label, - nodeVersion: process.version, - npmVersion, - packageSpecs: [graph.packageSpec], - }, - reportFile: path.join(artifactDirectory, `locked-graph-${index + 1}.json`), - resultFile: path.join(artifactDirectory, `locked-graph-${index + 1}-policy.json`), - threshold: config.severityThreshold, - throwOnBlock: false, - }), + artifactDirectory, + npmVersion, + ), })), ]; const failures: string[] = []; diff --git a/scripts/lib/openclaw-npm-remediation.mts b/scripts/lib/openclaw-npm-remediation.mts index 50675333b3e..4561999fb1c 100755 --- a/scripts/lib/openclaw-npm-remediation.mts +++ b/scripts/lib/openclaw-npm-remediation.mts @@ -27,7 +27,7 @@ type JsonObject = Record; type Remediation = Readonly<{ expectedPatchedMetadataIntegrity?: string; expectedPatchedTreeIntegrity?: string; - kind: "axios" | "core" | "jaeger" | "legacy-core"; + kind: "axios" | "core" | "current-core" | "jaeger" | "legacy-core"; version: "2026.3.11" | "2026.6.10" | "2026.7.1"; }>; @@ -85,6 +85,15 @@ const BRACE_EXPANSION_INTEGRITY = "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="; const BRACE_EXPANSION_TARBALL = "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz"; +const CURRENT_BRACE_EXPANSION_VERSION = "5.0.8"; +const CURRENT_BRACE_EXPANSION_INTEGRITY = + "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="; +const CURRENT_BRACE_EXPANSION_TARBALL = + "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz"; +const CURRENT_FAST_URI_VERSION = "3.1.4"; +const CURRENT_FAST_URI_INTEGRITY = + "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw=="; +const CURRENT_FAST_URI_TARBALL = "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz"; const JAEGER_PROPAGATOR_VERSION = "2.9.0"; const JAEGER_PROPAGATOR_INTEGRITY = "sha512-4mYGty27rYvSM0jtp1ZUOqd3LfVRCYg9H5G9OFzSx5HViYToU21MFhWfco7x1HwXr7ER8yGOiCIHZUwjPksc0Q=="; @@ -139,6 +148,14 @@ const REMEDIATIONS: Readonly> = Object.freeze({ kind: "core", version: "2026.6.10", }, + // openclaw/openclaw#113584: remove after a supported OpenClaw archive + // publishes both corrected transitive resolutions in its shrinkwrap. + "openclaw@2026.7.1": { + expectedPatchedTreeIntegrity: + "sha512-/D/vAtyFEvd0TvYxFBfBN/chwW8WGaAy8bH40ojjJSRuN+P461qnbfiMTHydA7asXN/69y2NnQHs7b3pDh8+0g==", + kind: "current-core", + version: "2026.7.1", + }, "openclaw@2026.3.11": { kind: "legacy-core", expectedPatchedMetadataIntegrity: @@ -497,6 +514,62 @@ export function patchOpenClawCorePackageGraph(packageDirectory: string): void { writeJson(shrinkwrapPath, shrinkwrap); } +export function patchCurrentOpenClawCorePackageGraph(packageDirectory: string): void { + const packageJsonPath = join(packageDirectory, "package.json"); + const shrinkwrapPath = join(packageDirectory, "npm-shrinkwrap.json"); + const packageJson = readJson(packageJsonPath); + requirePackageIdentity(packageJson, "openclaw", "2026.7.1", "OpenClaw core"); + if ( + packageJson.dependencies?.minimatch !== "10.2.5" || + packageJson.dependencies?.["@modelcontextprotocol/sdk"] !== "1.29.0" || + packageJson.dependencies?.["brace-expansion"] !== undefined || + packageJson.dependencies?.["fast-uri"] !== undefined + ) { + throw new Error("openclaw@2026.7.1 dependency boundary changed after review"); + } + + const shrinkwrap = readJson(shrinkwrapPath); + if (shrinkwrap.lockfileVersion !== 3 || !shrinkwrap.packages?.[""]) { + throw new Error("openclaw@2026.7.1 must ship an npm lockfileVersion 3 shrinkwrap"); + } + const packages = shrinkwrap.packages as JsonObject; + const root = packages[""] as JsonObject; + requirePackageIdentity(root, "openclaw", "2026.7.1", "OpenClaw shrinkwrap root"); + const braceExpansion = packages["node_modules/brace-expansion"] as JsonObject | undefined; + const fastUri = packages["node_modules/fast-uri"] as JsonObject | undefined; + const minimatch = packages["node_modules/minimatch"] as JsonObject | undefined; + const ajv = packages["node_modules/ajv"] as JsonObject | undefined; + if ( + braceExpansion?.version !== "5.0.7" || + braceExpansion.resolved !== + "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz" || + braceExpansion.integrity !== + "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==" || + braceExpansion.dependencies?.["balanced-match"] !== "^4.0.2" || + minimatch?.dependencies?.["brace-expansion"] !== "^5.0.5" + ) { + throw new Error("openclaw@2026.7.1 brace-expansion layout changed after review"); + } + if ( + fastUri?.version !== "3.1.2" || + fastUri.resolved !== "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz" || + fastUri.integrity !== + "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==" || + ajv?.version !== "8.20.0" || + ajv.dependencies?.["fast-uri"] !== "^3.0.1" + ) { + throw new Error("openclaw@2026.7.1 fast-uri layout changed after review"); + } + + braceExpansion.version = CURRENT_BRACE_EXPANSION_VERSION; + braceExpansion.resolved = CURRENT_BRACE_EXPANSION_TARBALL; + braceExpansion.integrity = CURRENT_BRACE_EXPANSION_INTEGRITY; + fastUri.version = CURRENT_FAST_URI_VERSION; + fastUri.resolved = CURRENT_FAST_URI_TARBALL; + fastUri.integrity = CURRENT_FAST_URI_INTEGRITY; + writeJson(shrinkwrapPath, shrinkwrap); +} + export function patchLegacyOpenClawCorePackageGraph(packageDirectory: string): void { const packageJsonPath = join(packageDirectory, "package.json"); const bundledTarPackageJsonPath = join(packageDirectory, "node_modules", "tar", "package.json"); @@ -768,6 +841,61 @@ export function buildRemediatedOpenClawPluginArchive( join(sourcePackage, "node_modules", "@openclaw", "fs-safe"), ); patchOpenClawCorePackageGraph(sourcePackage); + } else if (remediation.kind === "current-core") { + const braceExpansionArchive = packReplacement( + `brace-expansion@${CURRENT_BRACE_EXPANSION_VERSION}`, + CURRENT_BRACE_EXPANSION_INTEGRITY, + CURRENT_BRACE_EXPANSION_TARBALL, + remediationRoot, + env, + ); + const fastUriArchive = packReplacement( + `fast-uri@${CURRENT_FAST_URI_VERSION}`, + CURRENT_FAST_URI_INTEGRITY, + CURRENT_FAST_URI_TARBALL, + remediationRoot, + env, + ); + const braceExpansionPackage = extractArchive( + braceExpansionArchive.archivePath, + join(remediationRoot, "brace-expansion"), + remediationRoot, + env, + ); + const fastUriPackage = extractArchive( + fastUriArchive.archivePath, + join(remediationRoot, "fast-uri"), + remediationRoot, + env, + ); + const braceExpansionPackageJson = readJson(join(braceExpansionPackage, "package.json")); + const fastUriPackageJson = readJson(join(fastUriPackage, "package.json")); + requirePackageIdentity( + braceExpansionPackageJson, + "brace-expansion", + CURRENT_BRACE_EXPANSION_VERSION, + "OpenClaw brace-expansion remediation package", + ); + requireDependencyShape( + braceExpansionPackageJson, + { "balanced-match": "^4.0.2" }, + `brace-expansion@${CURRENT_BRACE_EXPANSION_VERSION}`, + ); + requirePackageIdentity( + fastUriPackageJson, + "fast-uri", + CURRENT_FAST_URI_VERSION, + "OpenClaw fast-uri remediation package", + ); + if ( + fastUriPackageJson.dependencies !== undefined && + Object.keys(fastUriPackageJson.dependencies).length !== 0 + ) { + throw new Error( + `fast-uri@${CURRENT_FAST_URI_VERSION} dependency graph changed; review the remediation before updating it`, + ); + } + patchCurrentOpenClawCorePackageGraph(sourcePackage); } else if (remediation.kind === "legacy-core") { const bundledTarPath = join(sourcePackage, "node_modules", "tar"); if (existsSync(bundledTarPath)) { diff --git a/scripts/lib/reviewed-npm-archive.mts b/scripts/lib/reviewed-npm-archive.mts index 4c329f92b9b..6cdb8291629 100755 --- a/scripts/lib/reviewed-npm-archive.mts +++ b/scripts/lib/reviewed-npm-archive.mts @@ -382,6 +382,11 @@ export function verifyReviewedNpmLock( if (topLevel.resolved !== request.tarballUrl) { throw new Error(`reviewed npm lock tarball URL mismatch for ${request.packageSpec}`); } + if (Object.prototype.hasOwnProperty.call(topLevel, "hasShrinkwrap")) { + throw new Error( + `reviewed npm lock must be authoritative for ${request.packageSpec}; nested shrinkwrap delegation is not allowed`, + ); + } const reviewed = readReviewedLockPackages(packages, request.lockfilePath, registryOrigin); verifyReviewedNpmMetadata(request, npmRunner); diff --git a/test/openclaw-integrity-pin-suite.ts b/test/openclaw-integrity-pin-suite.ts index 28945b1c1ab..69ceeaf77c2 100644 --- a/test/openclaw-integrity-pin-suite.ts +++ b/test/openclaw-integrity-pin-suite.ts @@ -46,6 +46,16 @@ const PINNED_OPENCLAW_VERSION = "2026.7.1"; const PINNED_OPENCLAW_INTEGRITY = "sha512-ge/Xss99CHAjPL/ikmH/UFoiOrjcxDB4sW3y9mhyCD+dYW3wzV7TKbAVdkrXFgAG2d2BjpJofP97zUZ+umxo8g=="; const PINNED_OPENCLAW_TARBALL = "https://registry.npmjs.org/openclaw/-/openclaw-2026.7.1.tgz"; +const OPENCLAW_RUNTIME_LOCKFILE = path.join( + REPO_ROOT, + "agents", + "openclaw", + "openclaw-runtime", + "package-lock.json", +); +const PINNED_OPENCLAW_LOCK_SHA256 = createHash("sha256") + .update(fs.readFileSync(OPENCLAW_RUNTIME_LOCKFILE)) + .digest("hex"); const PINNED_NEMOCLAW_TAR_VERSION = "7.5.20"; const PINNED_NEMOCLAW_TAR_INTEGRITY = "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ=="; @@ -117,15 +127,20 @@ function openClawBaseProvenance( status: "clean", }, ): string { + const lockSha256 = + version === PINNED_OPENCLAW_VERSION ? PINNED_OPENCLAW_LOCK_SHA256 : "none-legacy-fixture"; const recipe = - version === LEGACY_REBUILD_OPENCLAW_VERSION - ? "ignore-scripts+reviewed-lifecycle+transitive-remediation-v1" - : "ignore-scripts+reviewed-lifecycle-v1"; + version === PINNED_OPENCLAW_VERSION + ? "locked-ci+reviewed-lifecycle-v2" + : version === LEGACY_REBUILD_OPENCLAW_VERSION + ? "ignore-scripts+reviewed-lifecycle+transitive-remediation-v1" + : "ignore-scripts+reviewed-lifecycle-v1"; return [ "schema=3", `package=openclaw@${version}`, `integrity=${integrity}`, `tarball=${tarball}`, + `lock-sha256=${lockSha256}`, `recipe=${recipe}`, `mcporter-package=mcporter@${PINNED_MCPORTER_VERSION}`, `mcporter-integrity=${PINNED_MCPORTER_INTEGRITY}`, @@ -185,6 +200,8 @@ function runInstallBlock( baseProvenanceMetadata?: string; baseProvenanceSymlink?: boolean; auditExceptionPolicy?: string; + failOpenClawNpmCi?: boolean; + failOpenClawVerifyInstalledLock?: boolean; } = {}, ) { const { @@ -210,11 +227,16 @@ function runInstallBlock( baseProvenanceMetadata = "0:0:444", baseProvenanceSymlink = false, auditExceptionPolicy = fs.readFileSync(NPM_AUDIT_EXCEPTION_FILE, "utf-8"), + failOpenClawNpmCi = false, + failOpenClawVerifyInstalledLock = false, } = options; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openclaw-integrity-")); const blueprint = path.join(tmp, "blueprint.yaml"); const log = path.join(tmp, "calls.log"); const provenancePath = path.join(tmp, "openclaw-base-provenance-v1"); + const openclawRuntime = path.join(tmp, "openclaw-runtime"); + const openclawGlobal = path.join(tmp, "global", "openclaw"); + const openclawBin = path.join(tmp, "bin", "openclaw"); const mcporterRuntime = path.join(tmp, "mcporter-runtime"); const mcporterBin = path.join(tmp, "bin", "mcporter"); const reviewedNpmExecutable = path.join(tmp, "bin", "reviewed-npm-fixture"); @@ -226,7 +248,9 @@ function runInstallBlock( .update(auditExceptionPolicy) .digest("hex"); fs.mkdirSync(path.dirname(mcporterBin), { recursive: true }); + fs.mkdirSync(openclawRuntime, { recursive: true }); fs.mkdirSync(mcporterRuntime, { recursive: true }); + fs.copyFileSync(OPENCLAW_RUNTIME_LOCKFILE, path.join(openclawRuntime, "package-lock.json")); fs.copyFileSync(OPENCLAW_VERSION_EXTRACTOR, openclawVersionExtractor); fs.chmodSync(openclawVersionExtractor, 0o755); fs.copyFileSync(MCPORTER_LOCKFILE, path.join(mcporterRuntime, "package-lock.json")); @@ -338,8 +362,11 @@ function runInstallBlock( `openclaw_version_command_status=${openclawVersionCommandStatus}`, `installed_openclaw_version=${JSON.stringify(installedOpenClawVersion)}`, `installed_mcporter_version=${JSON.stringify(installedMcporterVersion)}`, + `fail_openclaw_npm_ci=${failOpenClawNpmCi ? "1" : "0"}`, + `fail_openclaw_verify_installed_lock=${failOpenClawVerifyInstalledLock ? "1" : "0"}`, "node() {", - ' if [ "${1:-}" = "/usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs" ]; then printf "node %s\\n" "$*" >> "$call_log"; return 0; fi', + ' if printf "%s\\n" "$*" | grep -q -- "--verify-installed-lock"; then printf "node %s\\n" "$*" >> "$call_log"; [ "$fail_openclaw_verify_installed_lock" = "0" ] || return 44; return 0; fi', + ` if [ "\${1:-}" = ${JSON.stringify(path.join(openclawRuntime, "node_modules", "openclaw", "scripts", "postinstall-bundled-plugins.mjs"))} ] || [ "\${1:-}" = ${JSON.stringify(path.join(openclawGlobal, "scripts", "postinstall-bundled-plugins.mjs"))} ]; then printf "node %s\\n" "$*" >> "$call_log"; return 0; fi`, ' if [ "${1:-}" = "--input-type=module" ] && [ "${2:-}" = "-e" ] && printf "%s\\n" "${3:-}" | grep -q "StreamableHTTPServerTransport"; then printf "node %s\\n" "$*" >> "$call_log"; return 0; fi', ' "$real_node" "$@"', "}", @@ -355,13 +382,15 @@ function runInstallBlock( ' command stat "$@"', "}", "sha256sum() {", + ` if [ "\${1:-}" = ${JSON.stringify(path.join(openclawRuntime, "package-lock.json"))} ]; then printf '%s %s\\n' ${JSON.stringify(PINNED_OPENCLAW_LOCK_SHA256)} "$1"; return 0; fi`, ` if [ "\${1:-}" = ${JSON.stringify(path.join(mcporterRuntime, "package-lock.json"))} ]; then printf '%s %s\\n' ${JSON.stringify(PINNED_MCPORTER_LOCK_SHA256)} "$1"; return 0; fi`, ` if [ "\${1:-}" = ${JSON.stringify(auditExceptionFile)} ]; then printf '%s %s\\n' ${JSON.stringify(auditExceptionPolicySha256)} "$1"; return 0; fi`, ' printf "unexpected sha256sum input: %s\\n" "${1:-}" >&2; return 1', "}", "npm() {", ' printf "npm %s\\n" "$*" >> "$call_log";', - ' [ "${1:-}" != "--prefix" ] || [ "${3:-}" != "ci" ] || installed_mcporter_version="$MCPORTER_VERSION"', + ` if [ "\${1:-}" = "--prefix" ] && [ "\${2:-}" = ${JSON.stringify(openclawRuntime)} ] && [ "\${3:-}" = "ci" ]; then [ "$fail_openclaw_npm_ci" = "0" ] || return 42; installed_openclaw_version="$OPENCLAW_VERSION"; return 0; fi`, + ` if [ "\${1:-}" = "--prefix" ] && [ "\${2:-}" = ${JSON.stringify(mcporterRuntime)} ] && [ "\${3:-}" = "ci" ]; then installed_mcporter_version="$MCPORTER_VERSION"; return 0; fi`, ' if [ "${1:-}" = "view" ] && [ "${3:-}" = "version" ]; then printf "%s\\n" "$OPENCLAW_VERSION"; return 0; fi', ` if [ "\${1:-}" = "view" ] && [ "\${2:-}" = "@zed-industries/codex-acp@${PINNED_CODEX_ACP_VERSION}" ] && [ "\${3:-}" = "dist.integrity" ]; then printf "%s\\n" ${JSON.stringify(codexAcpRegistryIntegrity)}; return 0; fi`, ` if [ "\${1:-}" = "view" ] && [ "\${2:-}" = "@zed-industries/codex-acp@${PINNED_CODEX_ACP_VERSION}" ] && [ "\${3:-}" = "dist.tarball" ]; then printf "%s\\n" ${JSON.stringify(codexAcpRegistryTarball)}; return 0; fi`, @@ -393,9 +422,15 @@ function runInstallBlock( .replaceAll("/opt/nemoclaw-blueprint/blueprint.yaml", blueprint) .replaceAll("/tmp/blueprint.yaml", blueprint) .replaceAll(OPENCLAW_BASE_PROVENANCE_PATH, provenancePath) + .replaceAll("/usr/local/lib/nemoclaw/openclaw-runtime", openclawRuntime) + .replaceAll("/usr/local/lib/node_modules/openclaw", openclawGlobal) + .replaceAll("/usr/local/lib/node_modules", path.dirname(openclawGlobal)) + .replaceAll("/usr/local/bin/openclaw", openclawBin) .replaceAll("/usr/local/lib/nemoclaw/mcporter-runtime", mcporterRuntime) .replaceAll("/usr/local/bin/mcporter", mcporterBin) .replaceAll("/usr/local/lib/nemoclaw/extract-semver", openclawVersionExtractor) + .replaceAll("/usr/local/lib", path.join(tmp, "usr-local-lib")) + .replaceAll("/usr/local/bin", path.join(tmp, "usr-local-bin")) .replaceAll("/scripts/lib/reviewed-npm-archive.mts", REVIEWED_NPM_ARCHIVE_HELPER) .replaceAll("/scripts/lib/openclaw-npm-remediation.mts", remediationHelper) .replaceAll("/scripts/lib/reviewed-npm-audit.mts", auditHelper) @@ -408,8 +443,23 @@ function runInstallBlock( const provenanceExists = fs.existsSync(provenancePath); const provenanceContent = provenanceExists ? fs.readFileSync(provenancePath, "utf-8") : null; const provenanceMode = provenanceExists ? fs.statSync(provenancePath).mode & 0o777 : null; + const runtimeExposed = [openclawGlobal, openclawBin].some((candidate) => { + try { + fs.lstatSync(candidate); + return true; + } catch { + return false; + } + }); fs.rmSync(tmp, { recursive: true, force: true }); - return { result, calls, provenanceExists, provenanceContent, provenanceMode }; + return { + result, + calls, + provenanceExists, + provenanceContent, + provenanceMode, + runtimeExposed, + }; } function runProductionBuildArgGuard( @@ -618,10 +668,10 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(reviewNote).toContain("@openclaw/diagnostics-otel@2026.7.1"); expect(reviewNote).toContain("@openclaw/brave-plugin@2026.7.1"); expect(reviewNote).toContain("@tencent-weixin/openclaw-weixin@2.4.3"); - expect(reviewNote).toContain("`13` moderate"); + expect(reviewNote).toContain("three production-compatible boundaries"); + expect(reviewNote).toContain("Lower-severity findings remain visible"); expect(reviewNote).toContain("`0` high"); expect(reviewNote).toContain("`0` critical"); - expect(reviewNote).toContain("`823` total dependencies"); expect(reviewNote).toContain( "`dist/pipeline.runtime-*.js`, which exports `prepareSlackMessage`", ); @@ -890,14 +940,14 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(production.result.status).toBe(0); expect(codexAcp.result.status).toBe(0); - expect(base.result.status).toBe(0); + expect(base.result.status, `${base.result.stdout}${base.result.stderr}`).toBe(0); expect(production.calls).toContain( `npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.integrity`, ); expect(production.calls).toContain( `npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.tarball`, ); - expect(production.calls).toContain( + expect(production.calls).not.toContain( `npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`, ); expect(codexAcp.calls).toContain( @@ -907,13 +957,9 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes `npm view @zed-industries/codex-acp@${PINNED_CODEX_ACP_VERSION} dist.tarball`, ); expect(codexAcp.calls).toContain(`npm pack ${PINNED_CODEX_ACP_TARBALL} --pack-destination`); - expect(production.calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", - ); - expect(production.calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); - expect(production.calls).toContain(`openclaw-${PINNED_OPENCLAW_VERSION}.tgz`); + expect(production.calls).toMatch(/npm --prefix \S+\/openclaw-runtime ci /u); + expect(production.calls).toContain("--verify-installed-lock"); + expect(production.calls).toContain("postinstall-bundled-plugins.mjs"); expect(codexAcp.calls).toContain( "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", ); @@ -921,12 +967,10 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(base.calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} version`); expect(base.calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.integrity`); expect(base.calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.tarball`); - expect(base.calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); - expect(base.calls).toContain("npm install -g --ignore-scripts "); - expect(base.calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); - expect(base.calls).toContain(`openclaw-${PINNED_OPENCLAW_VERSION}.tgz`); + expect(base.calls).not.toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); + expect(base.calls).toMatch(/npm --prefix \S+\/openclaw-runtime ci /u); + expect(base.calls).toContain("--verify-installed-lock"); + expect(base.calls).toContain("postinstall-bundled-plugins.mjs"); expect(base.provenanceContent).toBe(openClawBaseProvenance()); expect(base.provenanceMode).toBe(0o444); }); @@ -1063,7 +1107,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "wrong lifecycle recipe", { baseProvenance: openClawBaseProvenance().replace( - "recipe=ignore-scripts+reviewed-lifecycle-v1", + "recipe=locked-ci+reviewed-lifecycle-v2", "recipe=ignore-scripts-only-v1", ), }, @@ -1187,13 +1231,10 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(result.stdout).toContain("lacks exact reviewed provenance"); expect(calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.integrity`); expect(calls).toContain(`npm view openclaw@${PINNED_OPENCLAW_VERSION} dist.tarball`); - expect(calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); - expect(calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", - ); - expect(calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); + expect(calls).not.toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); + expect(calls).toMatch(/npm --prefix \S+\/openclaw-runtime ci /u); + expect(calls).toContain("--verify-installed-lock"); + expect(calls).toContain("postinstall-bundled-plugins.mjs"); expect(provenanceExists).toBe(false); }); @@ -1274,19 +1315,6 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes }); it("rejects npm pack filenames outside the fresh pack directories", () => { - const production = runInstallBlock( - extractRunBlock( - DOCKERFILE, - "# OPENCLAW_VERSION is the NemoClaw runtime build target", - "# Patch OpenClaw media fetch", - ), - { - openclawVersion: PINNED_OPENCLAW_VERSION, - committedIntegrity: PINNED_OPENCLAW_INTEGRITY, - registryIntegrity: PINNED_OPENCLAW_INTEGRITY, - packFilename: "../openclaw-2026.7.1.tgz", - }, - ); const codexAcp = runInstallBlock( extractRunBlock( DOCKERFILE, @@ -1307,10 +1335,13 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "# Baseline health check.", ), { - openclawVersion: PINNED_OPENCLAW_VERSION, - committedIntegrity: PINNED_OPENCLAW_INTEGRITY, - registryIntegrity: PINNED_OPENCLAW_INTEGRITY, - packFilename: "../openclaw-2026.7.1.tgz", + openclawVersion: LEGACY_GATEWAY_UPGRADE_OPENCLAW_VERSION, + committedIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + registryIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + registryTarball: LEGACY_GATEWAY_UPGRADE_OPENCLAW_TARBALL, + packIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + allowLegacyFixture: true, + packFilename: "../openclaw-2026.4.24.tgz", }, ); const optionalPlugin = runOptionalOpenClawPluginBlock({ @@ -1318,12 +1349,6 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes }); for (const item of [ - { - label: "production Dockerfile", - outcome: production, - unsafeFilename: "../openclaw-2026.7.1.tgz", - blockedCommand: "npm install -g", - }, { label: "codex-acp Dockerfile", outcome: codexAcp, @@ -1333,7 +1358,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes { label: "base Dockerfile", outcome: base, - unsafeFilename: "../openclaw-2026.7.1.tgz", + unsafeFilename: "../openclaw-2026.4.24.tgz", blockedCommand: "npm install -g", }, { @@ -1361,18 +1386,23 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "# Baseline health check.", ), { - openclawVersion: PINNED_OPENCLAW_VERSION, - committedIntegrity: PINNED_OPENCLAW_INTEGRITY, - registryIntegrity: PINNED_OPENCLAW_INTEGRITY, + openclawVersion: LEGACY_GATEWAY_UPGRADE_OPENCLAW_VERSION, + committedIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + registryIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + registryTarball: LEGACY_GATEWAY_UPGRADE_OPENCLAW_TARBALL, + packIntegrity: LEGACY_GATEWAY_UPGRADE_OPENCLAW_INTEGRITY, + allowLegacyFixture: true, packFilename: null, }, ); - const diagnostic = `npm pack openclaw@${PINNED_OPENCLAW_VERSION} did not report filename and integrity`; + const diagnostic = `npm pack openclaw@${LEGACY_GATEWAY_UPGRADE_OPENCLAW_VERSION} did not report filename and integrity`; expect(result.status).not.toBe(0); expect(result.stderr).toContain(diagnostic); expect(result.stdout).not.toContain(diagnostic); - expect(calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); + expect(calls).toContain( + `npm pack ${LEGACY_GATEWAY_UPGRADE_OPENCLAW_TARBALL} --pack-destination`, + ); expect(calls).not.toContain("npm install -g"); }); } @@ -1464,9 +1494,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes expect(fixtureBase.calls).not.toContain("postinstall-bundled-plugins.mjs"); expect(gatewayFixtureBase.result.status).toBe(0); expect(gatewayFixtureBase.calls).toContain("npm install -g --ignore-scripts "); - expect(gatewayFixtureBase.calls).toContain( - "node /usr/local/lib/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs", - ); + expect(gatewayFixtureBase.calls).toContain("postinstall-bundled-plugins.mjs"); }); it("guards production Docker build args from legacy OpenClaw fixture inputs", () => { @@ -1621,8 +1649,8 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes } }); - it("fails closed before npm install when the downloaded OpenClaw tarball integrity drifts", () => { - const { result, calls } = runInstallBlock( + it("leaves no runtime exposure when npm ci rejects downloaded OpenClaw bytes", () => { + const outcome = runInstallBlock( extractRunBlock( DOCKERFILE, "# OPENCLAW_VERSION is the NemoClaw runtime build target", @@ -1632,19 +1660,50 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes openclawVersion: PINNED_OPENCLAW_VERSION, committedIntegrity: PINNED_OPENCLAW_INTEGRITY, registryIntegrity: PINNED_OPENCLAW_INTEGRITY, - packIntegrity: "sha512-downloaded-drift", + failOpenClawNpmCi: true, }, ); - const output = `${result.stdout}${result.stderr}`; - expect(result.status).not.toBe(0); - expect(output).toContain( - `OpenClaw ${PINNED_OPENCLAW_VERSION} downloaded tarball integrity mismatch`, - ); - expect(output).toContain(`Expected: ${PINNED_OPENCLAW_INTEGRITY}`); - expect(output).toContain("Actual: sha512-downloaded-drift"); - expect(calls).toContain(`npm pack ${PINNED_OPENCLAW_TARBALL} --pack-destination`); - expect(calls).not.toContain("npm install -g"); + expect(outcome.result.status).not.toBe(0); + expect(outcome.calls).toMatch(/npm --prefix \S+\/openclaw-runtime ci /u); + expect(outcome.calls).not.toContain("postinstall-bundled-plugins.mjs"); + expect(outcome.runtimeExposed).toBe(false); + expect(outcome.provenanceExists).toBe(false); + }); + + it("leaves no runtime exposure when installed-lock verification fails", () => { + const installBlocks = [ + { + label: "production Dockerfile", + file: DOCKERFILE, + startMarker: "# OPENCLAW_VERSION is the NemoClaw runtime build target", + endMarker: "# Patch OpenClaw media fetch", + }, + { + label: "base Dockerfile", + file: DOCKERFILE_BASE, + startMarker: "# Install OpenClaw CLI + PyYAML.", + endMarker: "# Baseline health check.", + }, + ]; + + for (const block of installBlocks) { + const outcome = runInstallBlock( + extractRunBlock(block.file, block.startMarker, block.endMarker), + { + openclawVersion: PINNED_OPENCLAW_VERSION, + committedIntegrity: PINNED_OPENCLAW_INTEGRITY, + registryIntegrity: PINNED_OPENCLAW_INTEGRITY, + failOpenClawVerifyInstalledLock: true, + }, + ); + + expect(outcome.result.status, block.label).not.toBe(0); + expect(outcome.calls, block.label).toContain("--verify-installed-lock"); + expect(outcome.calls, block.label).not.toContain("postinstall-bundled-plugins.mjs"); + expect(outcome.runtimeExposed, block.label).toBe(false); + expect(outcome.provenanceExists, block.label).toBe(false); + } }); it("fails closed before npm install for unpinned production Dockerfile overrides", () => { diff --git a/test/openclaw-locked-install.test.ts b/test/openclaw-locked-install.test.ts index 380015946de..5fd4506c3a4 100644 --- a/test/openclaw-locked-install.test.ts +++ b/test/openclaw-locked-install.test.ts @@ -15,11 +15,11 @@ import { const REPO_ROOT = path.join(import.meta.dirname, ".."); const RUNTIME_DIRECTORY = path.join(REPO_ROOT, "agents", "openclaw", "openclaw-runtime"); const LOCKFILE = path.join(RUNTIME_DIRECTORY, "package-lock.json"); -const PACKAGE_SPEC = "openclaw@2026.6.10"; +const PACKAGE_SPEC = "openclaw@2026.7.1"; const INTEGRITY = - "sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug=="; -const TARBALL = "https://registry.npmjs.org/openclaw/-/openclaw-2026.6.10.tgz"; -const LOCK_SHA256 = "a0f91c7e0b769e73c3f6119b2a6ee2dfd9bcb32b3dc69655b22696c654694d2d"; + "sha512-ge/Xss99CHAjPL/ikmH/UFoiOrjcxDB4sW3y9mhyCD+dYW3wzV7TKbAVdkrXFgAG2d2BjpJofP97zUZ+umxo8g=="; +const TARBALL = "https://registry.npmjs.org/openclaw/-/openclaw-2026.7.1.tgz"; +const LOCK_SHA256 = "82489f62febb12da52833c0b1f7f6969f7e21a098c565ef1f91342b1e5e32d88"; const roots: string[] = []; function sha256(file: string): string { @@ -30,7 +30,7 @@ function lockRequest(lockfilePath = LOCKFILE, expectedLockSha256 = LOCK_SHA256) return { expectedIntegrity: INTEGRITY, expectedLockSha256, - label: "OpenClaw 2026.6.10 locked runtime graph", + label: "OpenClaw 2026.7.1 locked runtime graph", lockfilePath, packageSpec: PACKAGE_SPEC, registryOrigin: "https://registry.npmjs.org/", @@ -170,12 +170,11 @@ afterEach(() => { describe("locked OpenClaw production installation (#5896)", () => { it("binds the reviewed root artifact to the complete committed closure", () => { const verified = verifyReviewedNpmLock(lockRequest(), reviewedMetadata); - expect(verified).toHaveLength(305); + expect(verified).toHaveLength(307); expect(verified).toContain(PACKAGE_SPEC); + expect(verified).toContain("brace-expansion@5.0.8"); + expect(verified).toContain("fast-uri@3.1.4"); expect(sha256(LOCKFILE)).toBe(LOCK_SHA256); - expect(JSON.parse(fs.readFileSync(LOCKFILE, "utf-8"))).toMatchObject({ - packages: { "": { dependencies: { openclaw: "2026.6.10" } } }, - }); }); // source-shape-contract: security -- The committed production lock digest must fail before any registry-controlled metadata is consulted @@ -197,26 +196,33 @@ describe("locked OpenClaw production installation (#5896)", () => { // source-shape-contract: security -- Mutating the shipped lock proves every reviewed transitive identity remains bound to committed production bytes it.each([ { - expected: "root must depend only on openclaw@2026.6.10", + expected: "root must depend only on openclaw@2026.7.1", mutate: (lock: any) => { - lock.packages[""].dependencies.openclaw = "2026.6.11"; + lock.packages[""].dependencies.openclaw = "2026.7.2"; }, name: "root version drift", }, { - expected: "root must depend only on openclaw@2026.6.10", + expected: "root must depend only on openclaw@2026.7.1", mutate: (lock: any) => { lock.packages[""].optionalDependencies = { "left-pad": "1.3.0" }; }, name: "root optional dependency injection", }, { - expected: "lock integrity mismatch for openclaw@2026.6.10", + expected: "lock integrity mismatch for openclaw@2026.7.1", mutate: (lock: any) => { lock.packages["node_modules/openclaw"].integrity = `sha512-${"B".repeat(88)}`; }, name: "top-level integrity drift", }, + { + expected: "nested shrinkwrap delegation is not allowed", + mutate: (lock: any) => { + lock.packages["node_modules/openclaw"].hasShrinkwrap = true; + }, + name: "nested shrinkwrap delegation", + }, { expected: "must use a committed sha512 npm integrity value", mutate: (lock: any) => { @@ -351,7 +357,7 @@ describe("locked OpenClaw production installation (#5896)", () => { const audit = JSON.parse( fs.readFileSync(path.join(REPO_ROOT, "ci", "reviewed-npm-audit.json"), "utf-8"), ); - expect(audit.archivePackages).not.toEqual( + expect(audit.archivePackages).toEqual( expect.arrayContaining([expect.objectContaining({ packageSpec: PACKAGE_SPEC })]), ); expect(audit.lockedGraphs).toEqual( diff --git a/test/openclaw-npm-remediation.test.ts b/test/openclaw-npm-remediation.test.ts index 15d97609f04..a1cfbb3ea55 100644 --- a/test/openclaw-npm-remediation.test.ts +++ b/test/openclaw-npm-remediation.test.ts @@ -19,6 +19,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { buildRemediatedOpenClawArchive, hashPackageTree, + patchCurrentOpenClawCorePackageGraph, patchLegacyOpenClawCorePackageGraph, patchOpenClawDiagnosticsOtelPackageGraph, patchOpenClawPluginPackageGraph, @@ -140,6 +141,73 @@ function writeLegacyCoreFixture(tarVersion = "7.5.11"): string { return directory; } +function writeCurrentCoreFixture( + braceExpansionVersion = "5.0.7", + fastUriVersion = "3.1.2", +): string { + const directory = mkdtempSync(path.join(tmpdir(), "nemoclaw-current-openclaw-core-remediation-")); + temporaryDirectories.push(directory); + writeFileSync( + path.join(directory, "package.json"), + `${JSON.stringify( + { + name: "openclaw", + version: "2026.7.1", + dependencies: { + "@modelcontextprotocol/sdk": "1.29.0", + minimatch: "10.2.5", + }, + }, + null, + 2, + )}\n`, + ); + writeFileSync( + path.join(directory, "npm-shrinkwrap.json"), + `${JSON.stringify( + { + name: "openclaw", + version: "2026.7.1", + lockfileVersion: 3, + packages: { + "": { + name: "openclaw", + version: "2026.7.1", + dependencies: { + "@modelcontextprotocol/sdk": "1.29.0", + minimatch: "10.2.5", + }, + }, + "node_modules/ajv": { + version: "8.20.0", + dependencies: { "fast-uri": "^3.0.1" }, + }, + "node_modules/brace-expansion": { + version: braceExpansionVersion, + resolved: `https://registry.npmjs.org/brace-expansion/-/brace-expansion-${braceExpansionVersion}.tgz`, + integrity: + "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + dependencies: { "balanced-match": "^4.0.2" }, + }, + "node_modules/fast-uri": { + version: fastUriVersion, + resolved: `https://registry.npmjs.org/fast-uri/-/fast-uri-${fastUriVersion}.tgz`, + integrity: + "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + }, + "node_modules/minimatch": { + version: "10.2.5", + dependencies: { "brace-expansion": "^5.0.5" }, + }, + }, + }, + null, + 2, + )}\n`, + ); + return directory; +} + function readJson(file: string): T { return JSON.parse(readFileSync(file, "utf-8")) as T; } @@ -351,6 +419,40 @@ describe("OpenClaw npm remediation", () => { ); }); + it("replaces the reviewed current OpenClaw transitive resolutions", () => { + const directory = writeCurrentCoreFixture(); + + patchCurrentOpenClawCorePackageGraph(directory); + + const shrinkwrap = readJson<{ + packages: Record; + }>(path.join(directory, "npm-shrinkwrap.json")); + expect(shrinkwrap.packages["node_modules/brace-expansion"]).toMatchObject({ + version: "5.0.8", + resolved: "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + integrity: + "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + }); + expect(shrinkwrap.packages["node_modules/fast-uri"]).toMatchObject({ + version: "3.1.4", + resolved: "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + integrity: + "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + }); + }); + + it.each([ + ["brace-expansion", "5.0.6", "brace-expansion layout changed after review"], + ["fast-uri", "3.1.1", "fast-uri layout changed after review"], + ])("rejects a current OpenClaw %s graph that changed after review", (dependency, version, error) => { + const directory = + dependency === "brace-expansion" + ? writeCurrentCoreFixture(version) + : writeCurrentCoreFixture("5.0.7", version); + + expect(() => patchCurrentOpenClawCorePackageGraph(directory)).toThrow(error); + }); + it("rebuilds the legacy fixture archive with the reviewed tar package bundled", () => { const fixture = writeLegacyCoreArchiveFixtures(); const request = { diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index b648120a414..257f560f78b 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -2,8 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import fs from "node:fs"; +import { createRequire } from "node:module"; +import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { normalizeOpenClawSignatureAlias } from "../scripts/audit-reviewed-npm-graph.mts"; import { readYaml } from "./helpers/e2e-workflow-contract"; type WorkflowStep = { @@ -33,6 +36,8 @@ const BOOTSTRAP_IF = "${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && github.event.pull_request.number == 6830 && github.event.pull_request.head.repo.full_name == 'HOYALIM/NemoClaw' }}"; const REJECT_UNAVAILABLE_IF = "${{ steps.trusted-reviewed-npm-audit.outputs.available != 'true' && (github.event.pull_request.number != 6830 || github.event.pull_request.head.repo.full_name != 'HOYALIM/NemoClaw') }}"; +const DOMEXCEPTION_INTEGRITY = + "sha512-tlc/FcYIv5i8RYsl2iDil4A0gOihaas1R5jPcIC4Zw3GhjKsVilw90aHcVlhZPTBLGBzd379S+VcnsDjd9ChiA=="; function requiredStep(job: WorkflowJob, name: string): WorkflowStep { const step = job.steps?.find((candidate) => candidate.name === name); @@ -111,7 +116,7 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { "utf8", ); - expect(action).toContain('node-version: "22.22.2"'); + expect(action).toContain('node-version: "22.23.1"'); expect(action).toContain("npm install --global npm@10.9.4"); expect(action).toContain("NEMOCLAW_REVIEWED_NPM_AUDIT_TARGET_ROOT"); expect(action).toContain("NEMOCLAW_REVIEWED_NPM_AUDIT_REPORT_DIR"); @@ -122,4 +127,68 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { expect(driver).toContain("resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)"); expect(driver).not.toContain('resolveTargetPath(\n "ci/reviewed-npm-audit.json"'); }); + + it("normalizes only the reviewed OpenClaw npm alias for registry signature verification", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-signature-alias-")); + const aliasPath = path.join("node_modules", "openclaw", "node_modules", "node-domexception"); + const actualPath = path.join( + "node_modules", + "openclaw", + "node_modules", + "@nolyfill", + "domexception", + ); + const requesterPath = path.join("node_modules", "openclaw", "node_modules", "fetch-blob"); + const aliasManifest = { name: "@nolyfill/domexception", version: "1.0.28" }; + const requesterManifest = { + name: "fetch-blob", + version: "3.2.0", + dependencies: { "node-domexception": "^1.0.0" }, + }; + const lock = { + lockfileVersion: 3, + packages: { + [aliasPath]: { + ...aliasManifest, + resolved: "https://registry.npmjs.org/@nolyfill/domexception/-/domexception-1.0.28.tgz", + integrity: DOMEXCEPTION_INTEGRITY, + }, + [requesterPath]: requesterManifest, + }, + }; + try { + for (const [directory, manifest] of [ + [aliasPath, aliasManifest], + [requesterPath, requesterManifest], + ] as const) { + fs.mkdirSync(path.join(root, directory), { recursive: true }); + fs.writeFileSync( + path.join(root, directory, "package.json"), + `${JSON.stringify(manifest)}\n`, + ); + } + fs.writeFileSync(path.join(root, "package-lock.json"), `${JSON.stringify(lock)}\n`); + + normalizeOpenClawSignatureAlias(root); + + const normalizedLock = JSON.parse( + fs.readFileSync(path.join(root, "package-lock.json"), "utf-8"), + ); + const normalizedRequester = createRequire(import.meta.url)( + path.join(root, requesterPath, "package.json"), + ); + expect(fs.existsSync(path.join(root, aliasPath))).toBe(false); + expect(fs.existsSync(path.join(root, actualPath, "package.json"))).toBe(true); + expect(normalizedLock.packages[aliasPath]).toBeUndefined(); + expect(normalizedLock.packages[actualPath]).toMatchObject(aliasManifest); + expect(normalizedLock.packages[requesterPath].dependencies).toEqual({ + "@nolyfill/domexception": "1.0.28", + }); + expect(normalizedRequester.dependencies).toEqual({ + "@nolyfill/domexception": "1.0.28", + }); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } + }); }); diff --git a/test/sandbox-build-context.test.ts b/test/sandbox-build-context.test.ts index 9fa7c6e4c06..f98d95bfb1c 100644 --- a/test/sandbox-build-context.test.ts +++ b/test/sandbox-build-context.test.ts @@ -39,7 +39,7 @@ describe("sandbox build context staging", () => { path.join("ci", "npm-audit-exceptions.json"), `${JSON.stringify({ schemaVersion: 1, exceptions: [] })}\n`, ); - for (const runtimeName of ["mcporter-runtime", "wechat-runtime"]) { + for (const runtimeName of ["mcporter-runtime", "openclaw-runtime", "wechat-runtime"]) { for (const fileName of ["package.json", "package-lock.json"]) { writeFixture( path.join("agents", "openclaw", runtimeName, fileName), @@ -189,7 +189,7 @@ describe("sandbox build context staging", () => { } function expectStagedOpenClawRuntimeGraphs(buildCtx: string, sourceRoot: string) { - for (const runtimeName of ["mcporter-runtime", "wechat-runtime"]) { + for (const runtimeName of ["mcporter-runtime", "openclaw-runtime", "wechat-runtime"]) { const runtimeDir = path.join(buildCtx, "agents", "openclaw", runtimeName); expect(fs.readdirSync(runtimeDir).sort()).toEqual(["package-lock.json", "package.json"]); for (const fileName of ["package.json", "package-lock.json"]) { From 5a73c020b1b925a6b82e1162d5e71020b6828439 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sat, 25 Jul 2026 05:34:41 -0700 Subject: [PATCH 11/16] docs(security): record OpenClaw graph remediation Signed-off-by: Apurv Kumaria --- .../openclaw-2026.7.1-dependency-review.md | 85 +++++++++++++------ 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/docs/security/openclaw-2026.7.1-dependency-review.md b/docs/security/openclaw-2026.7.1-dependency-review.md index f69f065d4f4..62b1c17ec8f 100644 --- a/docs/security/openclaw-2026.7.1-dependency-review.md +++ b/docs/security/openclaw-2026.7.1-dependency-review.md @@ -15,6 +15,15 @@ graph contains the newly disclosed critical `tar` advisory. The reviewed migration archives, so its manifest and lock move from `7.5.11` to `7.5.20`; the exact plugin graph reports no vulnerabilities after that update. +The production OpenClaw install uses the authoritative committed lock at +`agents/openclaw/openclaw-runtime/package-lock.json`, with SHA-256 +`82489f62febb12da52833c0b1f7f6969f7e21a098c565ef1f91342b1e5e32d88`. +NemoClaw derives that lock from the SRI-verified `openclaw@2026.7.1` archive +after replacing the reviewed affected `brace-expansion@5.0.7` and +`fast-uri@3.1.2` resolutions with `5.0.8` and `3.1.4`. +Image builds verify the lock digest and installed production graph before they +expose the OpenClaw binary. + The release lineage is unusually wide and divergent: the direct upstream comparison reports 4,407 commits ahead and 34 behind. The maintainer requested this exact stable release after reviewing that risk. The long-term source of @@ -52,15 +61,23 @@ whose amd64 config reports Node `22.23.1`. - `tar@7.5.20` (NemoClaw plugin direct dependency) - `sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==` - `https://registry.npmjs.org/tar/-/tar-7.5.20.tgz` +- `brace-expansion@5.0.8` (OpenClaw locked-runtime remediation) + - `sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==` + - `https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz` +- `fast-uri@3.1.4` (OpenClaw and mcporter locked-runtime remediation) + - `sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==` + - `https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz` ## Audit result and temporary dependency remediations -The exact reviewed archive graph contains `823` total dependencies and reports -`13` moderate, `0` high, and `0` critical vulnerabilities. The critical `tar` -finding that blocked the previous pin and the high Jaeger finding are gone. npm -audit expands the remaining `@hono/node-server` advisory through its affected -OpenClaw and MCP dependents, alongside the separate moderate `protobufjs` -finding. Both advisories are below the configured `high` threshold. +The reviewed audit materializes three production-compatible boundaries: the +remediated reviewed-archive graph, the committed OpenClaw runtime lock, and the +committed mcporter runtime lock. A real registry-backed run reports `0` high +and `0` critical vulnerabilities for all three boundaries, and npm registry +signature verification passes for both locked graphs. The critical `tar` +finding that blocked the previous pin and the high Jaeger, +`brace-expansion`, and `fast-uri` findings are gone. Lower-severity findings +remain visible below the configured `high` threshold. The independently installed `nemoclaw/` plugin graph reports `0` vulnerabilities after resolving its direct `tar` dependency to `7.5.20`. @@ -75,14 +92,30 @@ requirement remains inside the image's Node contract, and real ESM plus CommonJS Streamable HTTP transport construction/start/close probes cover the major-version compatibility boundary. -The SDK's locked AJV graph also requests `fast-uri@^3.0.1`; the newly published -`GHSA-v2hh-gcrm-f6hx` affects releases through `3.1.3`. The same manifest pins -the first compatible release outside that range, `3.1.4`. The resulting -`138`-dependency graph reports `0` known vulnerabilities; image assembly reports -lower-severity findings and blocks unaccepted high or critical findings through -the empty-by-default audit exception registry. Signature verification and the -exact committed lock remain mandatory. Remove either override when the declared -graph resolves to a reviewed patched release. +OpenClaw's `minimatch@10.2.5` edge originally resolved +`brace-expansion@5.0.7`, which is affected by `GHSA-mh99-v99m-4gvg`. +The reviewed OpenClaw remediation selects `5.0.8`, retains the declared +`balanced-match@^4.0.2` dependency shape, and fails if the upstream shrinkwrap +or replacement archive identity changes. + +The AJV graphs in OpenClaw and mcporter request `fast-uri@^3.0.1`. +The reviewed upstream OpenClaw shrinkwrap resolves `3.1.2`, and +`GHSA-v2hh-gcrm-f6hx` affects releases through `3.1.3`. +Both committed runtime locks select the first compatible release outside that +range, `3.1.4`. Image assembly reports lower-severity findings and blocks +unaccepted high or critical findings through the empty-by-default audit +exception registry. Signature verification and the exact committed locks +remain mandatory. + +The OpenClaw audit first applies the same fail-closed remediation to the +SRI-verified reviewed archive, then independently installs and verifies the +committed lock. The audit configuration pins the official npm registry origin, +package identity, tarball URL, SRI, and lock SHA-256. It rejects repository +path escapes, lock drift, registry-origin drift, or an installed graph that +does not match the lock before evaluating advisories. Remove the two OpenClaw +core replacements only after a supported OpenClaw archive publishes both +corrected transitive resolutions and the regenerated lock, installed-graph +verification, audit, and signature checks all pass. The published Slack and Microsoft Teams plugin archives bundle `axios@1.16.0`. That version is in the affected range for the newly disclosed Axios @@ -126,13 +159,15 @@ The child-process probe confirms these results: - valid `uberctx-test` baggage remains available; - a valid Jaeger header produces the expected trace and span context. -`scripts/lib/openclaw-npm-remediation.mts` verifies the original plugin and +`scripts/lib/openclaw-npm-remediation.mts` verifies each original package and replacement package identities before it writes the archive. It rejects an -upstream graph that no longer resolves Axios `1.16.0`. It then verifies the -deterministic remediated package-tree integrity before installation. This -canonical tree digest is independent of npm-generated tar metadata, which can -vary between npm patch releases without changing package contents. The -production plugin installer and `reviewed-npm-audit` use this same function. +upstream graph that no longer resolves the reviewed Axios, Jaeger, +`brace-expansion`, or `fast-uri` source versions and dependency shapes. It then +verifies the deterministic remediated package-tree integrity before +installation or lock generation. This canonical tree digest is independent of +npm-generated tar metadata, which can vary between npm patch releases without +changing package contents. The production plugin installer, OpenClaw lock +workflow, and `reviewed-npm-audit` use this same function. The tree hash opens each regular file without following symbolic links and validates the opened descriptor before it reads the content. This keeps the metadata and content checks bound to the same file. @@ -146,10 +181,12 @@ Issue #7337 tracks removal of the Jaeger branch and its exact replacement pins. The reviewed installer verifies each registry identity and downloaded tarball integrity. `scripts/lib/reviewed-npm-archive.mts` uses `npm pack --json` and -rejects reported archive filenames containing unsafe archive paths. Its checks -bind reviewed npm installs to verified local archives and check each reviewed -npm plugin registry integrity. The helper returns only the verified local `.tgz` -path. +rejects reported archive filenames containing unsafe archive paths. Its archive +checks bind reviewed npm installs to verified local archives: they compare each +reviewed npm plugin registry integrity, and the helper returns only the verified +local `.tgz` path. Its locked-runtime +checks bind the OpenClaw and mcporter installs to exact committed lock digests, +the official registry origin, and post-install graph verification. ## OpenClaw Compiled-Dist Patch Runtime Boundary From 6211fb361bfc01754526c3237f20449d277632e6 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sat, 25 Jul 2026 05:55:23 -0700 Subject: [PATCH 12/16] fix(ci): refresh reviewed audit bootstrap Signed-off-by: Apurv Kumaria --- .github/workflows/pr.yaml | 2 +- test/reviewed-npm-audit-workflow.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 271a2ed7cc2..8d7fc269c03 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -319,7 +319,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: HOYALIM/NemoClaw - ref: 57c97bf5dc0bf2489ec494d4637977be3986afb8 + ref: 0c7dd29394d2c4db660c4d09f3654c0789e200d0 path: .trusted-reviewed-npm-audit-bootstrap persist-credentials: false sparse-checkout: | diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index 257f560f78b..c8e877ce758 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -29,7 +29,7 @@ type Workflow = { }; const REPO_ROOT = path.join(import.meta.dirname, ".."); -const BOOTSTRAP_SHA = "57c97bf5dc0bf2489ec494d4637977be3986afb8"; +const BOOTSTRAP_SHA = "0c7dd29394d2c4db660c4d09f3654c0789e200d0"; // Removal condition: delete the PR-6830 fork bootstrap after this PR merges and // the base branch contains the schema-v2 reviewed npm audit action. const BOOTSTRAP_IF = From ec2574b4a49d850c98db1fbd3228fef623ecc9db Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sat, 25 Jul 2026 06:05:43 -0700 Subject: [PATCH 13/16] fix(ci): include reviewed audit helper closure Signed-off-by: Apurv Kumaria --- .github/workflows/pr.yaml | 8 +++++++- test/reviewed-npm-audit-workflow.test.ts | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 8d7fc269c03..e2c9f5e72c0 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -295,7 +295,9 @@ jobs: .github/actions/ci-reviewed-npm-audit ci/reviewed-npm-audit.json scripts/audit-reviewed-npm-graph.mts + scripts/lib/openclaw-npm-remediation.mts scripts/lib/reviewed-npm-archive.mts + scripts/lib/reviewed-npm-audit.mts sparse-checkout-cone-mode: false - name: Detect trusted reviewed npm audit schema @@ -306,7 +308,9 @@ jobs: .trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit/action.yaml \ && grep -Fq 'resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)' \ .trusted-reviewed-npm-audit/scripts/audit-reviewed-npm-graph.mts \ - && [ -f .trusted-reviewed-npm-audit/ci/reviewed-npm-audit.json ]; then + && [ -f .trusted-reviewed-npm-audit/ci/reviewed-npm-audit.json ] \ + && [ -f .trusted-reviewed-npm-audit/scripts/lib/openclaw-npm-remediation.mts ] \ + && [ -f .trusted-reviewed-npm-audit/scripts/lib/reviewed-npm-audit.mts ]; then echo "available=true" >> "$GITHUB_OUTPUT" else echo "available=false" >> "$GITHUB_OUTPUT" @@ -326,7 +330,9 @@ jobs: .github/actions/ci-reviewed-npm-audit ci/reviewed-npm-audit.json scripts/audit-reviewed-npm-graph.mts + scripts/lib/openclaw-npm-remediation.mts scripts/lib/reviewed-npm-archive.mts + scripts/lib/reviewed-npm-audit.mts sparse-checkout-cone-mode: false - name: Reject unavailable trusted reviewed npm audit diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index c8e877ce758..7a1bc084cd5 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -64,12 +64,20 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { expect(sparseCheckout).toContain(".github/actions/ci-reviewed-npm-audit"); expect(sparseCheckout).toContain("ci/reviewed-npm-audit.json"); expect(sparseCheckout).toContain("scripts/audit-reviewed-npm-graph.mts"); + expect(sparseCheckout).toContain("scripts/lib/openclaw-npm-remediation.mts"); expect(sparseCheckout).toContain("scripts/lib/reviewed-npm-archive.mts"); + expect(sparseCheckout).toContain("scripts/lib/reviewed-npm-audit.mts"); const detection = requiredStep(prJob, "Detect trusted reviewed npm audit schema"); expect(detection.id).toBe("trusted-reviewed-npm-audit"); expect(detection.run).toContain("resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)"); expect(detection.run).toContain(".trusted-reviewed-npm-audit/ci/reviewed-npm-audit.json"); + expect(detection.run).toContain( + ".trusted-reviewed-npm-audit/scripts/lib/openclaw-npm-remediation.mts", + ); + expect(detection.run).toContain( + ".trusted-reviewed-npm-audit/scripts/lib/reviewed-npm-audit.mts", + ); const bootstrap = requiredStep(prJob, "Checkout pinned bootstrap reviewed npm audit"); expect(bootstrap.if).toBe(BOOTSTRAP_IF); @@ -79,6 +87,9 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { path: ".trusted-reviewed-npm-audit-bootstrap", "persist-credentials": false, }); + const bootstrapSparseCheckout = String(bootstrap.with?.["sparse-checkout"]); + expect(bootstrapSparseCheckout).toContain("scripts/lib/openclaw-npm-remediation.mts"); + expect(bootstrapSparseCheckout).toContain("scripts/lib/reviewed-npm-audit.mts"); const rejectUnavailable = requiredStep(prJob, "Reject unavailable trusted reviewed npm audit"); expect(rejectUnavailable.if).toBe(REJECT_UNAVAILABLE_IF); expect(rejectUnavailable.run).toContain("exit 1"); From d727cca33ccfb1ef4feda49129014d1a8c4a0dca Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sat, 25 Jul 2026 06:11:03 -0700 Subject: [PATCH 14/16] fix(ci): include reviewed audit policy Signed-off-by: Apurv Kumaria --- .github/workflows/pr.yaml | 3 +++ test/reviewed-npm-audit-workflow.test.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index e2c9f5e72c0..45ddc5103e5 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -293,6 +293,7 @@ jobs: persist-credentials: false sparse-checkout: | .github/actions/ci-reviewed-npm-audit + ci/npm-audit-exceptions.json ci/reviewed-npm-audit.json scripts/audit-reviewed-npm-graph.mts scripts/lib/openclaw-npm-remediation.mts @@ -308,6 +309,7 @@ jobs: .trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit/action.yaml \ && grep -Fq 'resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)' \ .trusted-reviewed-npm-audit/scripts/audit-reviewed-npm-graph.mts \ + && [ -f .trusted-reviewed-npm-audit/ci/npm-audit-exceptions.json ] \ && [ -f .trusted-reviewed-npm-audit/ci/reviewed-npm-audit.json ] \ && [ -f .trusted-reviewed-npm-audit/scripts/lib/openclaw-npm-remediation.mts ] \ && [ -f .trusted-reviewed-npm-audit/scripts/lib/reviewed-npm-audit.mts ]; then @@ -328,6 +330,7 @@ jobs: persist-credentials: false sparse-checkout: | .github/actions/ci-reviewed-npm-audit + ci/npm-audit-exceptions.json ci/reviewed-npm-audit.json scripts/audit-reviewed-npm-graph.mts scripts/lib/openclaw-npm-remediation.mts diff --git a/test/reviewed-npm-audit-workflow.test.ts b/test/reviewed-npm-audit-workflow.test.ts index 7a1bc084cd5..e69328126b3 100644 --- a/test/reviewed-npm-audit-workflow.test.ts +++ b/test/reviewed-npm-audit-workflow.test.ts @@ -62,6 +62,7 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { }); const sparseCheckout = String(trustedCheckout.with?.["sparse-checkout"]); expect(sparseCheckout).toContain(".github/actions/ci-reviewed-npm-audit"); + expect(sparseCheckout).toContain("ci/npm-audit-exceptions.json"); expect(sparseCheckout).toContain("ci/reviewed-npm-audit.json"); expect(sparseCheckout).toContain("scripts/audit-reviewed-npm-graph.mts"); expect(sparseCheckout).toContain("scripts/lib/openclaw-npm-remediation.mts"); @@ -71,6 +72,7 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { const detection = requiredStep(prJob, "Detect trusted reviewed npm audit schema"); expect(detection.id).toBe("trusted-reviewed-npm-audit"); expect(detection.run).toContain("resolveTrustedAuditConfigPath(TRUSTED_REPO_ROOT)"); + expect(detection.run).toContain(".trusted-reviewed-npm-audit/ci/npm-audit-exceptions.json"); expect(detection.run).toContain(".trusted-reviewed-npm-audit/ci/reviewed-npm-audit.json"); expect(detection.run).toContain( ".trusted-reviewed-npm-audit/scripts/lib/openclaw-npm-remediation.mts", @@ -88,6 +90,7 @@ describe("trusted reviewed npm audit workflow (#5896)", () => { "persist-credentials": false, }); const bootstrapSparseCheckout = String(bootstrap.with?.["sparse-checkout"]); + expect(bootstrapSparseCheckout).toContain("ci/npm-audit-exceptions.json"); expect(bootstrapSparseCheckout).toContain("scripts/lib/openclaw-npm-remediation.mts"); expect(bootstrapSparseCheckout).toContain("scripts/lib/reviewed-npm-audit.mts"); const rejectUnavailable = requiredStep(prJob, "Reject unavailable trusted reviewed npm audit"); From b53750b0bb67304dddef404d7e59c3942f8ebbe2 Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sat, 25 Jul 2026 06:47:36 -0700 Subject: [PATCH 15/16] fix(ci): align locked OpenClaw test contracts Signed-off-by: Apurv Kumaria --- .github/workflows/base-image.yaml | 1 - test/fetch-guard-patch-regression.test.ts | 47 ++++++++++++++--------- test/openclaw-final-image-layout.test.ts | 2 +- test/openclaw-lifecycle-policy.test.ts | 19 ++++++++- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/.github/workflows/base-image.yaml b/.github/workflows/base-image.yaml index 7a9e3178f04..241c0b3fe8e 100644 --- a/.github/workflows/base-image.yaml +++ b/.github/workflows/base-image.yaml @@ -37,7 +37,6 @@ on: - "nemoclaw-blueprint/blueprint.yaml" - "scripts/lib/openclaw-npm-remediation.mts" - "scripts/lib/reviewed-npm-audit.mts" - - "scripts/lib/reviewed-npm-archive.mts" - "scripts/checks/node-tar-image-scan.mts" - "scripts/patch-bundled-npm-tar.mts" - "scripts/upgrade-bundled-npm.mts" diff --git a/test/fetch-guard-patch-regression.test.ts b/test/fetch-guard-patch-regression.test.ts index d6ed5bd58e1..87e18b1a600 100644 --- a/test/fetch-guard-patch-regression.test.ts +++ b/test/fetch-guard-patch-regression.test.ts @@ -142,6 +142,7 @@ function runOpenClawUpgradeBlock(currentVersion: string) { const blueprint = path.join(tmp, "blueprint.yaml"); const log = path.join(tmp, "calls.log"); const openclawInstall = path.join(tmp, "openclaw-global"); + const openclawRuntime = path.join(tmp, "openclaw-runtime"); const openclawShim = path.join(tmp, "openclaw-bin"); const mcporterInstall = path.join(tmp, "mcporter-runtime"); const mcporterShim = path.join(tmp, "mcporter-bin"); @@ -160,9 +161,21 @@ function runOpenClawUpgradeBlock(currentVersion: string) { ); fs.writeFileSync(blueprint, `min_openclaw_version: "${readBlueprintMinOpenClawVersion()}"\n`); fs.mkdirSync(openclawInstall, { recursive: true }); + fs.mkdirSync(openclawRuntime, { recursive: true }); fs.mkdirSync(mcporterInstall, { recursive: true }); fs.mkdirSync(reviewedArchiveDir); fs.writeFileSync(path.join(mcporterInstall, "package-lock.json"), "{}"); + fs.copyFileSync( + path.join( + import.meta.dirname, + "..", + "agents", + "openclaw", + "openclaw-runtime", + "package-lock.json", + ), + path.join(openclawRuntime, "package-lock.json"), + ); fs.writeFileSync(openclawShim, ""); fs.writeFileSync(mcporterShim, ""); fs.writeFileSync(auditExceptions, '{"schemaVersion":1,"exceptions":[]}\n'); @@ -173,6 +186,11 @@ function runOpenClawUpgradeBlock(currentVersion: string) { ) .replaceAll("/opt/nemoclaw-blueprint/blueprint.yaml", blueprint) .replaceAll("/usr/local/lib/node_modules/openclaw", openclawInstall) + .replaceAll( + "mkdir -p /usr/local/lib/node_modules", + `mkdir -p ${JSON.stringify(path.dirname(openclawInstall))}`, + ) + .replaceAll("/usr/local/lib/nemoclaw/openclaw-runtime", openclawRuntime) .replaceAll("/usr/local/bin/openclaw", openclawShim) .replaceAll("/usr/local/lib/node_modules/mcporter", mcporterInstall) .replaceAll("/usr/local/lib/nemoclaw/mcporter-runtime", mcporterInstall) @@ -189,7 +207,7 @@ function runOpenClawUpgradeBlock(currentVersion: string) { `real_node=${JSON.stringify(process.execPath)}`, `audit_exceptions=${JSON.stringify(auditExceptions)}`, `mcporter_install=${JSON.stringify(mcporterInstall)}`, - `postinstall_path=${JSON.stringify(path.join(openclawInstall, "scripts/postinstall-bundled-plugins.mjs"))}`, + `postinstall_path=${JSON.stringify(path.join(openclawRuntime, "node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs"))}`, `reviewed_archive=${JSON.stringify(reviewedArchive)}`, `OPENCLAW_VERSION=${JSON.stringify(openclawVersion)}`, `BASE_IMAGE=${JSON.stringify("registry.example/nemoclaw-test-base:latest")}`, @@ -210,6 +228,7 @@ function runOpenClawUpgradeBlock(currentVersion: string) { ' printf "node %s\\n" "$*" >> "$call_log"; return 0;', " fi", ' if [ "${2:-}" = "/scripts/lib/reviewed-npm-archive.mts" ]; then', + ' if [ "${3:-}" = "--verify-lock" ] || [ "${3:-}" = "--verify-installed-lock" ]; then return 0; fi', ' if [ "${3:-}" = "--verify-only" ]; then', ' [ "$#" -eq 11 ] && [ "${4:-}" = "--package-spec" ] && [ "${5:-}" = "mcporter@${MCPORTER_VERSION}" ] || return 91;', ' [ "${6:-}" = "--integrity" ] && [ "${7:-}" = "$MCPORTER_0_7_3_INTEGRITY" ] || return 92;', @@ -352,38 +371,30 @@ describe("fetch-guard patch regression guard", () => { expect(fs.existsSync(inspectMarker)).toBe(true); }); - it("installs the reviewed archive for stale and same-version OpenClaw bases", () => { + it("installs the reviewed locked graph for stale and same-version OpenClaw bases", () => { const stale = runOpenClawUpgradeBlock("2026.3.11"); expect(stale.result.status, stale.result.stderr).toBe(0); expect(stale.result.stdout).toContain( `Base image OpenClaw 2026.3.11 lacks exact reviewed provenance; installing ${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}`, ); - expect(stale.calls).toContain( - `npm pack https://registry.npmjs.org/openclaw/-/openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz --pack-destination`, - ); - expect(stale.calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", + expect(stale.calls).toMatch( + /npm --prefix \S+\/openclaw-runtime ci --ignore-scripts --omit=dev --no-audit --no-fund --no-progress/, ); expect(stale.calls).toContain("postinstall-bundled-plugins.mjs"); - expect(stale.calls).toContain( - `openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz`, - ); + expect(stale.calls).not.toContain("npm install -g"); + expect(stale.calls).not.toContain("npm pack"); const current = runOpenClawUpgradeBlock(CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION); expect(current.result.status, current.result.stderr).toBe(0); expect(current.result.stdout).toContain( `Base image OpenClaw ${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION} lacks exact reviewed provenance; installing ${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}`, ); - expect(current.calls).toContain( - `npm pack https://registry.npmjs.org/openclaw/-/openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz --pack-destination`, - ); - expect(current.calls).toContain( - "npm install -g --no-audit --no-fund --no-progress --ignore-scripts ", + expect(current.calls).toMatch( + /npm --prefix \S+\/openclaw-runtime ci --ignore-scripts --omit=dev --no-audit --no-fund --no-progress/, ); expect(current.calls).toContain("postinstall-bundled-plugins.mjs"); - expect(current.calls).toContain( - `openclaw-${CURRENT_REVIEWED_OPENCLAW_PATCH_CLASSIFIER_VERSION}.tgz`, - ); + expect(current.calls).not.toContain("npm install -g"); + expect(current.calls).not.toContain("npm pack"); const newer = runOpenClawUpgradeBlock("2026.7.2"); expect(newer.result.status).toBe(1); diff --git a/test/openclaw-final-image-layout.test.ts b/test/openclaw-final-image-layout.test.ts index fc2ac4acd14..7fe8ec8ef5b 100644 --- a/test/openclaw-final-image-layout.test.ts +++ b/test/openclaw-final-image-layout.test.ts @@ -18,7 +18,7 @@ describe("OpenClaw final image layout", () => { const payloads = [ { stage: "openclaw-dependency-payload", - copies: 9, + copies: 11, metadata: "/ /usr /usr/local /usr/local/lib", }, { diff --git a/test/openclaw-lifecycle-policy.test.ts b/test/openclaw-lifecycle-policy.test.ts index 2123034d2cb..fdd11812769 100644 --- a/test/openclaw-lifecycle-policy.test.ts +++ b/test/openclaw-lifecycle-policy.test.ts @@ -23,14 +23,29 @@ function corePackageSpecs(block) { } function explicitLifecycleScripts(block) { - return [...block.matchAll( + const scripts = [...block.matchAll( /^\s*([0-9]+(?:\.[0-9]+){2}(?:\|[0-9]+(?:\.[0-9]+){2})*)\)\s+(node [^;]+postinstall-bundled-plugins\.mjs)\s+;;/gm, )].flatMap((match) => match[1].split("|").map((version) => ({ packageSpec: "openclaw@" + version, explicitCommand: match[2], })), - ).sort((left, right) => left.packageSpec.localeCompare(right.packageSpec)); + ); + const lockedRuntimeCommand = + "node /usr/local/lib/nemoclaw/openclaw-runtime/node_modules/openclaw/scripts/postinstall-bundled-plugins.mjs"; + if ( + block.includes("npm --prefix /usr/local/lib/nemoclaw/openclaw-runtime ci") && + block.includes(lockedRuntimeCommand) + ) { + const manifest = JSON.parse( + fs.readFileSync("agents/openclaw/openclaw-runtime/package.json", "utf8"), + ); + scripts.push({ + packageSpec: "openclaw@" + manifest.dependencies.openclaw, + explicitCommand: lockedRuntimeCommand, + }); + } + return scripts.sort((left, right) => left.packageSpec.localeCompare(right.packageSpec)); } const dockerfile = fs.readFileSync("Dockerfile", "utf8"); From b909b5d385bdddf190e373cafa0c34677cc88258 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Sat, 25 Jul 2026 08:04:16 -0700 Subject: [PATCH 16/16] fix(ci): remove stale reviewed metadata import Signed-off-by: Ho Lim --- scripts/audit-reviewed-npm-graph.mts | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/audit-reviewed-npm-graph.mts b/scripts/audit-reviewed-npm-graph.mts index ade794d16ba..e2e46dbc044 100755 --- a/scripts/audit-reviewed-npm-graph.mts +++ b/scripts/audit-reviewed-npm-graph.mts @@ -12,7 +12,6 @@ import { packReviewedNpmArchive, verifyInstalledNpmLock, verifyReviewedNpmLock, - verifyReviewedNpmMetadata, } from "./lib/reviewed-npm-archive.mts"; import { assertExceptionGraphs,