DEV Community

Alain Airom (Ayrom)
Alain Airom (Ayrom)

Posted on

Testing NVIDIA NemoClaw in a Sandboxed Environment

Testing NVIDIA NemoClaw in a Local Sandboxed Environment with Bob

Introduction

For a while, I’ve wanted to test and implement NemoClaw in a sandboxed environment to better understand its capabilities and real-world usage. Safety and isolation are paramount when granting LLMs agency—especially when agents can execute terminal commands, modify local workspaces, or perform egress network requests.

To evaluate its operational stack, I built a test harness and orchestration environment using Bob. In this post, I’ll walk through the fundamentals of NVIDIA NemoClaw, break down its core security architecture, and share an actionable implementation guide so you can spin up your own isolated agent sandbox.


TL;DR: What is NVIDIA NemoClaw?

NemoClaw is an open-source reference stack from NVIDIA designed to execute always-on AI agents safely inside OpenShell sandboxes. It serves as a bridge between high-capability agent runtimes—such as OpenClaw, Hermes, or LangChain Deep Agents—and host system security.


> Image from Nvidia (https://docs.nvidia.com/nemoclaw/user-guide/openclaw/reference/architecture)

+-----------------------------------------------------------------------+
|                              Host Machine                             |
|  +-------------------+  +------------------+  +--------------------+  |
|  | nemoclaw CLI      |  | OpenShell        |  | Host State         |  |
|  | (Node.js CJS)     |  | Gateway Proxy    |  | ~/.nemoclaw/       |  |
|  +---------+---------+  +--------+---------+  +--------------------+  |
+------------|---------------------|------------------------------------+
             | orchestrates        | L7 Proxy & Credentials
             v                     v
+-----------------------------------------------------------------------+
|                    🛡️ Sandbox Container (Podman/Docker)               |
|                    --read-only + tmpfs mounts                          |
|  +-------------------+  +------------------+  +--------------------+  |
|  | AI Agent          |  | NemoClaw         |  | Managed Endpoint   |  |
|  | (OpenClaw/Hermes) |  | In-Process Plugin|  | (inference.local)  |  |
|  +-------------------+  +------------------+  +--------------------+  |
+-----------------------------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

NVIDIA NemoClaw: Reference Stack for Sandboxed AI Agents in OpenShell (from official Github repository)


_

Image from Nvidia (https://docs.nvidia.com/nemoclaw/user-guide/openclaw/reference/architecture)
_

NVIDIA NemoClaw is an open source reference stack for running supported AI agents more safely inside NVIDIA OpenShell sandboxes. It provides guided onboarding, managed inference, network policy, managed integrations, snapshots, and lifecycle operations through the NemoClaw CLI and its agent-specific aliases.

Supported agents:

  • OpenClaw (default)
  • Hermes
  • LangChain Deep Agents Code

Technical Stack of the Local Implementation

Components Stack

The code and project uses the following stacks;

  • Podman (for provisionning NemoClaw Sandbox)
  • Ollama (local llm, if not using a model hosted by Nvidia using an API Key for Nvidia. One can use either Ollama or llama.cpp)
  • LLam.cpp (local llm, if not using a model hosted by Nvidia using an API Key for Nvidia. One can use either Ollama or llama.cpp)
  • Python Streamlit as the application’s framework

Core Architecture Highlights

  • Host & Sandbox Separation: The orchestration logic (nemoclaw CLI) runs on the host, while the agent executes inside an isolated container namespace (Podman or Docker).

  • No Raw Credentials in Sandbox: Agents make requests to a local proxy (inference.local). The host-side OpenShell Gateway injects real credentials (such as an NVIDIA_API_KEY) at egress and strips them from incoming responses.

  • Multi-Provider Routed Inference: Out-of-the-box support for switching between cloud endpoints like NVIDIA Nemotron/NIM and local providers like Ollama (localhost:11434) or llama.cpp (localhost:9931).

  • Strict Containment Policies: Containers launch with immutable root filesystems (--read-only), capability drops (--cap-drop=all), non-root privilege restrictions, and fine-grained network egress rules.


Implementation Guide And Tests

This step-by-step setup guide includes blueprint excerpts and setup scripts.

Environment Setup & Prerequisites

  • First, define your environment variables in .env to configure API keys and runtime parameters:
# NemoClaw Environment Configuration
# Copy this file to .env and fill in your values.
# NEVER commit .env to source control.

# ─── Inference Provider ────────────────────────────────────────────────────────
# NVIDIA API key for Nemotron / NIM inference
NVIDIA_API_KEY=your-nvidia-api-key-here

# Default inference model (e.g. nvidia/llama-3.1-nemotron-ultra-253b-v1)
NEMOCLAW_MODEL=nvidia/llama-3.1-nemotron-70b-instruct

# Inference provider: nvidia | ollama | openai-compatible | model-router
NEMOCLAW_INFERENCE_PROVIDER=nvidia

# ─── Ollama (local LLM) ────────────────────────────────────────────────────────
# If using Ollama as inference provider
OLLAMA_BASE_URL=http://localhost:11434

# ─── llama.cpp (local LLM) ────────────────────────────────────────────────────
LLAMACPP_BASE_URL=http://localhost:9931/v1
LLAMACPP_API_KEY=

# ─── OpenShell Gateway ────────────────────────────────────────────────────────
# Port for the OpenShell gateway (default: 10000)
NEMOCLAW_GATEWAY_PORT=10000

# ─── Agent Configuration ──────────────────────────────────────────────────────
# Agent to use: openclaw | hermes | langchain-deepagents-code
NEMOCLAW_AGENT=openclaw

# Sandbox name (1-63 lowercase letters, numbers, internal hyphens)
NEMOCLAW_SANDBOX_NAME=nemoclaw-sandbox

# ─── Dashboard / UI ───────────────────────────────────────────────────────────
# Port for the Streamlit dashboard (avoid 5000 on macOS - reserved for AirDrop)
DASHBOARD_PORT=8501

# ─── Logging ──────────────────────────────────────────────────────────────────
# Log level: debug | info | warn | error
LOG_LEVEL=info

# Log file path (optional, defaults to stdout)
LOG_FILE=

# ─── Network Policy ───────────────────────────────────────────────────────────
# Comma-separated list of allowed egress hosts (beyond defaults)
EXTRA_ALLOWED_HOSTS=

# ─── State / Storage ──────────────────────────────────────────────────────────
NEMOCLAW_STATE_DIR=~/.nemoclaw

# ─── Security ─────────────────────────────────────────────────────────────────
# SSRF validation: strict | permissive
SSRF_VALIDATION_MODE=strict

# ─── MCP Servers ──────────────────────────────────────────────────────────────
# Comma-separated list of MCP server names to enable
MCP_SERVERS=
Enter fullscreen mode Exit fullscreen mode
  • Run the initialization script to bootstrap Node.js dependencies, set up the Python virtual environment, and link the CLI globally:
#!/usr/bin/env bash
set -e

echo "=== 1. Installing Node Dependencies & Linking CLI ==="
npm install
cd nemoclaw && npm install && npm run build && cd ..
npm link

echo "=== 2. Setting up Python Environment ==="
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

echo "=== 3. Host Readiness Check ==="
nemoclaw host probe
Enter fullscreen mode Exit fullscreen mode

Architecture & Hardening Configuration

  • NemoClaw enforces isolation at the container engine layer. The following excerpt shows how runner.js provisions a hardened sandbox using Podman:
// Excerpt: bin/lib/runner.js - Container Launch Configuration
const podmanFlags = [
  'run', '-d',
  '--name', sandboxName,
  '--read-only',                                      // Immutable root filesystem
  '--security-opt', 'no-new-privileges',             // Prevent privilege escalation
  '--cap-drop=all',                                  // Drop all Linux capabilities
  '--tmpfs', '/tmp:rw,noexec,nosuid,size=128m',       // Writable non-executable temp space
  '--tmpfs', '/home/nemoclaw/.streamlit:rw,nosuid,size=32m', // Session cache space
  networkModeFlag,                                   // --network=pasta or --network=bridge
  containerImage
];

// Placeholder: Custom container invocation parameters
// ____________________________________________________
// ____________________________________________________
Enter fullscreen mode Exit fullscreen mode

Network Policy Definition

  • Network egress is restricted to explicitly allowed hostnames. The network policy is defined declaratively using YAML:
# nemoclaw-blueprint/policies/default-policy.yamlversion: "1"egress:  - host: inference.local    port: 443    protocol: https    comment: "Managed inference endpoint proxied by Gateway"  - host: github.com    port: 443    protocol: https    comment: "Repository operations"  - host: registry.npmjs.org    port: 443    protocol: https    comment: "Package dependencies"deny: []
Enter fullscreen mode Exit fullscreen mode
  • You can modify rules dynamically via the CLI without recreating the container:
# Add a custom host rule
nemoclaw policy add ________________________

# Apply a preset policy (e.g., Slack or Discord)
nemoclaw policy add slack
Enter fullscreen mode Exit fullscreen mode

Sandbox Onboarding & Execution

  • Execute the onboarding sequence to build the image, register credentials from .env, instantiate the OpenShell Gateway, and start the container runtime:
# Execute onboarding sequence
nemoclaw onboard --agent openclaw --name ________________________

# Verify status and running processes
nemoclaw status

# Stream live container logs
nemoclaw logs --follow
Enter fullscreen mode Exit fullscreen mode

Python State Inspection Test

  • You can write tests using unittest or pytest to programmatically inspect sandbox metadata and verify that secrets remain unexposed:
"""Unit tests for the local NemoClaw sandbox demonstration.

The five tests map directly to the scenarios in Docs/NemoClaw-Sandbox-User-Guide.md.
"""

import sys
import unittest
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))

from nemoclaw_sandbox_demo import AgentProfile, Sandbox, SandboxLimits, SandboxTimeout


class TestNemoClawSandboxScenarios(unittest.TestCase):
    """Validate the sandbox contract without requiring Podman or network access."""

    def setUp(self):
        self.agent = AgentProfile(
            name="agent",
            role="test agent",
            capabilities=frozenset({
                "read_workspace", "write_workspace", "network:inference", "compute", "inter_agent:send",
            }),
        )

    def test_normal_in_sandbox_execution(self):
        """An authorized agent can write and read only its private workspace."""
        with Sandbox() as sandbox:
            sandbox.register_agent(self.agent)
            written = sandbox.run(self.agent, "write_workspace", path="workspace/report.txt", content="pass")
            read = sandbox.run(self.agent, "read_workspace", path="workspace/report.txt")

        self.assertTrue(written.permitted)
        self.assertEqual(read.value, "pass")
        self.assertTrue(sandbox.closed)

    def test_escape_and_network_boundary_are_denied(self):
        """Traversal and non-approved egress are blocked and audited."""
        with Sandbox() as sandbox:
            sandbox.register_agent(self.agent)
            traversal = sandbox.run(self.agent, "read_workspace", path="../../etc/passwd")
            egress = sandbox.run(self.agent, "network_request", host="example.com")

        self.assertFalse(traversal.permitted)
        self.assertIn("filesystem path denied", traversal.error)
        self.assertFalse(egress.permitted)
        self.assertIn("network host denied", egress.error)

    def test_resource_limits_are_enforced(self):
        """Excess memory is rejected and long-running work is terminated."""
        limits = SandboxLimits(memory_limit_mb=8, timeout_seconds=0.1, cpu_limit_seconds=1)
        with Sandbox(limits) as sandbox:
            sandbox.register_agent(self.agent)
            memory = sandbox.run(self.agent, "allocate_memory", megabytes=16)
            timeout = sandbox.run(self.agent, "busy_loop")

        self.assertFalse(memory.permitted)
        self.assertIn("exceeds limit", memory.error)
        self.assertFalse(timeout.permitted)
        self.assertIsInstance(timeout.error, str)
        self.assertIn("timeout", timeout.error)

    def test_inter_agent_communication_stays_inside_sandbox(self):
        """A message can move between registered agents through the local mailbox."""
        sender = self.agent
        receiver = AgentProfile("receiver", "reviewer", frozenset({"inter_agent:receive"}))
        with Sandbox() as sandbox:
            sandbox.register_agent(sender)
            sandbox.register_agent(receiver)
            sent = sandbox.run(sender, "send_message", recipient="receiver", message="approved")
            received = sandbox.run(receiver, "receive_message")

        self.assertTrue(sent.permitted)
        self.assertEqual(received.value, "approved")

    def test_failure_is_reported_and_teardown_is_graceful(self):
        """A denied operation returns a structured error and context is removed."""
        sandbox = Sandbox()
        sandbox.__enter__()
        sandbox.register_agent(self.agent)
        result = sandbox.run(self.agent, "unsupported_operation")
        root = sandbox.root
        sandbox.teardown()

        self.assertFalse(result.permitted)
        self.assertIn("not supported", result.error)
        self.assertTrue(sandbox.closed)
        self.assertFalse(root.exists())
        with self.assertRaises(RuntimeError):
            sandbox.root


if __name__ == "__main__":
    unittest.main()
Enter fullscreen mode Exit fullscreen mode

Test-Case Walkthrough

Now that the application is running, we will validate its behavior under realistic conditions using the following test scenarios.

The test harness exposes five automated test scenarios (test/test_nemoclaw_sandbox.py) that validate allowed behaviors while asserting that policy violations fail predictably.

Normal In-Sandbox Execution (TC-01)

Validates that an authorized agent can write artifacts to its designated workspace and read them back cleanly.

# test/test_nemoclaw_sandbox.py - Scenario 1
def test_normal_in_sandbox_execution(self):
    """An authorized agent can write and read only its private workspace."""
    with Sandbox() as sandbox:
        sandbox.register_agent(self.agent)
        written = sandbox.run(self.agent, "write_workspace", path="workspace/report.txt", content="pass")
        read = sandbox.run(self.agent, "read_workspace", path="workspace/report.txt")

    self.assertTrue(written.permitted)
    self.assertEqual(read.value, "pass")
    self.assertTrue(sandbox.closed)
Enter fullscreen mode Exit fullscreen mode

Scenario 2: Traversal & Egress Violation Defense (TC-02)

  • Ensures that directory traversal attempts (e.g., ../../etc/passwd) and unauthorized external network requests (e.g., example.com) are immediately denied and logged to the audit trail.
# test/test_nemoclaw_sandbox.py - Scenario 2
def test_escape_and_network_boundary_are_denied(self):
    """Traversal and non-approved egress are blocked and audited."""
    with Sandbox() as sandbox:
        sandbox.register_agent(self.agent)
        traversal = sandbox.run(self.agent, "read_workspace", path="../../etc/passwd")
        egress = sandbox.run(self.agent, "network_request", host="example.com")

    self.assertFalse(traversal.permitted)
    self.assertIn("filesystem path denied", traversal.error)
    self.assertFalse(egress.permitted)
    self.assertIn("network host denied", egress.error)
Enter fullscreen mode Exit fullscreen mode

Scenario 3: Resource Limit Enforcement (TC-03)

  • Proves that attempts to exceed memory quotas are rejected prior to allocation, and long-running operations (such as infinite loops) are terminated by worker process timeouts.
# test/test_nemoclaw_sandbox.py - Scenario 3
def test_resource_limits_are_enforced(self):
    """Excess memory is rejected and long-running work is terminated."""
    limits = SandboxLimits(memory_limit_mb=8, timeout_seconds=0.1, cpu_limit_seconds=1)
    with Sandbox(limits) as sandbox:
        sandbox.register_agent(self.agent)
        memory = sandbox.run(self.agent, "allocate_memory", megabytes=16)
        timeout = sandbox.run(self.agent, "busy_loop")

    self.assertFalse(memory.permitted)
    self.assertIn("exceeds limit", memory.error)
    self.assertFalse(timeout.permitted)
    self.assertIsInstance(timeout.error, str)
    self.assertIn("timeout", timeout.error)
Enter fullscreen mode Exit fullscreen mode

Scenario 4: Inter-Agent Communication Containment (TC-04)

  • Demonstrates secure inter-agent messaging routed entirely within in-memory mailboxes inside the sandbox, without opening external host ports or network sockets.
# test/test_nemoclaw_sandbox.py - Scenario 4
def test_inter_agent_communication_stays_inside_sandbox(self):
    """A message can move between registered agents through the local mailbox."""
    sender = self.agent
    receiver = AgentProfile("receiver", "reviewer", frozenset({"inter_agent:receive"}))
    with Sandbox() as sandbox:
        sandbox.register_agent(sender)
        sandbox.register_agent(receiver)
        sent = sandbox.run(sender, "send_message", recipient="receiver", message="approved")
        received = sandbox.run(receiver, "receive_message")

    self.assertTrue(sent.permitted)
    self.assertEqual(received.value, "approved")
Enter fullscreen mode Exit fullscreen mode

Scenario 5: Failure Handling & Graceful Teardown (TC-05)

  • Confirms that when unsupported or illegal operations occur, structured errors are returned to the caller and the temporary workspace directory is unmounted and completely wiped upon teardown.
# test/test_nemoclaw_sandbox.py - Scenario 5
def test_failure_is_reported_and_teardown_is_graceful(self):
    """A denied operation returns a structured error and context is removed."""
    sandbox = Sandbox()
    sandbox.__enter__()
    sandbox.register_agent(self.agent)
    result = sandbox.run(self.agent, "unsupported_operation")
    root = sandbox.root
    sandbox.teardown()

    self.assertFalse(result.permitted)
    self.assertIn("not supported", result.error)
    self.assertTrue(sandbox.closed)
    self.assertFalse(root.exists())
    with self.assertRaises(RuntimeError):
        sandbox.root
Enter fullscreen mode Exit fullscreen mode

Implementation Blueprint & Execution

To run these verification checks directly in your environment, use the provided Python entry points:

Running the Full Test Suite

  • Running the tests;
  python3 -m unittest discover -s test -p 'test_nemoclaw_sandbox.py' -v
  test_escape_and_network_boundary_are_denied (test_nemoclaw_sandbox.TestNemoClawSandboxScenarios.test_escape_and_network_boundary_are_denied)
  Traversal and non-approved egress are blocked and audited. ... ok
  test_failure_is_reported_and_teardown_is_graceful (test_nemoclaw_sandbox.TestNemoClawSandboxScenarios.test_failure_is_reported_and_teardown_is_graceful)
  A denied operation returns a structured error and context is removed. ... ok
  test_inter_agent_communication_stays_inside_sandbox (test_nemoclaw_sandbox.TestNemoClawSandboxScenarios.test_inter_agent_communication_stays_inside_sandbox)
  A message can move between registered agents through the local mailbox. ... ok
  test_normal_in_sandbox_execution (test_nemoclaw_sandbox.TestNemoClawSandboxScenarios.test_normal_in_sandbox_execution)
  An authorized agent can write and read only its private workspace. ... ok
  test_resource_limits_are_enforced (test_nemoclaw_sandbox.TestNemoClawSandboxScenarios.test_resource_limits_are_enforced)
  Excess memory is rejected and long-running work is terminated. ... ok

  ----------------------------------------------------------------------
  Ran 5 tests in 0.114s
Enter fullscreen mode Exit fullscreen mode
  • Or... execute the 5 scenario assertions using standard Python unit testing tools:
# Run unit tests verbosely
python -m unittest test/test_nemoclaw_sandbox.py -v
Enter fullscreen mode Exit fullscreen mode
  • For automated integration pipelines, run the local harness script directly with the --json flag to inspect structured audit trails:
# Generate JSON audit report
python scripts/nemoclaw_sandbox_demo.py --json
Enter fullscreen mode Exit fullscreen mode

Example JSON Output:

  python3 scripts/nemoclaw_sandbox_demo.py --json
  {
    "results": [
      {
        "operation": "write_workspace",
        "permitted": true,
        "value": "workspace/result.txt",
        "error": null
      },
    {
      "operation": "read_workspace",
      "permitted": true,
      "value": "approved",
      "error": null
    },
    {
      "operation": "network_request",
      "permitted": true,
      "value": {
        "host": "inference.local",
        "status": "routed through inference.local"
      },
      "error": null
    },
    {
      "operation": "network_request",
      "permitted": false,
      "value": null,
      "error": "network host denied: example.com"
    },
    {
      "operation": "read_workspace",
      "permitted": false,
      "value": null,
      "error": "filesystem path denied: ../../etc/passwd"
    },
    {
      "operation": "busy_loop",
      "permitted": false,
      "value": null,
      "error": "operation exceeded 1.0s timeout"
    },
    {
      "operation": "allocate_memory",
      "permitted": false,
      "value": null,
      "error": "memory request 128 MiB exceeds limit 64 MiB"
    },
    {
      "operation": "send_message",
      "permitted": true,
      "value": "message delivered",
      "error": null
    },
    {
      "operation": "receive_message",
      "permitted": true,
      "value": "result ready",
      "error": null
    }
  ],
  "audit_events": 12,
  "sandbox_torn_down": true
}

Enter fullscreen mode Exit fullscreen mode

Troubleshooting & Production Best Practices

When transitioning from the local Python test harness to standard containerized deployments, refer to this common operational matrix:

Issue / Symptom Root Cause Resolution
filesystem path denied The requested path attempts directory traversal outside workspace/. MD Use strictly relative subpaths inside workspace/; do not weaken path validation. MD
network host denied Host is not present on the policy allowlist. MD Route requests through inference.local or explicitly add authorized host rules. MD
capability denied Agent profile lacks the required permission scope. MD Grant the minimum required capability to the AgentProfile scope. MD
operation exceeded timeout Task exceeded wall-clock deadline. MD Optimize worker routines or increase timeout_seconds in SandboxLimits with workload profiling evidence. MD
sandbox is not active Access attempted before context entry or post-teardown. MD Ensure work is wrapped within with Sandbox(...) context managers. MD

Summary Checklist for Deployment

  1. Least-Privilege Capabilities: Start agent profiles with zero capabilities and enable permissions on a strict need-to-have basis.

  2. Egress Boundary Lockdown: Deny external egress by default; route all model traffic through inference.local managed gateways.

  3. Secret Isolation: Never store or pass API keys inside prompts, agent workspace files, or sandbox environments.

  4. Guaranteed Teardown: Ensure sandbox contexts cleanly dispose of transient filesystems immediately upon execution completion.


Conclusion

Implementing NVIDIA NemoClaw with Bob demonstrates how modern AI agent orchestration can strike a practical balance between functional flexibility and strict security controls.

Key takeaways from this implementation:

  1. Zero-Trust Credentials inside Sandboxes: By routing agent API calls through inference.local, raw API keys never enter the sandbox environment.
  2. Defense-in-Depth Container Isolation: Combining --read-only filesystems, capability drops, tmpfs mounts, and network egress policies prevents agents from compromising host resources.
  3. Flexible Runtime Operations: Hot-swapping between cloud endpoints (NVIDIA NIM) and local runtimes (Ollama/llama.cpp) allows switching between local offline execution and production-grade models without altering agent business logic.

NemoClaw provides a dependable framework for safely running agentic workflows locally or in automated pipelines.

Thanks for reading 🦀

Links

Top comments (0)