From e8e6968c4decc71c04fd3f1646c12ca66fbebce2 Mon Sep 17 00:00:00 2001 From: T Savo Date: Wed, 8 Apr 2026 16:20:42 -0700 Subject: [PATCH 1/2] fix(image): install gnupg in sandbox base image so `gpg` is available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sandbox base image (`ghcr.io/nvidia/nemoclaw/sandbox-base`) does not include the gnupg package, even though `nemoclaw-start.sh` already exports `GNUPGHOME=/tmp/.gnupg` and `test/service-env.test.js` asserts the redirect is in place. As a result `gpg --list-keys` (or any other gpg invocation) inside the sandbox fails with `bash: gpg: command not found`, breaking workflows that expect signing/verification to be available — including the smoke check QA reported on DGX Spark (aarch64). The GNUPGHOME redirect was introduced in #1121 ("restrict /sandbox to read-only via Landlock") to keep gpg writable when `~/.gnupg` became unwritable, but the matching `apt-get install gnupg` line was never added to `Dockerfile.base`. The service-env tests assert the env var setup but don't actually invoke gpg, so CI never noticed the binary was missing. This adds `gnupg=2.2.40-1.1+deb12u2` (the bookworm-pinned version, matching the existing `=` pinning style for every other package in the same `apt-get install` block) right after `git`. No other changes — same `--no-install-recommends`, same cleanup tail. The package brings in dirmngr, gpg-wks-server, and gpg-wks-client as dependencies (per a clean install probe in the exact base image SHA). Total layer cost ~3 MB compressed. Smoke tested locally by building Dockerfile.base with the fix and running the exact failing command from the bug report: $ docker build -f Dockerfile.base -t nemoclaw-base-test:gnupg . $ docker run --rm nemoclaw-base-test:gnupg gpg --version gpg (GnuPG) 2.2.40 $ docker run --rm nemoclaw-base-test:gnupg gpg --list-keys gpg: directory '/root/.gnupg' created gpg: keybox '/root/.gnupg/pubring.kbx' created gpg: /root/.gnupg/trustdb.gpg: trustdb created (exit 0) $ docker run --rm -e GNUPGHOME=/tmp/.gnupg nemoclaw-base-test:gnupg \ sh -c 'mkdir -p /tmp/.gnupg && chmod 700 /tmp/.gnupg && gpg --list-keys' gpg: keybox '/tmp/.gnupg/pubring.kbx' created (exit 0) Both the default `~/.gnupg` and the runtime-redirected `/tmp/.gnupg` (matching what `nemoclaw-start.sh` exports) work as expected. Closes #1640. Signed-off-by: T Savo --- Dockerfile.base | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.base b/Dockerfile.base index ce8ba4efd5e..17ef7e237c4 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -58,6 +58,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3-venv=3.11.2-1+b1 \ curl=7.88.1-10+deb12u14 \ git=1:2.39.5-0+deb12u3 \ + gnupg=2.2.40-1.1+deb12u2 \ ca-certificates=20230311+deb12u1 \ iproute2=6.1.0-3 \ iptables=1.8.9-2 \ From 575341fa4e65d05be93cd2a46766552da072213b Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 9 Apr 2026 16:03:40 -0700 Subject: [PATCH 2/2] fix(security): create GNUPGHOME with mode 700 --- scripts/nemoclaw-start.sh | 6 ++++-- test/service-env.test.ts | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index 9155cdcc36c..777cba682f2 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -71,12 +71,14 @@ done if [ "$(id -u)" -eq 0 ]; then install -d -o sandbox -g sandbox -m 755 \ /tmp/.npm-cache /tmp/.cache /tmp/.config /tmp/.local/share \ - /tmp/.local/state /tmp/.runtime /tmp/.gnupg /tmp/.claude \ + /tmp/.local/state /tmp/.runtime /tmp/.claude \ /tmp/npm-global + install -d -o sandbox -g sandbox -m 700 /tmp/.gnupg else mkdir -p /tmp/.npm-cache /tmp/.cache /tmp/.config /tmp/.local/share \ - /tmp/.local/state /tmp/.runtime /tmp/.gnupg /tmp/.claude \ + /tmp/.local/state /tmp/.runtime /tmp/.claude \ /tmp/npm-global + install -d -m 700 /tmp/.gnupg fi # ── Drop unnecessary Linux capabilities ────────────────────────── diff --git a/test/service-env.test.ts b/test/service-env.test.ts index d58c4d1eab5..8b19f875f23 100644 --- a/test/service-env.test.ts +++ b/test/service-env.test.ts @@ -189,9 +189,15 @@ describe("service environment", () => { expect(src).toContain("/tmp/.config"); expect(src).toContain("/tmp/.cache"); expect(src).toContain("/tmp/.local/share"); - expect(src).toContain("/tmp/.gnupg"); expect(src).toContain("/tmp/npm-global"); }); + + it("entrypoint creates GNUPGHOME with restrictive permissions", () => { + const scriptPath = join(import.meta.dirname, "../scripts/nemoclaw-start.sh"); + const src = readFileSync(scriptPath, "utf-8"); + expect(src).toContain("install -d -o sandbox -g sandbox -m 700 /tmp/.gnupg"); + expect(src).toContain("install -d -m 700 /tmp/.gnupg"); + }); }); describe("proxy environment variables (issue #626)", () => {