fix(image): install gnupg in sandbox base image so gpg is available#1649
fix(image): install gnupg in sandbox base image so gpg is available#1649cv merged 5 commits intoNVIDIA:mainfrom
Conversation
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 NVIDIA#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 `=<version>` 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 NVIDIA#1640. Signed-off-by: T Savo <evilgenius@nefariousplan.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughInstalls a pinned Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR restores gpg availability in the NemoClaw sandbox base image (ghcr.io/nvidia/nemoclaw/sandbox-base) by adding the missing Debian gnupg package to the pinned apt-get install list in Dockerfile.base, addressing runtime failures like gpg: command not found (Issue #1640).
Changes:
- Add
gnupg=2.2.40-1.1+deb12u2to the existing pinnedapt-get installblock inDockerfile.base.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks for the thorough investigation and clean fix, @TSavo — the root cause trace through #1121 was especially helpful. Two things we'd like to see before merging:
Thanks again! |
|
I pushed a narrow follow-up onto this branch to address the remaining runtime hardening concern:
Local validation run: CI is rerunning now. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/service-env.test.ts (1)
195-200: Prefer behavior-based assertion over exact source-string matching.This test is useful, but it can still pass/fail on formatting-only edits and doesn’t validate effective directory mode behavior. Consider asserting the resulting mode/ownership from a small executed wrapper around the extracted setup block instead of
toContain(...)literals.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/service-env.test.ts` around lines 195 - 200, The test `entrypoint creates GNUPGHOME with restrictive permissions` relies on string matching and should instead execute the setup logic and assert actual filesystem effects: extract the setup block from the script (using the existing scriptPath/readFileSync in test/service-env.test.ts), run that snippet in a controlled temp directory as a small child process or by invoking the same shell commands, then use fs.stat (or fs.promises.stat) to assert the created directory mode is 0o700 and ownership matches the expected uid/gid (or at least not world-writable); replace the toContain(...) assertions with these behavior-based checks to avoid brittle formatting-dependent tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@test/service-env.test.ts`:
- Around line 195-200: The test `entrypoint creates GNUPGHOME with restrictive
permissions` relies on string matching and should instead execute the setup
logic and assert actual filesystem effects: extract the setup block from the
script (using the existing scriptPath/readFileSync in test/service-env.test.ts),
run that snippet in a controlled temp directory as a small child process or by
invoking the same shell commands, then use fs.stat (or fs.promises.stat) to
assert the created directory mode is 0o700 and ownership matches the expected
uid/gid (or at least not world-writable); replace the toContain(...) assertions
with these behavior-based checks to avoid brittle formatting-dependent tests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3e3ed07c-5072-433a-9cbc-8785f2cb1dba
📒 Files selected for processing (2)
scripts/nemoclaw-start.shtest/service-env.test.ts
|
Follow-up check after the salvage fix:
Ready for merge once a maintainer picks it up. |
…VIDIA#1649) <!-- markdownlint-disable MD041 --> ## Summary The sandbox base image (`ghcr.io/nvidia/nemoclaw/sandbox-base`) is missing the `gnupg` package — `gpg --list-keys` (and any other gpg invocation) fails with `bash: gpg: command not found` inside the sandbox. This adds a single pinned `gnupg=2.2.40-1.1+deb12u2` line to the existing `apt-get install` block in `Dockerfile.base`, restoring the binary that the rest of the codebase already assumes is present. ## Related Issue Closes NVIDIA#1640. ## Changes `Dockerfile.base`: add `gnupg=2.2.40-1.1+deb12u2` to the existing `apt-get install` block, slotted right after `git`. Same `--no-install-recommends`, same cleanup tail, same `=<version>` pinning style as every other package in the block. ```diff curl=7.88.1-10+deb12u14 \ git=1:2.39.5-0+deb12u3 \ + gnupg=2.2.40-1.1+deb12u2 \ ca-certificates=20230311+deb12u1 \ ``` The pinned version is the bookworm-stable `2.2.40-1.1+deb12u2`, verified by `apt-cache madison gnupg` against the exact base image SHA `node:22-slim@sha256:4f77a690...`. The package brings in `dirmngr`, `gpg-wks-server`, and `gpg-wks-client` as dependencies. Total layer cost ~3 MB compressed. Diff: **+1 / 0** in 1 file. ### Why this is the right fix (and not "lower the env var" or "remove the test") The fix isn't obvious unless you trace where `GNUPGHOME` came from. Walking that chain: 1. **PR NVIDIA#1121** (`fix(sandbox): restrict /sandbox to read-only via Landlock (NVIDIA#804)`, authored by @prekshivyas, merged 2026-04-08) made the `/sandbox` home directory Landlock-read-only to prevent agents from modifying their own runtime environment. 2. To keep tools that normally write under `~/...` working (gpg, git config, python history, npm prefix, etc.), that PR redirected each tool's homedir to a writable `/tmp/...` path via env vars in `scripts/nemoclaw-start.sh`. The relevant line is at `scripts/nemoclaw-start.sh:53`: ```sh 'GNUPGHOME=/tmp/.gnupg' ``` alongside `HISTFILE=/tmp/.bash_history`, `GIT_CONFIG_GLOBAL=/tmp/.gitconfig`, `PYTHONUSERBASE=/tmp/.local`, etc. 3. PR NVIDIA#1121 also added three matching assertions in `test/service-env.test.js` (lines 177, 191, 347) verifying that the redirect is set: ```js expect(src).toContain("GNUPGHOME=/tmp/.gnupg"); ``` 4. **What PR NVIDIA#1121 didn't do**: add `gnupg` to the `apt-get install` list in `Dockerfile.base`. The env var setup landed and the test assertions landed, but the install line was missed. 5. CI never noticed because `service-env.test.js` only asserts that the env var is *set* in the source — it never spawns a subprocess that actually runs `gpg`. So a working test suite + a missing binary coexist silently. The QA report (this issue, NVIDIA#1640) catches it as a runtime failure on DGX Spark aarch64 because their test step does invoke `gpg --list-keys`. The clear intent of NVIDIA#1121 was to **enable** gpg under a redirected `GNUPGHOME` — you wouldn't redirect the homedir if you wanted gpg blocked. This PR is the matching install line that NVIDIA#1121 should have included, closing a one-line oversight rather than adding new capability or rolling anything back. ### Why not just remove the GNUPGHOME redirect The env var redirect from NVIDIA#1121 is doing real work — without it, any future `apt-get install gnupg` would still leave gpg unable to write to its homedir under Landlock-read-only `/sandbox`. The redirect is the "right" half of the pair; the install is the missing left half. ### Why this isn't a security regression The sandbox runs LLM-driven agents and gpg is a credential-handling tool, so it's worth justifying explicitly: - The redirected `GNUPGHOME=/tmp/.gnupg` is **fresh and empty** per session — no preloaded keys. - Without keys, gpg can hash/check signatures of public material but cannot decrypt or sign anything. - An agent would have to first import a key (which requires the user to provide it — keys are not pulled from anywhere automatically) before gpg becomes capable of any sensitive operation. - This is the same threat model as `git` and `curl`, which are already in the image and could equally be used to fetch arbitrary content. gpg adds no new capability that the existing toolchain doesn't already have. If the project explicitly *did* want gpg unavailable to agents, the right fix would be to remove the GNUPGHOME redirect from NVIDIA#1121 *and* the matching test assertions, not to keep the env wiring while leaving the binary missing — that's just confusing. ## Type of Change - [x] Code change for a new feature, bug fix, or refactor. - [ ] Code change with doc updates. - [ ] Doc only. Prose changes without code sample modifications. - [ ] Doc only. Includes code sample changes. ## Testing Smoke-tested locally by building `Dockerfile.base` with the fix and running the exact failing command from the bug report: ```sh $ docker build -f Dockerfile.base -t nemoclaw-base-test:gnupg . [...] => exporting to image 46.7s done $ docker run --rm nemoclaw-base-test:gnupg gpg --version gpg (GnuPG) 2.2.40 libgcrypt 1.10.1 $ 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) # And with the runtime-redirected GNUPGHOME from nemoclaw-start.sh: $ 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. The exact `gpg --list-keys` failure from the bug report no longer reproduces. - [x] `hadolint Dockerfile.base` — clean (no warnings) - [x] `docker build -f Dockerfile.base` — succeeds, exports to image cleanly - [x] `gpg --version` in built image — works (`gpg (GnuPG) 2.2.40`) - [x] `gpg --list-keys` in built image — works (was `bash: gpg: command not found` before this PR) - [x] `gpg --list-keys` with `GNUPGHOME=/tmp/.gnupg` — works (matches the runtime env from `nemoclaw-start.sh`) - [ ] `npx prek run --all-files` — partial: ran the affected hooks (commitlint, gitleaks, hadolint) which all pass; did NOT run `test-cli` against the full local suite because two pre-existing baseline failures on stock `main` get in the way on a WSL2 dev host (the `shouldPatchCoredns` issue addressed by PR NVIDIA#1626 (merged) and the install-preflight PATH leakage addressed by PR NVIDIA#1628 (open)). Upstream CI runs on Linux GHA runners and doesn't hit either of those, so it'll exercise the full suite normally. - [ ] `npm test` — same caveat as above, ran the relevant projects in isolation - [ ] `make docs` builds without warnings. (for doc-only changes — N/A) ## Checklist ### General - [x] I have read and followed the [contributing guide](https://github.com/NVIDIA/NemoClaw/blob/main/CONTRIBUTING.md). - [ ] I have read and followed the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md). (for doc-only changes — N/A) ### Code Changes - [x] Formatters applied — `hadolint Dockerfile.base` clean. No JS/TS/Python files touched. - [x] Tests added or updated for new or changed behavior — N/A. The existing `service-env.test.js` already asserts the `GNUPGHOME` redirect introduced in NVIDIA#1121; this PR makes the corresponding binary available so those assertions reflect a runtime that actually works. A new test that spawns `gpg` directly inside a container would arguably be worth a follow-up (it would have caught this gap originally), but it's a separate concern from this one-line install fix. - [x] No secrets, API keys, or credentials committed. - [ ] Doc pages updated for any user-facing behavior changes — N/A. The bug report describes the expected behavior; this PR just makes runtime match it. No docs claim gpg is unavailable. ### Doc Changes - N/A (no doc changes) --- Signed-off-by: T Savo <evilgenius@nefariousplan.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Base system image now includes GnuPG as a pinned OS package. * **Bug Fixes / Security** * GnuPG runtime directory is now created in a separate step with stricter permissions and sandbox ownership when applicable, reducing exposure. * **Tests** * Test suite updated to verify the new directory creation and permission/ownership behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: T Savo <evilgenius@nefariousplan.com> Co-authored-by: Carlos Villela <cvillela@nvidia.com> Co-authored-by: Prekshi Vyas <34834085+prekshivyas@users.noreply.github.com>
…VIDIA#1649) <!-- markdownlint-disable MD041 --> ## Summary The sandbox base image (`ghcr.io/nvidia/nemoclaw/sandbox-base`) is missing the `gnupg` package — `gpg --list-keys` (and any other gpg invocation) fails with `bash: gpg: command not found` inside the sandbox. This adds a single pinned `gnupg=2.2.40-1.1+deb12u2` line to the existing `apt-get install` block in `Dockerfile.base`, restoring the binary that the rest of the codebase already assumes is present. ## Related Issue Closes NVIDIA#1640. ## Changes `Dockerfile.base`: add `gnupg=2.2.40-1.1+deb12u2` to the existing `apt-get install` block, slotted right after `git`. Same `--no-install-recommends`, same cleanup tail, same `=<version>` pinning style as every other package in the block. ```diff curl=7.88.1-10+deb12u14 \ git=1:2.39.5-0+deb12u3 \ + gnupg=2.2.40-1.1+deb12u2 \ ca-certificates=20230311+deb12u1 \ ``` The pinned version is the bookworm-stable `2.2.40-1.1+deb12u2`, verified by `apt-cache madison gnupg` against the exact base image SHA `node:22-slim@sha256:4f77a690...`. The package brings in `dirmngr`, `gpg-wks-server`, and `gpg-wks-client` as dependencies. Total layer cost ~3 MB compressed. Diff: **+1 / 0** in 1 file. ### Why this is the right fix (and not "lower the env var" or "remove the test") The fix isn't obvious unless you trace where `GNUPGHOME` came from. Walking that chain: 1. **PR NVIDIA#1121** (`fix(sandbox): restrict /sandbox to read-only via Landlock (NVIDIA#804)`, authored by @prekshivyas, merged 2026-04-08) made the `/sandbox` home directory Landlock-read-only to prevent agents from modifying their own runtime environment. 2. To keep tools that normally write under `~/...` working (gpg, git config, python history, npm prefix, etc.), that PR redirected each tool's homedir to a writable `/tmp/...` path via env vars in `scripts/nemoclaw-start.sh`. The relevant line is at `scripts/nemoclaw-start.sh:53`: ```sh 'GNUPGHOME=/tmp/.gnupg' ``` alongside `HISTFILE=/tmp/.bash_history`, `GIT_CONFIG_GLOBAL=/tmp/.gitconfig`, `PYTHONUSERBASE=/tmp/.local`, etc. 3. PR NVIDIA#1121 also added three matching assertions in `test/service-env.test.js` (lines 177, 191, 347) verifying that the redirect is set: ```js expect(src).toContain("GNUPGHOME=/tmp/.gnupg"); ``` 4. **What PR NVIDIA#1121 didn't do**: add `gnupg` to the `apt-get install` list in `Dockerfile.base`. The env var setup landed and the test assertions landed, but the install line was missed. 5. CI never noticed because `service-env.test.js` only asserts that the env var is *set* in the source — it never spawns a subprocess that actually runs `gpg`. So a working test suite + a missing binary coexist silently. The QA report (this issue, NVIDIA#1640) catches it as a runtime failure on DGX Spark aarch64 because their test step does invoke `gpg --list-keys`. The clear intent of NVIDIA#1121 was to **enable** gpg under a redirected `GNUPGHOME` — you wouldn't redirect the homedir if you wanted gpg blocked. This PR is the matching install line that NVIDIA#1121 should have included, closing a one-line oversight rather than adding new capability or rolling anything back. ### Why not just remove the GNUPGHOME redirect The env var redirect from NVIDIA#1121 is doing real work — without it, any future `apt-get install gnupg` would still leave gpg unable to write to its homedir under Landlock-read-only `/sandbox`. The redirect is the "right" half of the pair; the install is the missing left half. ### Why this isn't a security regression The sandbox runs LLM-driven agents and gpg is a credential-handling tool, so it's worth justifying explicitly: - The redirected `GNUPGHOME=/tmp/.gnupg` is **fresh and empty** per session — no preloaded keys. - Without keys, gpg can hash/check signatures of public material but cannot decrypt or sign anything. - An agent would have to first import a key (which requires the user to provide it — keys are not pulled from anywhere automatically) before gpg becomes capable of any sensitive operation. - This is the same threat model as `git` and `curl`, which are already in the image and could equally be used to fetch arbitrary content. gpg adds no new capability that the existing toolchain doesn't already have. If the project explicitly *did* want gpg unavailable to agents, the right fix would be to remove the GNUPGHOME redirect from NVIDIA#1121 *and* the matching test assertions, not to keep the env wiring while leaving the binary missing — that's just confusing. ## Type of Change - [x] Code change for a new feature, bug fix, or refactor. - [ ] Code change with doc updates. - [ ] Doc only. Prose changes without code sample modifications. - [ ] Doc only. Includes code sample changes. ## Testing Smoke-tested locally by building `Dockerfile.base` with the fix and running the exact failing command from the bug report: ```sh $ docker build -f Dockerfile.base -t nemoclaw-base-test:gnupg . [...] => exporting to image 46.7s done $ docker run --rm nemoclaw-base-test:gnupg gpg --version gpg (GnuPG) 2.2.40 libgcrypt 1.10.1 $ 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) # And with the runtime-redirected GNUPGHOME from nemoclaw-start.sh: $ 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. The exact `gpg --list-keys` failure from the bug report no longer reproduces. - [x] `hadolint Dockerfile.base` — clean (no warnings) - [x] `docker build -f Dockerfile.base` — succeeds, exports to image cleanly - [x] `gpg --version` in built image — works (`gpg (GnuPG) 2.2.40`) - [x] `gpg --list-keys` in built image — works (was `bash: gpg: command not found` before this PR) - [x] `gpg --list-keys` with `GNUPGHOME=/tmp/.gnupg` — works (matches the runtime env from `nemoclaw-start.sh`) - [ ] `npx prek run --all-files` — partial: ran the affected hooks (commitlint, gitleaks, hadolint) which all pass; did NOT run `test-cli` against the full local suite because two pre-existing baseline failures on stock `main` get in the way on a WSL2 dev host (the `shouldPatchCoredns` issue addressed by PR NVIDIA#1626 (merged) and the install-preflight PATH leakage addressed by PR NVIDIA#1628 (open)). Upstream CI runs on Linux GHA runners and doesn't hit either of those, so it'll exercise the full suite normally. - [ ] `npm test` — same caveat as above, ran the relevant projects in isolation - [ ] `make docs` builds without warnings. (for doc-only changes — N/A) ## Checklist ### General - [x] I have read and followed the [contributing guide](https://github.com/NVIDIA/NemoClaw/blob/main/CONTRIBUTING.md). - [ ] I have read and followed the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md). (for doc-only changes — N/A) ### Code Changes - [x] Formatters applied — `hadolint Dockerfile.base` clean. No JS/TS/Python files touched. - [x] Tests added or updated for new or changed behavior — N/A. The existing `service-env.test.js` already asserts the `GNUPGHOME` redirect introduced in NVIDIA#1121; this PR makes the corresponding binary available so those assertions reflect a runtime that actually works. A new test that spawns `gpg` directly inside a container would arguably be worth a follow-up (it would have caught this gap originally), but it's a separate concern from this one-line install fix. - [x] No secrets, API keys, or credentials committed. - [ ] Doc pages updated for any user-facing behavior changes — N/A. The bug report describes the expected behavior; this PR just makes runtime match it. No docs claim gpg is unavailable. ### Doc Changes - N/A (no doc changes) --- Signed-off-by: T Savo <evilgenius@nefariousplan.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Base system image now includes GnuPG as a pinned OS package. * **Bug Fixes / Security** * GnuPG runtime directory is now created in a separate step with stricter permissions and sandbox ownership when applicable, reducing exposure. * **Tests** * Test suite updated to verify the new directory creation and permission/ownership behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: T Savo <evilgenius@nefariousplan.com> Co-authored-by: Carlos Villela <cvillela@nvidia.com> Co-authored-by: Prekshi Vyas <34834085+prekshivyas@users.noreply.github.com>
Summary
The sandbox base image (
ghcr.io/nvidia/nemoclaw/sandbox-base) is missing thegnupgpackage —gpg --list-keys(and any other gpg invocation) fails withbash: gpg: command not foundinside the sandbox. This adds a single pinnedgnupg=2.2.40-1.1+deb12u2line to the existingapt-get installblock inDockerfile.base, restoring the binary that the rest of the codebase already assumes is present.Related Issue
Closes #1640.
Changes
Dockerfile.base: addgnupg=2.2.40-1.1+deb12u2to the existingapt-get installblock, slotted right aftergit. Same--no-install-recommends, same cleanup tail, same=<version>pinning style as every other package in the block.curl=7.88.1-10+deb12u14 \ git=1:2.39.5-0+deb12u3 \ + gnupg=2.2.40-1.1+deb12u2 \ ca-certificates=20230311+deb12u1 \The pinned version is the bookworm-stable
2.2.40-1.1+deb12u2, verified byapt-cache madison gnupgagainst the exact base image SHAnode:22-slim@sha256:4f77a690.... The package brings indirmngr,gpg-wks-server, andgpg-wks-clientas dependencies. Total layer cost ~3 MB compressed.Diff: +1 / 0 in 1 file.
Why this is the right fix (and not "lower the env var" or "remove the test")
The fix isn't obvious unless you trace where
GNUPGHOMEcame from. Walking that chain:fix(sandbox): restrict /sandbox to read-only via Landlock (#804), authored by @prekshivyas, merged 2026-04-08) made the/sandboxhome directory Landlock-read-only to prevent agents from modifying their own runtime environment.~/...working (gpg, git config, python history, npm prefix, etc.), that PR redirected each tool's homedir to a writable/tmp/...path via env vars inscripts/nemoclaw-start.sh. The relevant line is atscripts/nemoclaw-start.sh:53:'GNUPGHOME=/tmp/.gnupg'HISTFILE=/tmp/.bash_history,GIT_CONFIG_GLOBAL=/tmp/.gitconfig,PYTHONUSERBASE=/tmp/.local, etc.test/service-env.test.js(lines 177, 191, 347) verifying that the redirect is set:gnupgto theapt-get installlist inDockerfile.base. The env var setup landed and the test assertions landed, but the install line was missed.service-env.test.jsonly asserts that the env var is set in the source — it never spawns a subprocess that actually runsgpg. So a working test suite + a missing binary coexist silently. The QA report (this issue, [NemoClaw][All platforms] Sandbox image missing gnupg package — gpg command not found #1640) catches it as a runtime failure on DGX Spark aarch64 because their test step does invokegpg --list-keys.The clear intent of #1121 was to enable gpg under a redirected
GNUPGHOME— you wouldn't redirect the homedir if you wanted gpg blocked. This PR is the matching install line that #1121 should have included, closing a one-line oversight rather than adding new capability or rolling anything back.Why not just remove the GNUPGHOME redirect
The env var redirect from #1121 is doing real work — without it, any future
apt-get install gnupgwould still leave gpg unable to write to its homedir under Landlock-read-only/sandbox. The redirect is the "right" half of the pair; the install is the missing left half.Why this isn't a security regression
The sandbox runs LLM-driven agents and gpg is a credential-handling tool, so it's worth justifying explicitly:
GNUPGHOME=/tmp/.gnupgis fresh and empty per session — no preloaded keys.gitandcurl, which are already in the image and could equally be used to fetch arbitrary content. gpg adds no new capability that the existing toolchain doesn't already have.If the project explicitly did want gpg unavailable to agents, the right fix would be to remove the GNUPGHOME redirect from #1121 and the matching test assertions, not to keep the env wiring while leaving the binary missing — that's just confusing.
Type of Change
Testing
Smoke-tested locally by building
Dockerfile.basewith the fix and running the exact failing command from the bug report:Both the default
~/.gnupgand the runtime-redirected/tmp/.gnupg(matching whatnemoclaw-start.shexports) work as expected. The exactgpg --list-keysfailure from the bug report no longer reproduces.hadolint Dockerfile.base— clean (no warnings)docker build -f Dockerfile.base— succeeds, exports to image cleanlygpg --versionin built image — works (gpg (GnuPG) 2.2.40)gpg --list-keysin built image — works (wasbash: gpg: command not foundbefore this PR)gpg --list-keyswithGNUPGHOME=/tmp/.gnupg— works (matches the runtime env fromnemoclaw-start.sh)npx prek run --all-files— partial: ran the affected hooks (commitlint, gitleaks, hadolint) which all pass; did NOT runtest-cliagainst the full local suite because two pre-existing baseline failures on stockmainget in the way on a WSL2 dev host (theshouldPatchCorednsissue addressed by PR fix(platform): allow shouldPatchCoredns isWsl override for deterministic WSL2 tests #1626 (merged) and the install-preflight PATH leakage addressed by PR fix(test): isolate sysbin in install-preflight tests to prevent host PATH leakage #1628 (open)). Upstream CI runs on Linux GHA runners and doesn't hit either of those, so it'll exercise the full suite normally.npm test— same caveat as above, ran the relevant projects in isolationmake docsbuilds without warnings. (for doc-only changes — N/A)Checklist
General
Code Changes
hadolint Dockerfile.baseclean. No JS/TS/Python files touched.service-env.test.jsalready asserts theGNUPGHOMEredirect introduced in fix(sandbox): restrict /sandbox to read-only via Landlock (#804) #1121; this PR makes the corresponding binary available so those assertions reflect a runtime that actually works. A new test that spawnsgpgdirectly inside a container would arguably be worth a follow-up (it would have caught this gap originally), but it's a separate concern from this one-line install fix.Doc Changes
Signed-off-by: T Savo evilgenius@nefariousplan.com
Summary by CodeRabbit
Chores
Bug Fixes / Security
Tests