Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e7aabcc
fix(images): remove vulnerable node-tar copies
prekshivyas Jul 21, 2026
de198e3
fix(images): address node-tar validation feedback
prekshivyas Jul 21, 2026
1b13eb0
fix(images): harden node-tar review checks
prekshivyas Jul 21, 2026
cdb0407
test(images): fail closed on missing Docker stage
prekshivyas Jul 21, 2026
6e9b70b
fix(ci): preserve node-tar scanner imports
cjagwani Jul 21, 2026
7e6714d
fix(ci): keep node-tar image scan standalone
prekshivyas Jul 21, 2026
be631a0
fix(images): refresh mcporter advisory lock
cjagwani Jul 21, 2026
fc386e7
fix(images): remediate mcporter Hono advisory
prekshivyas Jul 21, 2026
a0fc5d0
fix(security): remediate Jaeger propagator advisory
cjagwani Jul 21, 2026
5bf0283
fix(security): bootstrap npm tar without npm
cjagwani Jul 21, 2026
5d25881
fix(security): eliminate tar archive check race
cjagwani Jul 21, 2026
e06c1cb
merge(images): reconcile security remediation updates
prekshivyas Jul 21, 2026
c317e52
Merge remote-tracking branch 'origin/codex/fix-node-tar-images' into …
prekshivyas Jul 21, 2026
ab9e1b8
Merge branch 'main' into codex/fix-node-tar-images
prekshivyas Jul 21, 2026
ca645df
test(images): model mcporter compatibility smoke
cjagwani Jul 21, 2026
33a2a55
fix(images): track bundled tar remediation lifecycle
cjagwani Jul 21, 2026
bc5d86f
chore(ci): retry credentialed e2e gate
cjagwani Jul 21, 2026
9bc45c1
chore(ci): recover cancelled credentialed e2e
prekshivyas Jul 21, 2026
7ec51ca
fix(deps): remediate fast-uri advisory
senthilr-nv Jul 22, 2026
3317170
merge: sync current main into PR #7332
senthilr-nv Jul 22, 2026
bc9f804
merge: sync latest main into PR #7332
senthilr-nv Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/base-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ on:
- "nemoclaw-blueprint/blueprint.yaml"
- "scripts/lib/openclaw-npm-remediation.mts"
- "scripts/lib/reviewed-npm-archive.mts"
- "scripts/checks/node-tar-image-scan.mts"
- "scripts/patch-bundled-npm-tar.mts"
- "scripts/lib/sandbox-rlimits.sh"
workflow_dispatch:
inputs:
Expand Down
93 changes: 93 additions & 0 deletions .github/workflows/sandbox-images-and-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,37 @@ jobs:
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-production .

- name: Scan completed OpenClaw image for node-tar
id: node-tar-scan
shell: bash
run: |
set -euo pipefail
image_id="$(docker image inspect --format '{{.Id}}' nemoclaw-production)"
docker run --rm \
--network none \
--read-only \
--cap-drop ALL \
--cap-add DAC_READ_SEARCH \
--security-opt no-new-privileges \
--pids-limit 64 \
--memory 256m \
--entrypoint node \
-v "${{ github.workspace }}/scripts/checks/node-tar-image-scan.mts:/scripts/checks/node-tar-image-scan.mts:ro" \
nemoclaw-production \
--experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / \
--image "${image_id}" \
| tee /tmp/openclaw-node-tar-inventory.json

- name: Upload OpenClaw node-tar inventory
if: ${{ always() && steps.node-tar-scan.outcome != 'skipped' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: openclaw-node-tar-inventory
path: /tmp/openclaw-node-tar-inventory.json
if-no-files-found: error
retention-days: 14

- name: Build sandbox test image (fixtures layered on production)
run: docker build -f test/Dockerfile.sandbox --build-arg BASE_IMAGE=nemoclaw-production -t nemoclaw-sandbox-test .

Expand Down Expand Up @@ -160,6 +191,37 @@ jobs:
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-hermes-production .

- name: Scan completed Hermes image for node-tar
id: node-tar-scan
shell: bash
run: |
set -euo pipefail
image_id="$(docker image inspect --format '{{.Id}}' nemoclaw-hermes-production)"
docker run --rm \
--network none \
--read-only \
--cap-drop ALL \
--cap-add DAC_READ_SEARCH \
--security-opt no-new-privileges \
--pids-limit 64 \
--memory 256m \
--entrypoint node \
-v "${{ github.workspace }}/scripts/checks/node-tar-image-scan.mts:/scripts/checks/node-tar-image-scan.mts:ro" \
nemoclaw-hermes-production \
--experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / \
--image "${image_id}" \
| tee /tmp/hermes-node-tar-inventory.json

- name: Upload Hermes node-tar inventory
if: ${{ always() && steps.node-tar-scan.outcome != 'skipped' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: hermes-node-tar-inventory
path: /tmp/hermes-node-tar-inventory.json
if-no-files-found: error
retention-days: 14

- name: Verify sandbox user can read copied files
run: |
set -euo pipefail
Expand Down Expand Up @@ -364,6 +426,37 @@ jobs:
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-production-arm64 .

- name: Scan completed OpenClaw arm64 image for node-tar
id: node-tar-scan
shell: bash
run: |
set -euo pipefail
image_id="$(docker image inspect --format '{{.Id}}' nemoclaw-production-arm64)"
docker run --rm \
--network none \
--read-only \
--cap-drop ALL \
--cap-add DAC_READ_SEARCH \
--security-opt no-new-privileges \
--pids-limit 64 \
--memory 256m \
--entrypoint node \
-v "${{ github.workspace }}/scripts/checks/node-tar-image-scan.mts:/scripts/checks/node-tar-image-scan.mts:ro" \
nemoclaw-production-arm64 \
--experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / \
--image "${image_id}" \
| tee /tmp/openclaw-arm64-node-tar-inventory.json

- name: Upload OpenClaw arm64 node-tar inventory
if: ${{ always() && steps.node-tar-scan.outcome != 'skipped' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: openclaw-arm64-node-tar-inventory
path: /tmp/openclaw-arm64-node-tar-inventory.json
if-no-files-found: error
retention-days: 14

- name: Build sandbox test image on arm64
run: docker build -f test/Dockerfile.sandbox --build-arg BASE_IMAGE=nemoclaw-production-arm64 -t nemoclaw-sandbox-test-arm64 .

Expand Down
27 changes: 24 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,24 @@ 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

# OpenShell blocks the link-local EC2 Instance Metadata Service. Keep AWS SDK
# credential chains from attempting an impossible metadata discovery path.
ENV AWS_EC2_METADATA_DISABLED=true

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
COPY agents/openclaw/wechat-runtime/package-lock.json /usr/local/lib/nemoclaw/wechat-runtime/package-lock.json
COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts
COPY scripts/lib/openclaw-npm-remediation.mts /scripts/lib/openclaw-npm-remediation.mts
COPY scripts/patch-bundled-npm-tar.mts /scripts/patch-bundled-npm-tar.mts

# OpenShell blocks the link-local EC2 Instance Metadata Service. Keep AWS SDK
# credential chains from attempting an impossible metadata discovery path.
ENV AWS_EC2_METADATA_DISABLED=true
# The final image owns the shipped dependency boundary independently of base
# freshness. Reassert the npm-private node-tar fix here; the helper is
# idempotent for a remediated base and fails closed on unexpected npm layouts.
RUN node --experimental-strip-types /scripts/patch-bundled-npm-tar.mts \
--npm-root /usr/local/lib/node_modules/npm

# OpenClaw 2026.6.10 loads some generated source through jiti. Disable its
# filesystem transform cache so source fragments that mention provider marker
Expand Down Expand Up @@ -377,6 +385,10 @@ RUN set -eu; \
rm -rf /usr/local/lib/node_modules/mcporter /usr/local/bin/mcporter; \
npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime ci \
--ignore-scripts --omit=dev --no-audit --no-fund --no-progress; \
npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime ls \
--omit=dev --all @hono/node-server @modelcontextprotocol/sdk mcporter >/dev/null; \
node --input-type=module -e \
'const { StreamableHTTPServerTransport } = await import("file:///usr/local/lib/nemoclaw/mcporter-runtime/node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js"); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); await transport.close();'; \
ln -s /usr/local/lib/nemoclaw/mcporter-runtime/node_modules/.bin/mcporter /usr/local/bin/mcporter; \
test "$(mcporter --version)" = "$MCPORTER_VERSION"; \
npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime audit --omit=dev --audit-level=low; \
Expand Down Expand Up @@ -1459,6 +1471,15 @@ RUN set -eu; \
fi; \
fi

# Gate the completed local filesystem too; CI repeats this scan in an isolated
# container and retains evidence keyed to the final image ID.
COPY scripts/checks/node-tar-image-scan.mts /scripts/checks/node-tar-image-scan.mts
RUN install -d -m 0755 /usr/local/share/nemoclaw \
&& node --experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / --image build:openclaw \
> /usr/local/share/nemoclaw/node-tar-inventory.json \
&& chmod 0444 /usr/local/share/nemoclaw/node-tar-inventory.json

# Health check: poll the gateway's /health endpoint so Docker (and Compose)
# can detect and restart unhealthy containers in standalone deployments.
# Ref: https://github.com/NVIDIA/NemoClaw/issues/1430
Expand Down
19 changes: 19 additions & 0 deletions Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ COPY agents/openclaw/mcporter-runtime/package.json /usr/local/lib/nemoclaw/mcpor
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
COPY scripts/lib/openclaw-npm-remediation.mts /scripts/lib/openclaw-npm-remediation.mts
COPY scripts/patch-bundled-npm-tar.mts /scripts/patch-bundled-npm-tar.mts

# npm 10.9.7 in the pinned Node 22 image bundles an affected node-tar copy.
# Replace only that private package from a registry- and SRI-verified archive
# before npm processes any OpenClaw or mcporter installation input.
RUN node --experimental-strip-types /scripts/patch-bundled-npm-tar.mts \
--npm-root /usr/local/lib/node_modules/npm

# Keep OpenClaw's jiti-generated source cache out of /tmp so provider marker
# names do not persist in runtime snapshots or leak-scan inputs.
Expand Down Expand Up @@ -307,6 +314,10 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep
&& rm -rf /usr/local/lib/node_modules/mcporter /usr/local/bin/mcporter \
&& npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime ci \
--ignore-scripts --omit=dev --no-audit --no-fund --no-progress \
&& npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime ls \
--omit=dev --all @hono/node-server @modelcontextprotocol/sdk mcporter >/dev/null \
&& node --input-type=module -e \
'const { StreamableHTTPServerTransport } = await import("file:///usr/local/lib/nemoclaw/mcporter-runtime/node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js"); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); await transport.close();' \
&& ln -s /usr/local/lib/nemoclaw/mcporter-runtime/node_modules/.bin/mcporter /usr/local/bin/mcporter \
&& test "$(mcporter --version)" = "$MCPORTER_VERSION" \
&& npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime audit --omit=dev --audit-level=low \
Expand Down Expand Up @@ -411,3 +422,11 @@ RUN { \
&& gosu sandbox bash -lc 'command -v brew' | grep -qx /usr/local/bin/brew \
&& gosu sandbox bash -lc 'brew --prefix' | grep -qx /home/linuxbrew/.linuxbrew \
&& gosu sandbox bash -lc "case \":\${PATH}:\" in *:/home/linuxbrew/.linuxbrew/bin:*) exit 0 ;; *) exit 1 ;; esac"

# Gate the exact completed base filesystem before it can be published.
COPY scripts/checks/node-tar-image-scan.mts /scripts/checks/node-tar-image-scan.mts
RUN install -d -m 0755 /usr/local/share/nemoclaw \
&& node --experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / --image build:openclaw-base \
> /usr/local/share/nemoclaw/node-tar-inventory.json \
&& chmod 0444 /usr/local/share/nemoclaw/node-tar-inventory.json
19 changes: 18 additions & 1 deletion agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ ARG BASE_IMAGE=ghcr.io/nvidia/nemoclaw/hermes-sandbox-base@sha256:fa05221f5c7bca
# hadolint ignore=DL3006
FROM ${BASE_IMAGE}

COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts
COPY scripts/patch-bundled-npm-tar.mts /scripts/patch-bundled-npm-tar.mts

# The final Hermes image owns the shipped dependency boundary independently of
# base freshness. Reassert the idempotent npm-private node-tar fix here.
RUN node --experimental-strip-types /scripts/patch-bundled-npm-tar.mts \
--npm-root /usr/local/lib/node_modules/npm

# Keep the final image contract explicit even when the published base image
# changes independently of this Dockerfile.
RUN set -eu; \
Expand Down Expand Up @@ -106,12 +114,12 @@ COPY agents/hermes/config/ /opt/nemoclaw-hermes-config/config/
COPY agents/hermes/host/managed-tool-gateway-matrix.json /opt/nemoclaw-hermes-config/managed-tool-gateway-matrix.json
COPY src/lib/tool-disclosure.ts /src/lib/tool-disclosure.ts
COPY src/lib/messaging/ /src/lib/messaging/
COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts
COPY scripts/lib/openclaw-npm-remediation.mts /scripts/lib/openclaw-npm-remediation.mts
RUN find /opt/nemoclaw-hermes-config -type d -exec chmod 755 {} + \
&& find /opt/nemoclaw-hermes-config -type f -exec chmod 444 {} + \
&& chmod 444 /src/lib/tool-disclosure.ts \
&& chmod 444 /scripts/lib/reviewed-npm-archive.mts /scripts/lib/openclaw-npm-remediation.mts \
/scripts/patch-bundled-npm-tar.mts \
&& chmod -R a+rX /src/lib/messaging

# Copy blueprint (shared infrastructure)
Expand Down Expand Up @@ -597,6 +605,15 @@ RUN if [ "$NEMOCLAW_DARWIN_VM_COMPAT" = "1" ]; then \
chmod a+rw /sandbox/.bashrc /sandbox/.profile; \
fi

# Gate the completed local filesystem too; CI repeats this scan in an isolated
# container and retains evidence keyed to the final image ID.
COPY scripts/checks/node-tar-image-scan.mts /scripts/checks/node-tar-image-scan.mts
RUN install -d -m 0755 /usr/local/share/nemoclaw \
&& node --experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / --image build:hermes \
> /usr/local/share/nemoclaw/node-tar-inventory.json \
&& chmod 0444 /usr/local/share/nemoclaw/node-tar-inventory.json

# start.sh handles privilege separation: runs as root initially, then drops
# to 'gateway' user via gosu for the agent process. See start.sh.
ENTRYPOINT ["/usr/local/bin/nemoclaw-start"]
Expand Down
17 changes: 17 additions & 0 deletions agents/hermes/Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@

FROM node:24-trixie-slim@sha256:05c08ce4291e9a58f59456a7985176defb12cdd42271f35ff81a3e167ea61d4c

COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts
COPY scripts/patch-bundled-npm-tar.mts /scripts/patch-bundled-npm-tar.mts

# npm 11.13.0 in the pinned Node 24 image bundles an affected node-tar copy.
# Patch that private package before npm processes the checksum-pinned Hermes
# release lockfiles.
RUN node --experimental-strip-types /scripts/patch-bundled-npm-tar.mts \
--npm-root /usr/local/lib/node_modules/npm

ENV DEBIAN_FRONTEND=noninteractive

# Hermes version pinned for reproducibility. All four values below are
Expand Down Expand Up @@ -294,3 +303,11 @@ ENV PATH="/usr/local/bin:/opt/hermes/.venv/bin:${PATH}" \
RUN /usr/local/bin/hermes --version \
&& /opt/hermes/.venv/bin/python -c \
'import mcp; from tools import mcp_tool; assert getattr(mcp_tool, "_MCP_AVAILABLE", False), "Hermes MCP client runtime is unavailable"; assert getattr(mcp_tool, "_MCP_HTTP_AVAILABLE", False), "Hermes MCP Streamable HTTP runtime is unavailable"'

# Gate the exact completed base filesystem before it can be published.
COPY scripts/checks/node-tar-image-scan.mts /scripts/checks/node-tar-image-scan.mts
RUN install -d -m 0755 /usr/local/share/nemoclaw \
&& node --experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / --image build:hermes-base \
> /usr/local/share/nemoclaw/node-tar-inventory.json \
&& chmod 0444 /usr/local/share/nemoclaw/node-tar-inventory.json
16 changes: 16 additions & 0 deletions agents/langchain-deepagents-code/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ FROM ${BASE_IMAGE}

USER root

COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts
COPY scripts/patch-bundled-npm-tar.mts /scripts/patch-bundled-npm-tar.mts

# The final managed image owns the shipped dependency boundary independently
# of base freshness. Reassert the idempotent npm-private node-tar fix here.
RUN node --experimental-strip-types /scripts/patch-bundled-npm-tar.mts \
--npm-root /usr/local/lib/node_modules/npm

RUN set -eu; \
dcode_path="$(command -v dcode 2>/dev/null || true)"; \
if [ "$dcode_path" != "/usr/local/bin/dcode" ]; then \
Expand Down Expand Up @@ -180,6 +188,14 @@ RUN if [ "$NEMOCLAW_DARWIN_VM_COMPAT" = "1" ]; then \
chmod a+rw /sandbox/.bashrc /sandbox/.profile; \
fi

# Gate the completed local filesystem for dynamically built managed images.
COPY scripts/checks/node-tar-image-scan.mts /scripts/checks/node-tar-image-scan.mts
RUN install -d -m 0755 /usr/local/share/nemoclaw \
&& node --experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / --image build:deepagents-code \
> /usr/local/share/nemoclaw/node-tar-inventory.json \
&& chmod 0444 /usr/local/share/nemoclaw/node-tar-inventory.json

USER sandbox
ENTRYPOINT ["/usr/local/bin/nemoclaw-start"]
CMD ["/bin/bash"]
16 changes: 16 additions & 0 deletions agents/langchain-deepagents-code/Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@

FROM node:22-trixie-slim@sha256:2d9f5c76c8f4dd36e8f253bee5d828a83a6c09f36188f0b0414325232e0b175d

COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts
COPY scripts/patch-bundled-npm-tar.mts /scripts/patch-bundled-npm-tar.mts

# Node remains available to the managed terminal at runtime, so remediate
# npm's private node-tar copy even though Deep Agents Code itself is Python.
RUN node --experimental-strip-types /scripts/patch-bundled-npm-tar.mts \
--npm-root /usr/local/lib/node_modules/npm

ENV DEBIAN_FRONTEND=noninteractive \
VIRTUAL_ENV=/opt/venv \
PATH="/usr/local/bin:/opt/venv/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin"
Expand Down Expand Up @@ -114,3 +122,11 @@ ENV HOME=/sandbox \
DEEPAGENTS_CODE_NO_UPDATE_CHECK=1

WORKDIR /sandbox

# Gate the exact completed base filesystem before it can be published.
COPY scripts/checks/node-tar-image-scan.mts /scripts/checks/node-tar-image-scan.mts
RUN install -d -m 0755 /usr/local/share/nemoclaw \
&& node --experimental-strip-types /scripts/checks/node-tar-image-scan.mts \
--root / --image build:deepagents-code-base \
> /usr/local/share/nemoclaw/node-tar-inventory.json \
&& chmod 0444 /usr/local/share/nemoclaw/node-tar-inventory.json
Loading
Loading