diff --git a/.github/workflows/managed-images.yaml b/.github/workflows/managed-images.yaml index 0da44c70f05..cc4dddd9623 100644 --- a/.github/workflows/managed-images.yaml +++ b/.github/workflows/managed-images.yaml @@ -192,6 +192,7 @@ jobs: staging_root="$GITHUB_WORKSPACE/staging-qa-base-source" for relative in \ agents/langchain-deepagents-code/Dockerfile.base \ + scripts/security/build-native-security-packages.sh \ agents/langchain-deepagents-code/requirements.lock do source_file="$candidate_root/$relative" diff --git a/Dockerfile b/Dockerfile index 388238a0030..dc23d9313cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -734,7 +734,7 @@ RUN set -eu; \ apt-get install -y --no-install-recommends procps=2:4.0.4-9; \ fi; \ if [ "$needs_chattr" = "1" ]; then \ - apt-get install -y --no-install-recommends e2fsprogs=1.47.2-3+b11; \ + apt-get install -y --no-install-recommends e2fsprogs=1.47.2-3+b12; \ fi; \ if [ "$needs_tmux" = "1" ]; then \ apt-get install -y --no-install-recommends tmux=3.5a-3; \ @@ -2428,7 +2428,7 @@ RUN set -eu; \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" \ "perl-base=5.44.0-1nemoclaw1" \ "perl=5.44.0-1nemoclaw1" \ "libevent-core-2.1-7t64=2.1.13-stable-1" \ @@ -2441,7 +2441,7 @@ RUN set -eu; \ test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1"; \ test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2"; \ test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2"; \ - test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1"; \ + test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u5+nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl-base)" = "5.44.0-1nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl)" = "5.44.0-1nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' libevent-core-2.1-7t64)" = "2.1.13-stable-1"; \ diff --git a/Dockerfile.base b/Dockerfile.base index 8f17d53e4d0..6b2dbc1f02e 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -64,7 +64,7 @@ FROM node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada75 RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential=12.12 \ ca-certificates=20250419 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ libssl-dev=3.5.7-1~deb13u2 \ openssh-server=1:10.0p1-7+deb13u4 \ @@ -119,18 +119,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3=3.13.5-1 \ python3-pip=25.1.1+dfsg-1 \ python3-venv=3.13.5-1 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ gnupg=2.4.7-21+deb13u1 \ ca-certificates=20250419 \ iproute2=6.15.0-1 \ iptables=1.8.11-2 \ nftables=1.1.3-1 \ - libcap2-bin=1:2.75-10+deb13u1+b1 \ + libcap2-bin=1:2.75-10+deb13u1+b3 \ libssl3t64=3.5.7-1~deb13u2 \ util-linux=2.41-5 \ procps=2:4.0.4-9 \ - e2fsprogs=1.47.2-3+b11 \ + e2fsprogs=1.47.2-3+b12 \ "dos2unix=7.5.2-1*" \ libonig5=6.9.9-1+b1 \ openssh-sftp-server=1:10.0p1-7+deb13u4 \ @@ -219,7 +219,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1" \ && test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2" \ && test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2" \ - && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1" \ + && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u5+nemoclaw1" \ && test "$(dpkg-query -W -f='${Version}' perl)" = "$perl_version_before" \ && test "$(jq --version)" = "jq-1.8.2" \ && ldd /usr/bin/jq | grep -Eq 'libonig[.]so[.]5' \ @@ -246,7 +246,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" \ > /usr/local/share/nemoclaw/security-packages.txt \ && chown root:root /usr/local/share/nemoclaw/security-packages.txt \ && chmod 0644 /usr/local/share/nemoclaw/security-packages.txt \ diff --git a/agents/hermes/Dockerfile b/agents/hermes/Dockerfile index fb13cabae63..3f9d7a46058 100644 --- a/agents/hermes/Dockerfile +++ b/agents/hermes/Dockerfile @@ -1713,6 +1713,11 @@ RUN check_metadata() { \ RUN set -eu; \ security_inventory=/usr/local/share/nemoclaw/security-packages.txt; \ arch="$(dpkg --print-architecture)"; \ + python_fix_version="$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)"; \ + case "$python_fix_version" in \ + 3.13.5-2+deb13u4+nemoclaw1|3.13.5-2+deb13u5+nemoclaw1) ;; \ + *) echo "ERROR: unexpected patched Python package version: $python_fix_version" >&2; exit 1 ;; \ + esac; \ test -f "$security_inventory"; \ test ! -L "$security_inventory"; \ test "$(stat -c '%u:%g:%a' "$security_inventory")" = "0:0:444"; \ @@ -1726,7 +1731,7 @@ RUN set -eu; \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=$python_fix_version" \ "perl-base=5.44.0-1nemoclaw1" \ "perl=5.44.0-1nemoclaw1" \ | cmp -s - "$security_inventory"; \ @@ -1738,7 +1743,6 @@ RUN set -eu; \ test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1"; \ test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2"; \ test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2"; \ - test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl-base)" = "5.44.0-1nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl)" = "5.44.0-1nemoclaw1"; \ test "$(perl -e 'print $^V')" = "v5.44.0"; \ diff --git a/agents/hermes/Dockerfile.base b/agents/hermes/Dockerfile.base index 366d8cf59c4..63f7b582112 100644 --- a/agents/hermes/Dockerfile.base +++ b/agents/hermes/Dockerfile.base @@ -28,7 +28,7 @@ FROM node:24-trixie-slim@sha256:05c08ce4291e9a58f59456a7985176defb12cdd42271f35f RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential=12.12 \ ca-certificates=20250419 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ libssl-dev=3.5.7-1~deb13u2 \ openssh-server=1:10.0p1-7+deb13u4 \ @@ -96,20 +96,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3=3.13.5-1 \ python3-pip=25.1.1+dfsg-1 \ python3-venv=3.13.5-1 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ gnupg=2.4.7-21+deb13u1 \ ca-certificates=20250419 \ iproute2=6.15.0-1 \ iptables=1.8.11-2 \ nftables=1.1.3-1 \ - libcap2-bin=1:2.75-10+deb13u1+b1 \ + libcap2-bin=1:2.75-10+deb13u1+b3 \ libssl3t64=3.5.7-1~deb13u2 \ util-linux=2.41-5 \ procps=2:4.0.4-9 \ - e2fsprogs=1.47.2-3+b11 \ + e2fsprogs=1.47.2-3+b12 \ openssh-sftp-server=1:10.0p1-7+deb13u4 \ - socat=1.8.0.3-1 \ + socat=1.8.0.3-1+deb13u1 \ "dos2unix=7.5.2-1*" \ && arch="$(dpkg --print-architecture)" \ && case "$arch" in \ @@ -189,7 +189,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1" \ && test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2" \ && test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2" \ - && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1" \ + && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u5+nemoclaw1" \ && test "$(jq --version)" = "jq-1.8.2" \ && ldd /usr/bin/jq | grep -Eq 'libonig[.]so[.]5' \ && printf '%s\n' '{"sandbox":"healthy"}' | jq -e '.sandbox == "healthy"' >/dev/null \ @@ -224,7 +224,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" \ "perl-base=5.44.0-1nemoclaw1" \ "perl=5.44.0-1nemoclaw1" \ > /usr/local/share/nemoclaw/security-packages.txt \ diff --git a/agents/langchain-deepagents-code/Dockerfile b/agents/langchain-deepagents-code/Dockerfile index ea8f044d343..de1535a842e 100644 --- a/agents/langchain-deepagents-code/Dockerfile +++ b/agents/langchain-deepagents-code/Dockerfile @@ -415,7 +415,7 @@ RUN set -eu; \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" \ "perl-base=5.44.0-1nemoclaw1" \ "perl=5.44.0-1nemoclaw1" \ | cmp -s - "$security_inventory"; \ @@ -427,7 +427,7 @@ RUN set -eu; \ test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1"; \ test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2"; \ test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2"; \ - test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1"; \ + test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u5+nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl-base)" = "5.44.0-1nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl)" = "5.44.0-1nemoclaw1"; \ test "$(perl -e 'print $^V')" = "v5.44.0"; \ diff --git a/agents/langchain-deepagents-code/Dockerfile.base b/agents/langchain-deepagents-code/Dockerfile.base index 17a2391c8fe..8b3bf991df6 100644 --- a/agents/langchain-deepagents-code/Dockerfile.base +++ b/agents/langchain-deepagents-code/Dockerfile.base @@ -21,7 +21,7 @@ ARG NEMOCLAW_CORPORATE_CA_B64 RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential=12.12 \ ca-certificates=20250419 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ libssl-dev=3.5.7-1~deb13u2 \ openssh-server=1:10.0p1-7+deb13u4 \ @@ -87,17 +87,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3=3.13.5-1 \ python3-pip=25.1.1+dfsg-1 \ python3-venv=3.13.5-1 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ ca-certificates=20250419 \ iproute2=6.15.0-1 \ iptables=1.8.11-2 \ nftables=1.1.3-1 \ - libcap2-bin=1:2.75-10+deb13u1+b1 \ + libcap2-bin=1:2.75-10+deb13u1+b3 \ libssl3t64=3.5.7-1~deb13u2 \ util-linux=2.41-5 \ procps=2:4.0.4-9 \ - e2fsprogs=1.47.2-3+b11 \ + e2fsprogs=1.47.2-3+b12 \ "dos2unix=7.5.2-1*" \ openssh-sftp-server=1:10.0p1-7+deb13u4 \ ripgrep=14.1.1-1+b4 \ @@ -191,7 +191,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1" \ && test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2" \ && test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2" \ - && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1" \ + && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u5+nemoclaw1" \ && test "$(jq --version)" = "jq-1.8.2" \ && ldd /usr/bin/jq | grep -Eq 'libonig[.]so[.]5' \ && printf '%s\n' '{"sandbox":"healthy"}' | jq -e '.sandbox == "healthy"' >/dev/null \ @@ -226,7 +226,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" \ "perl-base=5.44.0-1nemoclaw1" \ "perl=5.44.0-1nemoclaw1" \ > /usr/local/share/nemoclaw/security-packages.txt \ diff --git a/agents/pi/Dockerfile b/agents/pi/Dockerfile index 6c964bb28bf..fa0e1d3a997 100644 --- a/agents/pi/Dockerfile +++ b/agents/pi/Dockerfile @@ -276,7 +276,7 @@ RUN set -eu; \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" \ "perl-base=5.44.0-1nemoclaw1" \ "perl=5.44.0-1nemoclaw1" \ | cmp -s - "$security_inventory"; \ @@ -288,7 +288,7 @@ RUN set -eu; \ test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1"; \ test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2"; \ test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2"; \ - test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1"; \ + test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u5+nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl-base)" = "5.44.0-1nemoclaw1"; \ test "$(dpkg-query -W -f='${Version}' perl)" = "5.44.0-1nemoclaw1"; \ test "$(perl -e 'print $^V')" = "v5.44.0"; \ diff --git a/agents/pi/Dockerfile.base b/agents/pi/Dockerfile.base index d8ad81ac041..3b60ac7bd62 100644 --- a/agents/pi/Dockerfile.base +++ b/agents/pi/Dockerfile.base @@ -25,7 +25,7 @@ ARG NEMOCLAW_CORPORATE_CA_B64 RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential=12.12 \ ca-certificates=20250419 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ libssl-dev=3.5.7-1~deb13u2 \ openssh-server=1:10.0p1-7+deb13u4 \ @@ -91,17 +91,17 @@ ENV DEBIAN_FRONTEND=noninteractive \ # hadolint ignore=DL3001,DL4006 RUN apt-get update && apt-get install -y --no-install-recommends \ python3=3.13.5-1 \ - curl=8.14.1-2+deb13u4 \ + curl=8.14.1-2+deb13u5 \ git=1:2.47.3-0+deb13u1 \ ca-certificates=20250419 \ iproute2=6.15.0-1 \ iptables=1.8.11-2 \ nftables=1.1.3-1 \ - libcap2-bin=1:2.75-10+deb13u1+b1 \ + libcap2-bin=1:2.75-10+deb13u1+b3 \ libssl3t64=3.5.7-1~deb13u2 \ util-linux=2.41-5 \ procps=2:4.0.4-9 \ - e2fsprogs=1.47.2-3+b11 \ + e2fsprogs=1.47.2-3+b12 \ "dos2unix=7.5.2-1*" \ openssh-sftp-server=1:10.0p1-7+deb13u4 \ ripgrep=14.1.1-1+b4 \ @@ -195,7 +195,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && test "$(dpkg-query -W -f='${Version}' vim-tiny)" = "2:9.2.0858-1" \ && test "$(dpkg-query -W -f='${Version}' libssh2-1t64)" = "1.11.1-1+deb13u1+nemoclaw2" \ && test "$(dpkg-query -W -f='${Version}' libssl3t64)" = "3.5.7-1~deb13u2" \ - && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u4+nemoclaw1" \ + && test "$(dpkg-query -W -f='${Version}' nemoclaw-python3.13-htmlparser-fix)" = "3.13.5-2+deb13u5+nemoclaw1" \ && test "$(jq --version)" = "jq-1.8.2" \ && ldd /usr/bin/jq | grep -Eq 'libonig[.]so[.]5' \ && printf '%s\n' '{"sandbox":"healthy"}' | jq -e '.sandbox == "healthy"' >/dev/null \ @@ -230,7 +230,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ "vim-tiny=2:9.2.0858-1" \ "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" \ "libssl3t64=3.5.7-1~deb13u2" \ - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" \ + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" \ "perl-base=5.44.0-1nemoclaw1" \ "perl=5.44.0-1nemoclaw1" \ > /usr/local/share/nemoclaw/security-packages.txt \ diff --git a/ci/pi-agent-qualification-v1-linux-amd64.json b/ci/pi-agent-qualification-v1-linux-amd64.json index b5e4a379db2..76c4527c767 100644 --- a/ci/pi-agent-qualification-v1-linux-amd64.json +++ b/ci/pi-agent-qualification-v1-linux-amd64.json @@ -3,13 +3,13 @@ "agent": "pi", "platform": "linux/amd64", "image": "ghcr.io/nvidia/nemoclaw/pi-sandbox", - "digest": "sha256:943605332e2f14ae41af8a665eeaebcf281e85a91c41219ef5f9f12294ab5eba", - "reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:943605332e2f14ae41af8a665eeaebcf281e85a91c41219ef5f9f12294ab5eba", + "digest": "sha256:838dcc19764788aa7963183e14a063380fc4f03b9e6813b677473e64768296d0", + "reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:838dcc19764788aa7963183e14a063380fc4f03b9e6813b677473e64768296d0", "source": { "repository": "NVIDIA/NemoClaw", - "revision": "322bd36725ed1c6b2d5993fb2e1a727591ccdf90", + "revision": "4a3560862a8262abe087b6e94431b03e825ba965", "release": "v0.1.0", - "cohort": "ghrun-34634582790-1" + "cohort": "ghrun-34717583242-1" }, "startupProfileContractVersion": 1, "capabilityContractVersion": 1 diff --git a/ci/pi-agent-qualification-v1-linux-arm64.json b/ci/pi-agent-qualification-v1-linux-arm64.json index c39ff8d8ab1..df26d8b3789 100644 --- a/ci/pi-agent-qualification-v1-linux-arm64.json +++ b/ci/pi-agent-qualification-v1-linux-arm64.json @@ -3,13 +3,13 @@ "agent": "pi", "platform": "linux/arm64", "image": "ghcr.io/nvidia/nemoclaw/pi-sandbox", - "digest": "sha256:5c10876276dd9c4492925a0a68efb0b447a6d390076110cf6bd883ab7d44c5cd", - "reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:5c10876276dd9c4492925a0a68efb0b447a6d390076110cf6bd883ab7d44c5cd", + "digest": "sha256:45ea8e84013f63437e4aff87e6c82818d726eb3401d786fb9ede6c208df868aa", + "reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:45ea8e84013f63437e4aff87e6c82818d726eb3401d786fb9ede6c208df868aa", "source": { "repository": "NVIDIA/NemoClaw", - "revision": "322bd36725ed1c6b2d5993fb2e1a727591ccdf90", + "revision": "4a3560862a8262abe087b6e94431b03e825ba965", "release": "v0.1.0", - "cohort": "ghrun-34634582790-1" + "cohort": "ghrun-34717583242-1" }, "startupProfileContractVersion": 1, "capabilityContractVersion": 1 diff --git a/scripts/security/build-native-security-packages.sh b/scripts/security/build-native-security-packages.sh index dbbb909a62a..eae571f265c 100755 --- a/scripts/security/build-native-security-packages.sh +++ b/scripts/security/build-native-security-packages.sh @@ -8,7 +8,7 @@ readonly LIBSSH2_VERSION="1.11.1" readonly LIBSSH2_DEBIAN_VERSION="1.11.1-1+deb13u1" readonly LIBSSH2_PACKAGE_VERSION="${LIBSSH2_DEBIAN_VERSION}+nemoclaw2" readonly LIBSSH2_SOURCE_SHA256="9954cb54c4f548198a7cbebad248bdc87dd64bd26185708a294b2b50771e3769" -readonly PYTHON_DEBIAN_VERSION="3.13.5-2+deb13u4" +readonly PYTHON_DEBIAN_VERSION="3.13.5-2+deb13u5" readonly PYTHON_FIX_VERSION="${PYTHON_DEBIAN_VERSION}+nemoclaw1" readonly PYTHON_PARSER_SHA256="f91ec3de6331206bbe2ec3e54a05f646bd23d3c61a18d4a01b25164e070bacc9" readonly PYTHON_PARSER_FIXED_SHA256="4ff43a8578bda2f14686c67911b64c18e869841973722b1c623b5727491bdaf7" @@ -244,10 +244,10 @@ build_python_fix_package() { case "${architecture}" in amd64) - original_sha256="0def2d972310b59704ad119abee5a97f95409e14ff1359edd8cc7b8892cfd43f" + original_sha256="db161322a3481d2c0c3b9a3b9a03c3ab0e2b1718f54b88755fb4a3f939165b84" ;; arm64) - original_sha256="37cce6086b7c1ca93086f83b68761737607689e634693b6972b5dbfd6c080872" + original_sha256="d1178d24e4d143cc6c577d9dc0f26dd982efccc2e600d25ce86361f168a22be0" ;; *) printf 'Unsupported architecture: %s\n' "${architecture}" >&2 @@ -256,7 +256,7 @@ build_python_fix_package() { esac download \ - "${DEBIAN_SNAPSHOT_URL}/p/python3.13/libpython3.13-stdlib_${PYTHON_DEBIAN_VERSION}_${architecture}.deb" \ + "https://snapshot.debian.org/archive/debian/20260906T023042Z/pool/main/p/python3.13/libpython3.13-stdlib_${PYTHON_DEBIAN_VERSION}_${architecture}.deb" \ "${original_deb}" verify_sha256 "${original_sha256}" "${original_deb}" dpkg-deb -x "${original_deb}" "${original_root}" diff --git a/src/lib/agent/candidate-authority.ts b/src/lib/agent/candidate-authority.ts index 5dc82151f67..258639058d0 100644 --- a/src/lib/agent/candidate-authority.ts +++ b/src/lib/agent/candidate-authority.ts @@ -13,8 +13,8 @@ export const CANDIDATE_QUALIFICATION_RECEIPT_DIGESTS: Readonly< Record > = Object.freeze({ pi: Object.freeze([ - "f3f3dd129db2cd0902a0072336f871979c72324fcd8296ad9c1e226e27b1c052", - "d9a727ce41822cb95250459f01bcc75b7105bf76d24f259fbbfff5521cda4b94", + "2cf8ffed471002b66923742a887dab3c7a86573f0caf806f827bd202fa671f26", + "c9150fb26ee656d7f5241be22e07de0f09cb1dc028494746f8e7ec3a4a19997b", ]), }); diff --git a/src/lib/onboard/dockerfile-remote-dashboard-bind-contract.ts b/src/lib/onboard/dockerfile-remote-dashboard-bind-contract.ts index 19fc8daecb5..135da7bfc4d 100644 --- a/src/lib/onboard/dockerfile-remote-dashboard-bind-contract.ts +++ b/src/lib/onboard/dockerfile-remote-dashboard-bind-contract.ts @@ -71,6 +71,7 @@ const CANONICAL_POST_GENERATOR_INSTRUCTION_SHA256 = new Set([ "c682148fc7efec9f947c326c6029181cd879b7cba3e8361246aba7d0e6fe70a3", "2801e488822e10a39a5586bd150279e54df4612e30c2fa782453534a466def59", "8f0861e48c0cec37faa662fccd130ab21f972ac3ed2a0ce5f4e5a1e9ec223130", + "6364b77bae0a2a4449737beefac36c439333a5e37993ac404c02e375aa170515", // Reviewed late messaging inputs, metadata setup, and runtime assertions. "7e5f7e1dfb90e5e4b863afdfb9ba58e57e3693bdc6f47ac8c13e80bdc9eff56b", "8f5966da093ef75cefd35c2b7f1361fbf5b32e63a4a8a34cb3ac7f76a1330e5e", diff --git a/src/lib/sandbox-base-image/security-inventory.ts b/src/lib/sandbox-base-image/security-inventory.ts index 5cbc8b4ea4a..b354595f62e 100644 --- a/src/lib/sandbox-base-image/security-inventory.ts +++ b/src/lib/sandbox-base-image/security-inventory.ts @@ -14,7 +14,7 @@ export const SANDBOX_BASE_SECURITY_PACKAGE_INVENTORY = [ "vim-tiny=2:9.2.0858-1", "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2", "libssl3t64=3.5.7-1~deb13u2", - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1", + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1", "perl-base=5.44.0-1nemoclaw1", "perl=5.44.0-1nemoclaw1", ] as const; diff --git a/test/helpers/base-apt-security-functions.ts b/test/helpers/base-apt-security-functions.ts index c4eaa3cab00..df75409afd5 100644 --- a/test/helpers/base-apt-security-functions.ts +++ b/test/helpers/base-apt-security-functions.ts @@ -65,7 +65,7 @@ export function baseAptSecurityFunctions(architecture: DebianArchitecture): stri ' vim-common|vim-tiny) printf "2:9.2.0858-1" ;;', ' libssh2-1t64) printf "1.11.1-1+deb13u1+nemoclaw2" ;;', ' libssl3t64) printf "3.5.7-1~deb13u2" ;;', - ' nemoclaw-python3.13-htmlparser-fix) printf "3.13.5-2+deb13u4+nemoclaw1" ;;', + ' nemoclaw-python3.13-htmlparser-fix) printf "3.13.5-2+deb13u5+nemoclaw1" ;;', " *) return 64 ;;", " esac", "}", diff --git a/test/helpers/onboard-script-mocks.cjs b/test/helpers/onboard-script-mocks.cjs index d2089a33bc4..63067b30485 100644 --- a/test/helpers/onboard-script-mocks.cjs +++ b/test/helpers/onboard-script-mocks.cjs @@ -396,7 +396,7 @@ const OPENCLAW_SECURITY_INVENTORY_PROBE = [ 'test -f "$security_inventory"', 'test ! -L "$security_inventory"', `test "$(stat -c '%u:%g:%a' "$security_inventory")" = "0:0:444"`, - `printf '%s\\n' "architecture=$arch" "libexpat1=2.8.3-1" "libonig5=6.9.9-1+b1" "libjq1=1.8.2-1" "jq=1.8.2-1" "vim-common=2:9.2.0858-1" "vim-tiny=2:9.2.0858-1" "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" "libssl3t64=3.5.7-1~deb13u2" "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" "perl-base=5.44.0-1nemoclaw1" "perl=5.44.0-1nemoclaw1" "libevent-core-2.1-7t64=2.1.13-stable-1" | cmp -s - "$security_inventory"`, + `printf '%s\\n' "architecture=$arch" "libexpat1=2.8.3-1" "libonig5=6.9.9-1+b1" "libjq1=1.8.2-1" "jq=1.8.2-1" "vim-common=2:9.2.0858-1" "vim-tiny=2:9.2.0858-1" "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2" "libssl3t64=3.5.7-1~deb13u2" "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" "perl-base=5.44.0-1nemoclaw1" "perl=5.44.0-1nemoclaw1" "libevent-core-2.1-7t64=2.1.13-stable-1" | cmp -s - "$security_inventory"`, `printf '%s\\n' "nemoclaw-security-inventory-ok"`, ].join("; "); diff --git a/test/inference/managed/managed-image-staging-qa-workflow.test.ts b/test/inference/managed/managed-image-staging-qa-workflow.test.ts index 2f39ee81296..d804368cdfb 100644 --- a/test/inference/managed/managed-image-staging-qa-workflow.test.ts +++ b/test/inference/managed/managed-image-staging-qa-workflow.test.ts @@ -1,6 +1,11 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + import { describe, expect, it } from "vitest"; import { readWorkflow, required, step } from "../../helpers/managed-image-publication-workflow"; @@ -29,6 +34,12 @@ function stagingQaDeepCodeBuilder(workflow: Workflow): Job { ); } +function writeOverlayFixture(root: string, relative: string, content: string) { + const destination = path.join(root, relative); + fs.mkdirSync(path.dirname(destination), { recursive: true }); + fs.writeFileSync(destination, content); +} + describe("managed-image staging QA workflow", () => { it("rebuilds the staging QA base from exact source before validating the Deep Agents Code repair (#8665)", () => { const workflow = readWorkflow("managed-images.yaml"); @@ -92,6 +103,44 @@ describe("managed-image staging QA workflow", () => { "scripts/security/patches/perl-5.44.0-net-ping-capability-tests.patch", ); + const overlayFixture = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-staging-overlay-")); + const candidateRoot = path.join(overlayFixture, "candidate"); + const stagingRoot = path.join(overlayFixture, "staging-qa-base-source"); + const dockerfile = "agents/langchain-deepagents-code/Dockerfile.base"; + const packageBuilder = "scripts/security/build-native-security-packages.sh"; + const requirements = "agents/langchain-deepagents-code/requirements.lock"; + const npmBundler = "scripts/lib/bundled-npm-package.mts"; + const perlPatch = "scripts/security/patches/perl-5.44.0-net-ping-capability-tests.patch"; + try { + writeOverlayFixture(candidateRoot, dockerfile, "candidate Dockerfile\n"); + writeOverlayFixture(candidateRoot, packageBuilder, "exit 42\n"); + writeOverlayFixture(candidateRoot, requirements, "candidate requirements\n"); + writeOverlayFixture(candidateRoot, npmBundler, "candidate npm bundler\n"); + writeOverlayFixture(candidateRoot, perlPatch, "candidate Perl patch\n"); + writeOverlayFixture(stagingRoot, dockerfile, "staging Dockerfile\n"); + writeOverlayFixture(stagingRoot, packageBuilder, "exit 41\n"); + writeOverlayFixture(stagingRoot, requirements, "staging requirements\n"); + writeOverlayFixture(stagingRoot, npmBundler, "staging npm bundler\n"); + writeOverlayFixture(stagingRoot, perlPatch, "staging Perl patch\n"); + const summary = path.join(overlayFixture, "summary.md"); + const result = spawnSync("bash", ["-c", overlaySource], { + encoding: "utf-8", + env: { + ...process.env, + CANDIDATE_SHA: "candidate-sha", + GITHUB_STEP_SUMMARY: summary, + GITHUB_WORKSPACE: overlayFixture, + }, + }); + expect({ status: result.status, stderr: result.stderr }).toEqual({ + status: 0, + stderr: "", + }); + expect(spawnSync("bash", [path.join(stagingRoot, packageBuilder)]).status).toBe(42); + } finally { + fs.rmSync(overlayFixture, { recursive: true, force: true }); + } + const baseSource = required(baseBuild.run, "staging QA base build is missing"); expect(baseBuild.env?.DOCKER_BUILDKIT).toBe("1"); expect(baseSource).toContain( diff --git a/test/install/native-security-packages.test.ts b/test/install/native-security-packages.test.ts index ce282b0a310..a33285e179f 100644 --- a/test/install/native-security-packages.test.ts +++ b/test/install/native-security-packages.test.ts @@ -101,6 +101,55 @@ function runLibssh2Harness(nestedFailure = false) { return { fixture, harnessLog, result }; } +function runPythonFixPackageHarness(architecture: "amd64" | "arm64", sourceHashFailure = false) { + const fixture = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-python-fix-harness-")); + const outputDir = path.join(fixture, "out"); + const harnessLog = path.join(fixture, "harness-log"); + fs.mkdirSync(outputDir); + fs.mkdirSync(harnessLog); + + const result = spawnSync( + "bash", + [ + "-c", + [ + "set -euo pipefail", + 'harness_output="$1"', + 'architecture="$2"', + 'set -- "$harness_output"', + "source scripts/security/build-native-security-packages.sh", + 'download() { printf "download %s\\n" "$1" >>"$HARNESS_LOG/calls"; : >"$2"; }', + 'verify_sha256() { printf "verify %s %s\\n" "$1" "${2##*/}" >>"$HARNESS_LOG/calls"; if [[ "${FAIL_SOURCE_HASH:-0}" == "1" && "$2" == *python-stdlib-original.deb ]]; then return 1; fi; }', + "dpkg-deb() {", + ' case "$1" in', + ' -x) mkdir -p "$3/usr/lib/python3.13/html"; printf "original parser\\n" >"$3/usr/lib/python3.13/html/parser.py" ;;', + ' -f) if [[ "$3" == "Package" ]]; then printf "libpython3.13-stdlib\\n"; elif [[ "$2" == "$output_dir/nemoclaw-python3.13-htmlparser-fix.deb" ]]; then printf "%s\\n" "$PYTHON_FIX_VERSION"; else printf "%s\\n" "$PYTHON_DEBIAN_VERSION"; fi ;;', + ' --build) : >"$4"; printf "build %s\\n" "${4##*/}" >>"$HARNESS_LOG/calls" ;;', + " *) return 64 ;;", + " esac", + "}", + 'git() { printf "git %s\\n" "$*" >>"$HARNESS_LOG/calls"; }', + "refresh_md5sums() { :; }", + 'build_python_fix_package "$architecture"', + 'cp "$build_root/python-htmlparser-fix/DEBIAN/control" "$HARNESS_LOG/control"', + ].join("\n"), + "python-fix-harness", + outputDir, + architecture, + ], + { + cwd: ROOT, + encoding: "utf-8", + env: { + ...process.env, + FAIL_SOURCE_HASH: sourceHashFailure ? "1" : "0", + HARNESS_LOG: harnessLog, + }, + }, + ); + return { fixture, harnessLog, outputDir, result }; +} + describe("native security package remediation", () => { it("keeps the package builder syntactically valid", () => { const result = spawnSync("bash", ["-n", BUILD_SCRIPT], { encoding: "utf-8" }); @@ -144,6 +193,54 @@ describe("native security package remediation", () => { } }); + it.each([ + ["amd64", "db161322a3481d2c0c3b9a3b9a03c3ab0e2b1718f54b88755fb4a3f939165b84"], + ["arm64", "d1178d24e4d143cc6c577d9dc0f26dd982efccc2e600d25ce86361f168a22be0"], + ] as const)( + "builds the Python fix from the reviewed %s snapshot artifact", + (architecture, hash) => { + const { fixture, harnessLog, outputDir, result } = runPythonFixPackageHarness(architecture); + try { + expect({ status: result.status, stderr: result.stderr }).toEqual({ + status: 0, + stderr: "", + }); + const calls = fs.readFileSync(path.join(harnessLog, "calls"), "utf-8"); + expect(calls).toContain( + `download https://snapshot.debian.org/archive/debian/20260906T023042Z/pool/main/p/python3.13/libpython3.13-stdlib_3.13.5-2+deb13u5_${architecture}.deb`, + ); + expect(calls).toContain(`verify ${hash} python-stdlib-original.deb`); + expect(calls).toContain( + "verify f91ec3de6331206bbe2ec3e54a05f646bd23d3c61a18d4a01b25164e070bacc9 parser.py", + ); + expect(calls).toContain( + "verify 4ff43a8578bda2f14686c67911b64c18e869841973722b1c623b5727491bdaf7 parser.py", + ); + expect(calls).toContain("build nemoclaw-python3.13-htmlparser-fix.deb"); + expect(fs.readFileSync(path.join(harnessLog, "control"), "utf-8")).toContain( + "Depends: libpython3.13-stdlib (= 3.13.5-2+deb13u5)", + ); + expect(fs.existsSync(path.join(outputDir, "nemoclaw-python3.13-htmlparser-fix.deb"))).toBe( + true, + ); + } finally { + fs.rmSync(fixture, { recursive: true, force: true }); + } + }, + ); + + it("stops before producing a Python fix package when the source hash is invalid", () => { + const { fixture, outputDir, result } = runPythonFixPackageHarness("amd64", true); + try { + expect(result.status).toBe(1); + expect(fs.existsSync(path.join(outputDir, "nemoclaw-python3.13-htmlparser-fix.deb"))).toBe( + false, + ); + } finally { + fs.rmSync(fixture, { recursive: true, force: true }); + } + }); + it("records every reviewed upstream fix at the patch boundary", () => { const libssh2Patch = fs.readFileSync(LIBSSH2_PATCH, "utf-8"); expect( @@ -205,7 +302,7 @@ describe("native security package remediation", () => { ); expect(content).toContain("libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2"); expect(content).toContain("libssl3t64=3.5.7-1~deb13u2"); - expect(content).toContain("nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1"); + expect(content).toContain("nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1"); expect(content).toContain("4ff43a8578bda2f14686c67911b64c18e869841973722b1c623b5727491bdaf7"); expect(content).toContain("[p.feed('') for _ in range(20000)]"); expect(content).toContain("or sys.exit('empty feeds accumulated pending entries')"); diff --git a/test/platform/images/node-tar-dockerfile-contract.test.ts b/test/platform/images/node-tar-dockerfile-contract.test.ts index baa6d7c350e..262e5c2be18 100644 --- a/test/platform/images/node-tar-dockerfile-contract.test.ts +++ b/test/platform/images/node-tar-dockerfile-contract.test.ts @@ -368,7 +368,7 @@ describe("node-tar image remediation contract", () => { "RUN apt-get update && apt-get install -y --no-install-recommends", patchInputReady, ); - const curlPackage = source.indexOf("curl=8.14.1-2+deb13u4", aptInstall); + const curlPackage = source.indexOf("curl=8.14.1-2+deb13u5", aptInstall); const aptInstallCleanup = source.indexOf("&& rm -rf /var/lib/apt/lists/*", curlPackage); expect( aptInstall > patchCopy && diff --git a/test/runtime/sandbox/sandbox-base-runtime-tools.test.ts b/test/runtime/sandbox/sandbox-base-runtime-tools.test.ts index 926485c2c48..ad6f66452d3 100644 --- a/test/runtime/sandbox/sandbox-base-runtime-tools.test.ts +++ b/test/runtime/sandbox/sandbox-base-runtime-tools.test.ts @@ -167,7 +167,7 @@ describe("sandbox base runtime tools", () => { expect({ status: result.status, stderr: result.stderr }).toEqual({ status: 0, stderr: "" }); expect(calls).toContain("procps=2:4.0.4-9"); expect(calls).toContain("util-linux=2.41-5"); - expect(calls).toContain("e2fsprogs=1.47.2-3+b11"); + expect(calls).toContain("e2fsprogs=1.47.2-3+b12"); expect(calls).toContain("openssh-sftp-server=1:10.0p1-7+deb13u4"); }); diff --git a/test/runtime/sandbox/sandbox-base-security-packages.test.ts b/test/runtime/sandbox/sandbox-base-security-packages.test.ts index 746d727973c..79948325bfd 100644 --- a/test/runtime/sandbox/sandbox-base-security-packages.test.ts +++ b/test/runtime/sandbox/sandbox-base-security-packages.test.ts @@ -69,7 +69,7 @@ const EXPECTED_SECURITY_PACKAGE_INVENTORY = [ "vim-tiny=2:9.2.0858-1", "libssh2-1t64=1.11.1-1+deb13u1+nemoclaw2", "libssl3t64=3.5.7-1~deb13u2", - "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1", + "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1", "perl-base=5.44.0-1nemoclaw1", "perl=5.44.0-1nemoclaw1", ] as const; @@ -77,6 +77,12 @@ const EXPECTED_OPENCLAW_SECURITY_PACKAGE_INVENTORY = [ ...EXPECTED_SECURITY_PACKAGE_INVENTORY, "libevent-core-2.1-7t64=2.1.13-stable-1", ] as const; +const PUBLISHED_HERMES_SECURITY_PACKAGE_INVENTORY = EXPECTED_SECURITY_PACKAGE_INVENTORY.map( + (entry) => + entry === "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u5+nemoclaw1" + ? "nemoclaw-python3.13-htmlparser-fix=3.13.5-2+deb13u4+nemoclaw1" + : entry, +); const SECURITY_CASES = SECURITY_IMAGES.flatMap((image) => ARCHITECTURES.map((architecture) => [image.name, architecture, image] as const), ); @@ -159,14 +165,27 @@ function baseAptSecurityFunctionsWithLibeventVersion( ); } +function baseAptSecurityFunctionsWithPythonFixVersion( + architecture: (typeof ARCHITECTURES)[number], + version: string, +): string[] { + return baseAptSecurityFunctions(architecture).map((definition) => + definition.replace( + 'nemoclaw-python3.13-htmlparser-fix) printf "3.13.5-2+deb13u5+nemoclaw1"', + `nemoclaw-python3.13-htmlparser-fix) printf "${version}"`, + ), + ); +} + function completedImageSecurityCommand( image: (typeof SECURITY_IMAGES)[number], tmp: string, architecture: (typeof ARCHITECTURES)[number], + packageInventory: readonly string[] = image.expectedInventory, ): { command: string; inventory: string; pythonShim: string } { const inventory = path.join(tmp, "security-packages.txt"); const { fixedParser, pythonShim } = stageFixedParser(tmp); - fs.writeFileSync(inventory, securityInventory(architecture, image.expectedInventory), { + fs.writeFileSync(inventory, securityInventory(architecture, packageInventory), { mode: 0o444, }); const dockerfile = fs.readFileSync(image.finalDockerfile, "utf-8"); @@ -313,6 +332,51 @@ describe("sandbox base security packages", () => { }, ); + it.each(ARCHITECTURES)( + "accepts the exact published Hermes Python package on %s", + (architecture) => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-hermes-published-security-")); + const hermes = SECURITY_IMAGES.find((image) => image.name === "Hermes")!; + const prepared = completedImageSecurityCommand( + hermes, + tmp, + architecture, + PUBLISHED_HERMES_SECURITY_PACKAGE_INVENTORY, + ); + + try { + const { result } = runLoggedDockerShell( + prepared.command, + tmp, + [ + "perl_base_installed=1", + "perl_installed=1", + [ + "stat() {", + ` [[ "$#" -eq 3 && "$1" == "-c" && "$2" == "%u:%g:%a" && "$3" == ${JSON.stringify(prepared.inventory)} ]] || return 64`, + ' printf "0:0:444\\n"', + "}", + ].join("\n"), + ...useRealPatchedParser( + baseAptSecurityFunctionsWithPythonFixVersion( + architecture, + "3.13.5-2+deb13u4+nemoclaw1", + ), + prepared.pythonShim, + ), + ], + { timeoutMs: 15_000 }, + ); + expect({ status: result.status, stderr: result.stderr }).toEqual({ + status: 0, + stderr: "", + }); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }, + ); + it.each(SECURITY_CASES)( "rejects a stale Vim patch range in the base image for %s on %s", (_name, architecture, image) => { diff --git a/test/runtime/sandbox/sandbox-provisioning.test.ts b/test/runtime/sandbox/sandbox-provisioning.test.ts index aeb446503b8..14e3c9a5dda 100644 --- a/test/runtime/sandbox/sandbox-provisioning.test.ts +++ b/test/runtime/sandbox/sandbox-provisioning.test.ts @@ -1007,7 +1007,7 @@ describe("sandbox provisioning: base runtime tools", () => { `chattr_marker=${JSON.stringify(chattrMarker)}`, `tmux_marker=${JSON.stringify(tmuxMarker)}`, 'apt-mark() { printf "apt-mark %s\\n" "$*" >> "$call_log"; }', - 'apt-get() { printf "apt-get %s\\n" "$*" >> "$call_log"; if [[ "$*" == *"install"* && "$*" == *"procps=2:4.0.4-9"* ]]; then touch "$ps_marker"; fi; if [[ "$*" == *"install"* && "$*" == *"e2fsprogs=1.47.2-3+b11"* ]]; then touch "$chattr_marker"; fi; if [[ "$*" == *"install"* && "$*" == *"tmux=3.5a-3"* ]]; then touch "$tmux_marker"; fi; }', + 'apt-get() { printf "apt-get %s\\n" "$*" >> "$call_log"; if [[ "$*" == *"install"* && "$*" == *"procps=2:4.0.4-9"* ]]; then touch "$ps_marker"; fi; if [[ "$*" == *"install"* && "$*" == *"e2fsprogs=1.47.2-3+b12"* ]]; then touch "$chattr_marker"; fi; if [[ "$*" == *"install"* && "$*" == *"tmux=3.5a-3"* ]]; then touch "$tmux_marker"; fi; }', 'command() { if [ "${1:-}" = "-v" ] && [ "${2:-}" = "ps" ]; then [ -f "$ps_marker" ]; elif [ "${1:-}" = "-v" ] && [ "${2:-}" = "chattr" ]; then [ -f "$chattr_marker" ]; elif [ "${1:-}" = "-v" ] && [ "${2:-}" = "tmux" ]; then [ -f "$tmux_marker" ]; else builtin command "$@"; fi; }', 'ps() { [ -f "$ps_marker" ] || return 127; printf "procps test version\\n"; }', command, @@ -1022,7 +1022,7 @@ describe("sandbox provisioning: base runtime tools", () => { expect(calls).toContain("apt-get autoremove --purge -y"); expect(calls).toContain("apt-get update"); expect(calls).toContain("apt-get install -y --no-install-recommends procps=2:4.0.4-9"); - expect(calls).toContain("apt-get install -y --no-install-recommends e2fsprogs=1.47.2-3+b11"); + expect(calls).toContain("apt-get install -y --no-install-recommends e2fsprogs=1.47.2-3+b12"); expect(result.stdout).toContain("procps test version"); } finally { fs.rmSync(tmp, { recursive: true, force: true });