Skip to content

fix: remove gcc and netcat from sandbox image (#807, #808)#810

Closed
cluster2600 wants to merge 4 commits into
NVIDIA:mainfrom
cluster2600:security/harden-sandbox-image
Closed

fix: remove gcc and netcat from sandbox image (#807, #808)#810
cluster2600 wants to merge 4 commits into
NVIDIA:mainfrom
cluster2600:security/harden-sandbox-image

Conversation

@cluster2600

@cluster2600 cluster2600 commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Strip compiler toolchain (gcc, g++, cpp, make) and netcat from the sandbox image
  • Applies to both production Dockerfile and test sandbox image
  • Adds regression test to verify dangerous packages are purged

Closes #807, closes #808

Defence layers

graph TD
    subgraph "Image Hardening"
        A[Base Image] -->|apt-get purge| B[No gcc/g++/make]
        A -->|apt-get purge| C[No netcat]
        B --> D[Cannot compile LD_PRELOAD libraries]
        C --> E[Cannot open reverse shells]
    end
    subgraph "Existing Defences"
        F[Landlock] --> G[Read-only /usr /etc]
        H[Network Proxy] --> I[Allowlisted egress]
    end
    D --> J[Defence in Depth]
    E --> J
    F --> J
    H --> J
Loading

Test plan

  • npx vitest run test/dockerfile-attack-surface.test.js passes
  • docker build -t nemoclaw-hardened . succeeds
  • Inside sandbox: which gcc returns not found
  • Inside sandbox: which nc returns not found
  • Existing functionality unaffected (node, python3, curl, git still present)

Summary by CodeRabbit

  • New Features

    • Implemented fork-bomb protection using configurable process limits to prevent denial-of-service attacks in sandboxed environments.
  • Documentation

    • Added comprehensive guides on process limits enforcement and fork-bomb protection mechanisms.
    • Added documentation detailing sandbox image hardening through removal of dangerous packages.
  • Tests

    • Added validation tests for sandbox image hardening.
    • Added enforcement tests for process limit configuration.

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d7a67af-0f4f-44e0-9f1c-9bea9128fcde

📥 Commits

Reviewing files that changed from the base of the PR and between fa2698f and 2747d3c.

📒 Files selected for processing (4)
  • docs/security/process-limits.md
  • docs/security/sandbox-hardening.md
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • scripts/nemoclaw-start.sh
✅ Files skipped from review due to trivial changes (3)
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • docs/security/process-limits.md
  • docs/security/sandbox-hardening.md

📝 Walkthrough

Walkthrough

The changes implement fork-bomb protection and sandbox hardening by adding documentation for process limit enforcement at two layers (container and in-container), removing unnecessary packages (gcc, netcat) from the sandbox image, and introducing tests to verify the hardening measures.

Changes

Cohort / File(s) Summary
Security Documentation
docs/security/process-limits.md, docs/security/sandbox-hardening.md
New documentation pages describing two-layer process limit enforcement for fork-bomb protection and removal of compiler and network utilities from sandbox image with defense-in-depth rationale.
Process Limits Enforcement
scripts/nemoclaw-start.sh, nemoclaw-blueprint/policies/openclaw-sandbox.yaml
Implementation of fork-bomb protection: startup script adds fallback ulimit -u 512 check when limit is unlimited; policy file documents that enforcement must occur at container runtime level.
Hardening Validation Tests
test/dockerfile-attack-surface.test.js, test/process-limits.test.js
New test suites verifying removal of gcc, g++, make, and netcat packages from Dockerfiles, and validating process limit enforcement across startup script, policy file, and onboarding logic.

Sequence Diagram(s)

sequenceDiagram
    actor Operator
    participant ContainerRuntime as Container Runtime<br/>(nemoclaw onboard)
    participant Kernel as Kernel<br/>cgroup (pids.max)
    participant Container as Container<br/>Startup
    participant Shell as Shell Script<br/>(nemoclaw-start.sh)
    
    Operator->>ContainerRuntime: Deploy NemoClaw
    ContainerRuntime->>ContainerRuntime: docker update --pids-limit 512
    ContainerRuntime->>Kernel: Set cgroup pids.max = 512
    Operator->>Container: Start container
    Container->>Shell: Execute nemoclaw-start.sh
    Shell->>Shell: ulimit -u (query current limit)
    alt Limit is unlimited
        Shell->>Shell: ulimit -u 512 (attempt to set)
        Shell->>Kernel: Register process limit
    else Limit already set
        Shell->>Shell: Skip override
    end
    Shell->>Shell: Continue startup
    note over Kernel: Fork attempts beyond 512<br/>fail with EAGAIN
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Fortified warrens need strong walls,
No compilers when danger calls,
Fork bombs thwarted, two shields in place,
Netcat banished from sandbox space—
Defense in depth keeps agents tame! 🛡️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: remove gcc and netcat from sandbox image (#807, #808)' is clear and specific, accurately summarizing the primary security hardening changes in the changeset.
Linked Issues check ✅ Passed The pull request successfully addresses all primary objectives from linked issues: removes gcc/g++/make [#807], purges netcat packages [#808], adds regression tests for removed packages, and includes defence-in-depth documentation.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue objectives: documentation pages explain the hardening and fork-bomb protections, test files verify removals, and scripts implement the protection mechanisms specified in the requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
docs/security/process-limits.md (1)

33-34: One sentence per line for readable diffs.

Per documentation guidelines, each sentence should be on its own line in the source.

📝 Proposed fix
-The default process limit is 512 per sandbox. This is sufficient for normal
-agent operation (typically < 50 processes) whilst preventing fork bombs.
+The default process limit is 512 per sandbox.
+This is sufficient for normal agent operation (typically < 50 processes) whilst preventing fork bombs.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/security/process-limits.md` around lines 33 - 34, Split the two
sentences in the paragraph so each sentence occupies its own line in the source:
move "The default process limit is 512 per sandbox." and "This is sufficient for
normal agent operation (typically < 50 processes) whilst preventing fork bombs."
onto separate lines (i.e., one sentence per line) to follow the documentation
guideline for readable diffs.
docs/security/sandbox-hardening.md (1)

41-43: One sentence per line for readable diffs.

Per documentation guidelines, each sentence should be on its own line.

📝 Proposed fix
-Even if one layer is bypassed, the others provide protection. Removing tools
-from the image means an attacker who escapes the proxy still cannot compile
-custom tooling or open raw TCP connections.
+Even if one layer is bypassed, the others provide protection.
+Removing tools from the image means an attacker who escapes the proxy still cannot compile custom tooling or open raw TCP connections.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/security/sandbox-hardening.md` around lines 41 - 43, The paragraph "Even
if one layer is bypassed, the others provide protection. Removing tools from the
image means an attacker who escapes the proxy still cannot compile custom
tooling or open raw TCP connections." should be split so each sentence is on its
own line; edit the docs/security/sandbox-hardening.md content replacing that
single-line paragraph with two lines, one containing "Even if one layer is
bypassed, the others provide protection." and the next containing "Removing
tools from the image means an attacker who escapes the proxy still cannot
compile custom tooling or open raw TCP connections." to satisfy the
one-sentence-per-line guideline.
test/process-limits.test.js (1)

66-69: Make the default-value assertion resilient to equivalent refactors.

The regex at Line 68 only accepts || "512". If code switches to nullish coalescing (?? "512"), behavior stays correct but this test fails. Consider allowing both operators.

Optional refactor
-      expect(onboardJs).toMatch(/\|\|\s*"512"/);
+      expect(onboardJs).toMatch(/(\|\||\?\?)\s*["']512["']/);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/process-limits.test.js` around lines 66 - 69, Update the assertion in
the test "uses NEMOCLAW_PIDS_LIMIT env var with 512 default" to accept both
logical OR and nullish coalescing forms; modify the
expect(onboardJs).toMatch(...) regex to allow either "||" or "??" (with optional
whitespace) before the default string "512" so it still passes if code switches
from || "512" to ?? "512".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/onboard.js`:
- Around line 637-641: The pidsLimit environment value (NEMOCLAW_PIDS_LIMIT) is
injected directly into the shell command used by runCapture which allows command
injection; validate and sanitize it before use by parsing it as an integer and
falling back to a safe default if invalid, then pass that numeric value (e.g.,
parsedPidLimit) into the docker update invocation instead of the raw string;
update code references where pidsLimit is read and used (NEMOCLAW_PIDS_LIMIT,
pidsLimit, runCapture, and the docker update command that uses sandboxName) to
enforce an integer-only range (e.g., positive integer and reasonable upper
bound) and handle errors by logging and using the default.

In `@docs/security/process-limits.md`:
- Around line 62-64: Change the CLI code block to use the console language tag
and add a $ prompt: replace the ```bash fence containing
"NEMOCLAW_PIDS_LIMIT=1024 nemoclaw onboard" with a ```console fence and prefix
the command with "$ " so the block reads "$ NEMOCLAW_PIDS_LIMIT=1024 nemoclaw
onboard"; update the code fence start/end accordingly around the
"NEMOCLAW_PIDS_LIMIT" environment variable and "nemoclaw onboard" command.
- Around line 1-4: Add YAML frontmatter to the top of the page containing title,
description, keywords, topics, tags, content_type (or content type), difficulty,
audience, and status fields, then immediately after the frontmatter add the
required SPDX license header (e.g., "SPDX-License-Identifier: <license>");
ensure the frontmatter values are concise and accurate for "Process Limits" /
"Fork Bomb Protection" and include relevant keywords and topics so the page
meets the documentation guidelines.

In `@docs/security/sandbox-hardening.md`:
- Around line 1-6: The document lacks the required SPDX license header and
frontmatter and uses passive voice; add the standard SPDX header line at the top
and a YAML frontmatter block containing required fields (title, description,
author/date or other repo-required keys) before the content, and rewrite passive
sentences like "The NemoClaw sandbox image is hardened..." to active voice
(e.g., "NemoClaw hardens the sandbox image...")—update the title "Sandbox Image
Hardening" and the sentence mentioning "NemoClaw sandbox image" to reflect
active voice and ensure frontmatter values match those names.

---

Nitpick comments:
In `@docs/security/process-limits.md`:
- Around line 33-34: Split the two sentences in the paragraph so each sentence
occupies its own line in the source: move "The default process limit is 512 per
sandbox." and "This is sufficient for normal agent operation (typically < 50
processes) whilst preventing fork bombs." onto separate lines (i.e., one
sentence per line) to follow the documentation guideline for readable diffs.

In `@docs/security/sandbox-hardening.md`:
- Around line 41-43: The paragraph "Even if one layer is bypassed, the others
provide protection. Removing tools from the image means an attacker who escapes
the proxy still cannot compile custom tooling or open raw TCP connections."
should be split so each sentence is on its own line; edit the
docs/security/sandbox-hardening.md content replacing that single-line paragraph
with two lines, one containing "Even if one layer is bypassed, the others
provide protection." and the next containing "Removing tools from the image
means an attacker who escapes the proxy still cannot compile custom tooling or
open raw TCP connections." to satisfy the one-sentence-per-line guideline.

In `@test/process-limits.test.js`:
- Around line 66-69: Update the assertion in the test "uses NEMOCLAW_PIDS_LIMIT
env var with 512 default" to accept both logical OR and nullish coalescing
forms; modify the expect(onboardJs).toMatch(...) regex to allow either "||" or
"??" (with optional whitespace) before the default string "512" so it still
passes if code switches from || "512" to ?? "512".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c7f63b2-2a81-4752-a165-02e4108ce379

📥 Commits

Reviewing files that changed from the base of the PR and between 6e1208c and 478773d.

📒 Files selected for processing (9)
  • Dockerfile
  • bin/lib/onboard.js
  • docs/security/process-limits.md
  • docs/security/sandbox-hardening.md
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • scripts/nemoclaw-start.sh
  • test/Dockerfile.sandbox
  • test/dockerfile-attack-surface.test.js
  • test/process-limits.test.js

Comment thread bin/lib/onboard.js Outdated
Comment thread docs/security/process-limits.md
Comment thread docs/security/process-limits.md
Comment thread docs/security/sandbox-hardening.md Outdated
@wscurran wscurran added bug Something fails against expected or documented behavior Docker security Potential vulnerability, unsafe behavior, or access risk labels Mar 25, 2026
@wscurran

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR with a detailed summary, it addresses a security concern and improves the overall security of the sandbox image, which could reduce the risk of vulnerabilities.

@cluster2600 cluster2600 force-pushed the security/harden-sandbox-image branch from 2716869 to f061384 Compare March 25, 2026 19:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
bin/lib/onboard.js (1)

1511-1515: ⚠️ Potential issue | 🔴 Critical

Sanitize NEMOCLAW_PIDS_LIMIT to prevent command injection.

The pidsLimit value is interpolated directly into the shell command without validation. A malicious environment variable like NEMOCLAW_PIDS_LIMIT="512; rm -rf /" could execute arbitrary commands.

Note: This was flagged in a previous review where the author indicated the code was in PR #811, but the vulnerability exists in this PR's code as well.

🛡️ Proposed fix to validate the pids limit
   const pidsLimit = process.env.NEMOCLAW_PIDS_LIMIT || "512";
+  if (!/^\d+$/.test(pidsLimit)) {
+    console.error(`  Invalid NEMOCLAW_PIDS_LIMIT: ${pidsLimit} (must be a positive integer)`);
+    process.exit(1);
+  }
   const dockerUpdate = runCapture(
     `docker update --pids-limit ${pidsLimit} "${sandboxName}" 2>&1`,
     { ignoreError: true }
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 1511 - 1515, pidsLimit is taken directly
from process.env and interpolated into the shell command, allowing command
injection; change the logic around the pidsLimit assignment so you parse and
validate it as an integer (e.g., const parsed =
parseInt(process.env.NEMOCLAW_PIDS_LIMIT, 10)), ensure it is a finite number
within an acceptable range (fallback to 512 if NaN/out-of-range), and then use
that validated integer when calling runCapture for the `docker update
--pids-limit ${pidsLimit}` invocation so only a numeric value is injected into
the command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dockerfile`:
- Around line 31-35: The Dockerfile RUN step that does "apt-get update &&
apt-get purge -y --auto-remove ... && rm -rf /var/lib/apt/lists/* || true" is
masking failures; remove the trailing "|| true" so the build fails if apt-get
update/purge/cleanup fails, ensuring the purge of packages like gcc/g++/make and
netcat completes or the build stops; keep the existing chained commands and
error propagation semantics (no additional try/catch), so failures in that RUN
line fail the image build.

---

Duplicate comments:
In `@bin/lib/onboard.js`:
- Around line 1511-1515: pidsLimit is taken directly from process.env and
interpolated into the shell command, allowing command injection; change the
logic around the pidsLimit assignment so you parse and validate it as an integer
(e.g., const parsed = parseInt(process.env.NEMOCLAW_PIDS_LIMIT, 10)), ensure it
is a finite number within an acceptable range (fallback to 512 if
NaN/out-of-range), and then use that validated integer when calling runCapture
for the `docker update --pids-limit ${pidsLimit}` invocation so only a numeric
value is injected into the command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2272595c-a1c3-4561-b8b8-605dc422d662

📥 Commits

Reviewing files that changed from the base of the PR and between 2716869 and f061384.

📒 Files selected for processing (8)
  • Dockerfile
  • bin/lib/onboard.js
  • docs/security/process-limits.md
  • docs/security/sandbox-hardening.md
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • scripts/nemoclaw-start.sh
  • test/dockerfile-attack-surface.test.js
  • test/process-limits.test.js
✅ Files skipped from review due to trivial changes (5)
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • test/process-limits.test.js
  • docs/security/process-limits.md
  • docs/security/sandbox-hardening.md
  • scripts/nemoclaw-start.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/dockerfile-attack-surface.test.js

Comment thread Dockerfile Outdated
Comment on lines +31 to +35
RUN apt-get update && apt-get purge -y --auto-remove \
gcc g++ cpp make \
netcat-openbsd netcat-traditional \
&& rm -rf /var/lib/apt/lists/* \
|| true

@coderabbitai coderabbitai Bot Mar 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether a hardening purge command is currently error-masked.
set -euo pipefail
rg -nP 'apt-get\s+purge[\s\S]*\|\|\s*true' Dockerfile -C 2
# Expected: no matches after fix.

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Directly read the Dockerfile around the specified lines
cat -n Dockerfile | sed -n '25,40p'

Repository: NVIDIA/NemoClaw

Length of output: 879


Remove || true from the security-hardening purge step.

Lines 31–35 contain || true, which suppresses failures from apt-get update, apt-get purge, and cleanup operations. If any of these commands fail, the build succeeds anyway while dangerous packages remain installed, undermining the security guarantee documented in the surrounding comments.

Suggested fix
 RUN apt-get update && apt-get purge -y --auto-remove \
         gcc g++ cpp make \
         netcat-openbsd netcat-traditional \
     && rm -rf /var/lib/apt/lists/* \
-    || true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN apt-get update && apt-get purge -y --auto-remove \
gcc g++ cpp make \
netcat-openbsd netcat-traditional \
&& rm -rf /var/lib/apt/lists/* \
|| true
RUN apt-get update && apt-get purge -y --auto-remove \
gcc g++ cpp make \
netcat-openbsd netcat-traditional \
&& rm -rf /var/lib/apt/lists/*
🧰 Tools
🪛 Trivy (0.69.3)

[error] 31-35: 'RUN update' instruction alone

The instruction 'RUN update' should always be followed by ' install' in the same RUN statement.

Rule: DS-0017

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 31 - 35, The Dockerfile RUN step that does "apt-get
update && apt-get purge -y --auto-remove ... && rm -rf /var/lib/apt/lists/* ||
true" is masking failures; remove the trailing "|| true" so the build fails if
apt-get update/purge/cleanup fails, ensuring the purge of packages like
gcc/g++/make and netcat completes or the build stops; keep the existing chained
commands and error propagation semantics (no additional try/catch), so failures
in that RUN line fail the image build.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The || true is intentional — node:22-slim doesn't ship gcc or netcat in all builds, so the purge can fail legitimately on images that don't have them. Removing it would break the build on those base images.

The security guarantee comes from the test (dockerfile-attack-surface.test.js) verifying the purge line is present, plus the runtime verification we ran on the DGX Spark confirming the packages are actually gone.

That said, a tighter approach would be to check before purging:

RUN dpkg -l gcc g++ cpp make netcat-openbsd netcat-traditional 2>/dev/null | grep -q ^ii \
    && apt-get update && apt-get purge -y --auto-remove gcc g++ cpp make netcat-openbsd netcat-traditional \
    && rm -rf /var/lib/apt/lists/* \
    || true

Happy to update if the maintainers prefer that pattern — it makes the || true only cover the "packages not installed" case, not a genuine purge failure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cluster2600, thanks for the clear explanation — you're right. apt-get purge exits with code 100 when a package isn't installed, so || true is necessary for portability across node:22-slim variants that may not include those packages. I withdraw my original concern on that basis.

The dpkg -l pre-check you proposed is a nice improvement in clarity:

RUN dpkg -l gcc g++ cpp make netcat-openbsd netcat-traditional 2>/dev/null | grep -q ^ii \
    && apt-get update && apt-get purge -y --auto-remove gcc g++ cpp make netcat-openbsd netcat-traditional \
    && rm -rf /var/lib/apt/lists/* \
    || true

One small caveat: because grep -q ^ii exits 0 as soon as it finds any ii-prefixed line, if only a subset of those packages are installed and the purge fails mid-way for a real reason, the trailing || true still masks it. That's a marginal scenario, and the overall intent is clearer than the current form — so either way is fine. The existing test coverage in dockerfile-attack-surface.test.js and the runtime DGX Spark verification are the stronger guarantees here.

(*ᵔ ᗜ ᔕ*)


✏️ Learnings added
Learnt from: cluster2600
Repo: NVIDIA/NemoClaw PR: 810
File: Dockerfile:31-35
Timestamp: 2026-03-25T19:32:46.200Z
Learning: In the NemoClaw `Dockerfile`, the `|| true` appended to the `apt-get purge` hardening step (removing gcc, g++, cpp, make, netcat-openbsd, netcat-traditional) is intentional. `apt-get purge` exits with code 100 when a package is absent, which would break builds on `node:22-slim` variants that don't include those packages. Do not flag this `|| true` as masking a security issue — the guarantee that packages are absent is validated by `test/dockerfile-attack-surface.test.js` and runtime verification.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: cluster2600
Repo: NVIDIA/NemoClaw PR: 782
File: test/config-io.test.js:54-75
Timestamp: 2026-03-24T11:21:32.603Z
Learning: In the NVIDIA/NemoClaw repo, the test suite (including `test/config-io.test.js`) runs on Ubuntu CI runners as a non-root user. Chmod-driven EACCES assertions (e.g., `fs.chmodSync(dir, 0o500)` to simulate permission errors) are intentional and reliable in this environment. Do not flag these as fragile or platform-unsafe unless there is evidence of root/Windows CI usage.

Learnt from: cluster2600
Repo: NVIDIA/NemoClaw PR: 782
File: test/policies.test.js:0-0
Timestamp: 2026-03-24T11:21:33.065Z
Learning: In NVIDIA/NemoClaw, test files (e.g., `test/policies.test.js`) intentionally use raw substring checks (`content.includes("binaries:")`, `content.includes("network_policies:")`, etc.) to validate YAML preset structure rather than parsing YAML. This is a deliberate project convention — no YAML parser dependency is added just for tests. Do not flag these substring assertions as insufficient or recommend switching to structural YAML parsing.

Learnt from: paritoshd-nv
Repo: NVIDIA/NemoClaw PR: 678
File: spark-install.md:105-105
Timestamp: 2026-03-23T01:26:31.640Z
Learning: In `spark-install.md`, the command `docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi` is intentionally correct. The NVIDIA Container Toolkit mounts `nvidia-smi` from the host into any container (including the plain `ubuntu` image) at runtime via the utility capability — no CUDA base image is required. Do not flag this pattern as an issue.

Learnt from: rwipfelnv
Repo: NVIDIA/NemoClaw PR: 227
File: k8s/nemoclaw-k8s.yaml:69-71
Timestamp: 2026-03-25T16:04:51.157Z
Learning: In `k8s/nemoclaw-k8s.yaml` (and any NemoClaw K8s testing manifests), the line `curl -fsSL https://nvidia.com/nemoclaw.sh | bash` is the official documented installation command for NemoClaw as published by NVIDIA at docs.nvidia.com/nemoclaw and nvidia.com/nemoclaw.sh. Do not flag this as a supply-chain or curl|bash security issue — it intentionally mirrors the upstream documented installer invocation.

Strip compiler toolchain (gcc, g++, cpp, make) and netcat from the
runtime image to reduce attack surface. A prompt-injected agent can
no longer compile LD_PRELOAD libraries or open reverse shells.

Signed-off-by: Maxime Grenu <maxime.grenu@gmail.com>
Set ulimit -u 512 in nemoclaw-start.sh when the container runtime
hasn't set a process limit. Documents the need for --pids-limit
at the container level as a follow-up.

Signed-off-by: Maxime Grenu <maxime.grenu@gmail.com>
Add frontmatter, SPDX licence header, use active voice, and
myst-compatible mermaid fencing per documentation guidelines.

Signed-off-by: Maxime Grenu <maxime.grenu@gmail.com>
ESLint flags __dirname as undefined in ESM context. Use
import.meta.dirname which is the standard pattern in this project.

Signed-off-by: Maxime Grenu <maxime.grenu@gmail.com>
@cluster2600 cluster2600 force-pushed the security/harden-sandbox-image branch from fa2698f to 2747d3c Compare April 14, 2026 09:10
@cluster2600

Copy link
Copy Markdown
Contributor Author

Closing — these changes landed in #830 (remove gcc/netcat, drop capabilities). Cheers to @BenediktSchackenberg for pushing it through.

@wscurran wscurran added area: packaging Packages, images, registries, installers, or distribution bug-fix PR fixes a bug or regression platform: container Affects Docker, containerd, Podman, or images and removed area: packaging Packages, images, registries, installers, or distribution Docker bug Something fails against expected or documented behavior labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PR fixes a bug or regression platform: container Affects Docker, containerd, Podman, or images security Potential vulnerability, unsafe behavior, or access risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SECURITY] netcat (nc) available in sandbox image [SECURITY] gcc available in sandbox image — unnecessary attack surface

2 participants