Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
120511a
refactor(sandbox): default to mutable config, make shields opt-in
ericksoa Apr 22, 2026
2b5cf1c
merge: resolve conflicts with origin/main
ericksoa Apr 22, 2026
edfde69
fix(test): update regex boundary and stale Landlock references
ericksoa Apr 22, 2026
cdc7b42
fix(test): update exec-approvals test for simplified Dockerfile
ericksoa Apr 22, 2026
07f734b
chore: regenerate agent skills from updated docs
ericksoa Apr 22, 2026
d772472
Merge branch 'main' into remove-config-immutability
ericksoa Apr 22, 2026
900cd8d
chore: apply Prettier formatting to edited test files
ericksoa Apr 22, 2026
2eaa295
chore: apply Prettier to plugin files edited by subagent
ericksoa Apr 22, 2026
de1c1be
fix(test): flip config-hash and .openclaw write assertions to mutable…
ericksoa Apr 22, 2026
44edfc3
fix: address CodeRabbit review findings
ericksoa Apr 22, 2026
afadf4d
feat(migration): update Dockerfile.base and add legacy layout migration
ericksoa Apr 22, 2026
2f51dd3
fix(shields): treat missing state as mutable default, fix E2E assertions
ericksoa Apr 22, 2026
495c561
fix(test): replace fixed sleep with poll loop for shields auto-restore
ericksoa Apr 22, 2026
a9b92d8
merge: resolve conflicts with origin/main
ericksoa Apr 22, 2026
c865f82
fix(shields): lock/unlock .env and .config-hash alongside main config
ericksoa Apr 22, 2026
1ba4591
fix(test): auto-restore timer test was using shields up (no-op), fix …
ericksoa Apr 22, 2026
8579c08
fix(security): address NC-2227-01 through NC-2227-05 from security re…
ericksoa Apr 24, 2026
a9be78b
merge: resolve conflicts with origin/main
ericksoa Apr 24, 2026
9d14624
Merge branch 'main' into remove-config-immutability
ericksoa Apr 24, 2026
abd49db
merge(main): resolve conflicts with type-safety pass
ericksoa Apr 24, 2026
9a018ab
fix(deps): pin typescript to 6.0.2 to avoid template literal regressions
ericksoa Apr 24, 2026
a0bb985
fix(test): restore ts-nocheck for onboard test template literals
ericksoa Apr 24, 2026
5af51c2
Merge branch 'main' into remove-config-immutability
ericksoa Apr 24, 2026
05415af
merge(main): pick up strict typecheck fixes from #2458
ericksoa Apr 24, 2026
a19f4ba
chore(test): drop ts-nocheck workaround superseded by #2458
ericksoa Apr 24, 2026
1b9b9df
merge: resolve conflict with main in onboard-command usage line
ericksoa Apr 25, 2026
0da9772
fix(test): remove dangerouslySkipPermissions from --fresh test expect…
ericksoa Apr 25, 2026
01a79a9
Merge remote-tracking branch 'origin/main' into remove-config-immutab…
ericksoa Apr 29, 2026
ce0f62e
fix(ci): remove stale skip-permissions e2e refs
ericksoa Apr 29, 2026
6667788
Merge branch 'main' into remove-config-immutability
ericksoa Apr 29, 2026
fd3872e
fix(review): address coderabbit security feedback
ericksoa Apr 29, 2026
55c7b74
fix(ci): satisfy shellcheck in migration scripts
ericksoa Apr 29, 2026
937304b
fix(ci): keep entrypoint extraction sourceable
ericksoa Apr 29, 2026
3527a31
fix(review): harden legacy migration follow-ups
ericksoa Apr 29, 2026
536d338
Merge branch 'main' into remove-config-immutability
ericksoa Apr 29, 2026
dbcd2a7
fix(ci): remove stale nightly e2e dependency
ericksoa Apr 29, 2026
7c2f7e0
fix(review): close legacy openclaw-data bypass
ericksoa Apr 29, 2026
5ad7dd6
fix(ci): flatten legacy sandbox state layout
ericksoa Apr 29, 2026
915d864
fix(ci): update layout regression guards
ericksoa Apr 29, 2026
9affde4
Merge branch 'main' into remove-config-immutability
ericksoa Apr 29, 2026
bfab80f
fix(review): address remaining coderabbit findings
ericksoa Apr 29, 2026
c76d205
fix(e2e): harden legacy layout cleanup
ericksoa Apr 29, 2026
27a471d
fix(installer): preserve branch clone path
ericksoa Apr 29, 2026
c1513f2
Merge branch 'main' into remove-config-immutability
ericksoa Apr 29, 2026
f8ada3e
fix(e2e): repair mutable layout regressions
ericksoa Apr 29, 2026
57b4a22
fix(sandbox): address latest coderabbit feedback
ericksoa Apr 29, 2026
1a2a863
fix(sandbox): address coderabbit follow-ups
ericksoa Apr 29, 2026
c7d45b0
fix(sandbox): address follow-up review hardening
ericksoa Apr 29, 2026
e4bb4b3
Merge origin/main into remove-config-immutability
ericksoa Apr 29, 2026
22d1fe0
fix(sandbox): harden startup root writes
ericksoa Apr 29, 2026
5bdfd56
fix(sandbox): address startup hardening review
ericksoa Apr 30, 2026
d9c275a
Merge branch 'main' into remove-config-immutability
ericksoa Apr 30, 2026
13f9412
fix(sandbox): harden migration follow-ups
ericksoa Apr 30, 2026
4535c00
fix(snapshot): tolerate non-root restore ownership repair
ericksoa Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -207,24 +207,23 @@ The container mounts system directories read-only to prevent the agent from modi
| Risk if relaxed | Making `/usr` or `/lib` writable lets the agent replace system binaries (such as `curl` or `node`) with trojanized versions. Making `/etc` writable lets the agent modify DNS resolution, TLS trust stores, or user accounts. |
| Recommendation | Never make system paths writable. If the agent needs a writable location for generated files, use a subdirectory of `/sandbox`. |

### Read-Only `.openclaw` Config
### Agent Config Directory

The `/sandbox/.openclaw` directory contains the OpenClaw gateway configuration, including auth tokens and CORS settings.
The container mounts it read-only while writable agent state (plugins, agent data) lives in `/sandbox/.openclaw-data` through symlinks.
The `/sandbox/.openclaw` directory contains the OpenClaw gateway configuration (including auth tokens and CORS settings) and agent state (plugins, workspace, memory).

Multiple defense layers protect this directory:
By default, this directory starts writable so the agent can manage its own config, install skills, and write to standard home-directory paths natively.
Operators can opt into immutability by running `nemoclaw <name> shields up`, which applies `chmod 444 root:root` and `chattr +i` to lock the config until `shields down` restores the default writable state.

- **DAC permissions.** Root owns the directory and `openclaw.json` with `chmod 444`, so the sandbox user cannot write to them.
- **Immutable flag.** The entrypoint applies `chattr +i` to the directory and all symlinks, preventing modification even if other controls fail.
- **Symlink validation.** At startup, the entrypoint verifies every symlink in `.openclaw` points to the expected `.openclaw-data` target. If any symlink points elsewhere, the container refuses to start.
- **DAC permissions (default).** The sandbox user owns the directory and `openclaw.json` with `chmod 600`, so the agent can read and write config directly.
- **Config integrity hash.** The build process pins a SHA256 hash of `openclaw.json`. The entrypoint verifies it at startup and refuses to start if the hash does not match.
- **Shields UP (opt-in).** `nemoclaw <name> shields up` applies `chmod 444 root:root` and `chattr +i`, locking config until the operator runs `shields down`.

| Aspect | Detail |
|---|---|
| Default | The container mounts `/sandbox/.openclaw` as read-only, root-owned, immutable, and integrity-verified at startup. `/sandbox/.openclaw-data` remains writable. |
| What you can change | Move `/sandbox/.openclaw` from `read_only` to `read_write` in the policy file. |
| Risk if relaxed | A writable `.openclaw` directory lets the agent modify its own gateway config: disabling CORS, changing auth tokens, or redirecting inference to an attacker-controlled endpoint. This is the single most dangerous filesystem change. |
| Recommendation | Never make `/sandbox/.openclaw` writable. |
| Default | `/sandbox/.openclaw` is writable (`600 sandbox:sandbox`). The agent can read and write config, install skills, and manage state directly. |
| What you can change | Run `nemoclaw <name> shields up` to lock config with DAC permissions and the immutable flag. Run `shields down` to return to the writable default. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
| Risk of default | A writable `.openclaw` directory lets the agent modify its own gateway config: disabling CORS, changing auth tokens, or redirecting inference to an attacker-controlled endpoint. |
| Recommendation | For always-on assistants handling sensitive workloads, use `shields up` to lock config after initial setup. For development workflows, the writable default is appropriate. |

### Writable Paths

Expand Down Expand Up @@ -423,7 +422,7 @@ The scanner intercepts Write, Edit, and similar tool calls targeting memory and
| Aspect | Detail |
|---|---|
| Default | Enabled. The plugin registers a `before_tool_call` hook that scans for 14 high-confidence secret patterns. |
| What it covers | `.openclaw-data/memory/`, `workspace/`, `agents/`, `skills/`, `hooks/`, and `MEMORY.md`. |
| What it covers | `.openclaw/memory/`, `workspace/`, `agents/`, `skills/`, `hooks/`, and `MEMORY.md`. |
| What you can change | This is not a user-facing knob. The plugin enforces it automatically. |
| Risk if relaxed | Without scanning, the agent could persist API keys or tokens in memory files that survive across sessions and backups. |
| Recommendation | No action needed. If a write is blocked, the agent receives an actionable error listing the detected patterns. |
Expand Down Expand Up @@ -512,7 +511,7 @@ The following patterns weaken security without providing meaningful benefit.
| Omitting `protocol: rest` on REST API endpoints | Endpoints without a `protocol` field use L4-only enforcement. The proxy allows the TCP stream through after checking host, port, and binary, but cannot see or filter individual HTTP requests. | Add `protocol: rest` with explicit `rules` to enable per-request method and path control on REST APIs. |
| Adding endpoints to the baseline policy for one-off requests | Adding an endpoint to the baseline policy makes it permanently reachable across all sandbox instances. | Use operator approval. Approved endpoints persist within the sandbox instance but reset when you destroy and recreate the sandbox. |
| Relying solely on the entrypoint for capability drops | The entrypoint drops dangerous capabilities using `capsh`, but this is best-effort. If `capsh` is unavailable or `CAP_SETPCAP` is not in the bounding set, the container runs with the default capability set. | Pass `--cap-drop=ALL` at the container runtime level as defense-in-depth. |
| Granting write access to `/sandbox/.openclaw` | This directory contains the OpenClaw gateway configuration. A writable `.openclaw` lets the agent modify auth tokens, disable CORS, or redirect inference routing. | Store agent-writable state in `/sandbox/.openclaw-data`. |
| Leaving `/sandbox/.openclaw` writable on sensitive workloads | This directory contains the OpenClaw gateway configuration. A writable `.openclaw` lets the agent modify auth tokens, disable CORS, or redirect inference routing. | Run `nemoclaw <name> shields up` to lock config for always-on assistants handling sensitive data. |
| Adding inference provider hosts to the network policy | Direct network access to an inference host bypasses credential isolation and usage tracking. | Use OpenShell inference routing instead of adding hosts like `api.openai.com` or `api.anthropic.com` to the network policy. |
| Disabling device auth for remote deployments | Without device auth, any device on the network can connect to the gateway without pairing. Combined with a cloudflared tunnel, this makes the dashboard publicly accessible and unauthenticated. | Keep `NEMOCLAW_DISABLE_DEVICE_AUTH` at its default (`0`). Only set it to `1` for local headless or development environments. |

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/nemoclaw-user-deploy-remote/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ Group chats stay open by default so rebuilt sandboxes do not silently drop Teleg

Complete the rest of the wizard so the blueprint can create OpenShell providers (for example `<sandbox>-telegram-bridge`), bake channel configuration into the image (`NEMOCLAW_MESSAGING_CHANNELS_B64`), and start the sandbox.

Channel entries in `/sandbox/.openclaw/openclaw.json` are fixed at image build time. Landlock keeps that path read-only at runtime, so you cannot patch messaging config inside a running sandbox.
Channel entries in `/sandbox/.openclaw/openclaw.json` are baked into the container image at build time. Changes made inside the running sandbox do not persist across rebuilds.

If you add or change `TELEGRAM_BOT_TOKEN` (or toggle channels) after a sandbox already exists, you typically need to run `nemoclaw onboard` again so the image and provider attachments are rebuilt with the new settings.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,23 @@ services:
> capability dropping in your `docker run` flags, Compose file, or Kubernetes
> `securityContext`.

## Read-Only Home Directory
## Filesystem Layout

The sandbox Landlock policy restricts `/sandbox` (the agent's home directory) to read-only access.
Only explicitly declared directories are writable:
The sandbox Landlock policy declares which paths are writable.
The agent's home directory (`/sandbox`) is writable by default:

| Path | Access | Purpose |
|------|--------|---------|
| `/sandbox` | read-only | Home directory — agents cannot create arbitrary files |
| `/sandbox/.openclaw` | read-only | Immutable gateway config (auth tokens, CORS) |
| `/sandbox/.openclaw-data` | read-write | Agent state, workspace, plugins (via symlinks) |
| `/sandbox` | read-write | Home directory — agents can create files and use standard home paths |
| `/sandbox/.openclaw` | read-write | Agent config, state, workspace, plugins (lockable via `shields up`) |
| `/sandbox/.nemoclaw` | read-write | Plugin state and config; blueprints within are DAC-protected (root-owned) |
| `/tmp` | read-write | Temporary files and logs |

This prevents agents from:
System paths remain read-only to prevent agents from:

- Writing scripts and executing them later
- Modifying their own runtime environment
- Creating hidden files that persist across invocations
- Using writable space for data staging before exfiltration
- Replacing system binaries with trojanized versions
- Modifying DNS resolution or TLS trust stores
- Tampering with libraries or shell configuration outside `/sandbox`

The image build pre-creates shell init files `.bashrc` and `.profile`.
These files source runtime proxy configuration from `/tmp/nemoclaw-proxy-env.sh`.
Expand All @@ -91,7 +89,7 @@ Landlock LSM requires Linux kernel 5.13 or later with `CONFIG_SECURITY_LANDLOCK=
The NemoClaw sandbox policy uses `compatibility: best_effort`, which means Landlock enforcement is silently skipped on kernels that do not support it.

On such kernels, protection falls back to DAC (file ownership and permissions) only.
Files owned by the sandbox user (e.g., `.bashrc`, `.profile`) would be writable by the agent despite the Landlock read-only policy.
Files outside the writable paths would be inaccessible to the agent regardless of DAC permissions.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Operators should verify Landlock availability:

Expand All @@ -104,7 +102,7 @@ The `test/e2e/e2e-cloud-experimental/checks/04-landlock-readonly.sh` script vali

## References

- [#804](https://github.com/NVIDIA/NemoClaw/issues/804): Read-only home directory
- [#804](https://github.com/NVIDIA/NemoClaw/issues/804): Filesystem layout and Landlock policy
- [#807](https://github.com/NVIDIA/NemoClaw/issues/807): gcc in sandbox image
- [#808](https://github.com/NVIDIA/NemoClaw/issues/808): netcat in sandbox image
- [#809](https://github.com/NVIDIA/NemoClaw/issues/809): No process limit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The following table compares the two paths.
| Sandbox isolation | Yes. OpenShell applies seccomp filters, Landlock filesystem restrictions, privilege dropping, network namespace isolation, and no-new-privileges enforcement. The community sandbox bundles its own policy tailored for OpenClaw. | Yes. NemoClaw applies these through the blueprint and layers a more restrictive policy on top (see rows below). |
| Credential handling | OpenShell's provider system replaces real credentials with placeholder tokens in the sandbox environment. The L7 proxy resolves placeholders to real values at egress. You create providers manually with `openshell provider create`. | NemoClaw creates OpenShell providers automatically during onboarding. It also filters sensitive host environment variables (provider API keys, `DISCORD_BOT_TOKEN`, `SLACK_BOT_TOKEN`, `TELEGRAM_BOT_TOKEN`) from the sandbox creation command to prevent accidental leakage through build args. |
| Image hardening | The community image includes standard system tools for general-purpose use. | NemoClaw strips build toolchains (`gcc`, `g++`, `make`) and network probes (`netcat`) from the runtime image to reduce attack surface. |
| Filesystem policy | The community sandbox bundles a policy for OpenClaw. | NemoClaw defines a more restrictive read-only and read-write layout. The agent's home directory (`/sandbox`) is read-only. Gateway config (`/sandbox/.openclaw`) is immutable. Only specific data paths (`/sandbox/.openclaw-data`, `/sandbox/.nemoclaw`, `/tmp`) are writable. This prevents the agent from writing and executing scripts, modifying its own environment, or staging data for exfiltration. |
| Filesystem policy | The community sandbox bundles a policy for OpenClaw. | NemoClaw defines a targeted read-only and read-write layout. System paths (`/usr`, `/lib`, `/etc`) are read-only. The agent's home directory (`/sandbox`) and config directory (`/sandbox/.openclaw`) are writable by default so the agent can manage config, install skills, and write to standard paths natively. Operators can opt into config immutability with `nemoclaw <name> shields up`. |
| Inference setup | The community sandbox includes an `openclaw-start` script that runs OpenClaw's onboarding wizard inside the sandbox. You can also create providers and configure OpenShell inference routing manually from the host. | NemoClaw's onboarding wizard validates your credential from the host, lets you select a provider (NVIDIA Endpoints, OpenAI, Anthropic, Google Gemini, Ollama, and compatible endpoints), and configures OpenShell's inference routing automatically. Credentials stay on the host and are delivered through OpenShell's provider system. |
| Channel messaging | OpenShell provides the credential provider system and L7 proxy that delivers channel tokens securely (including path-based resolution for Telegram's `/bot<token>/` URL pattern). You create providers and configure OpenClaw's channel settings manually. | NemoClaw automates channel setup during onboarding: it collects bot tokens, registers them as OpenShell providers, and bakes OpenClaw channel config with placeholder tokens that OpenShell's proxy resolves at egress. No separate bridge process runs on the host. |
| Blueprint versioning | No blueprint. The community sandbox uses whatever image version is currently published. | NemoClaw downloads the blueprint artifact, checks version compatibility, and verifies its digest before applying. Running `nemoclaw onboard` on different machines produces the same sandbox. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ The sandbox starts with a default policy that controls network egress, filesyste
| Layer | What it protects | When it applies |
|---|---|---|
| Network | Blocks unauthorized outbound connections. | Hot-reloadable at runtime. |
| Filesystem | Prevents reads and writes outside `/sandbox` and `/tmp`. | Locked at sandbox creation. |
| Filesystem | Restricts system paths to read-only; `/sandbox` and `/tmp` are writable. | Locked at sandbox creation. |
| Process | Blocks privilege escalation and dangerous syscalls. | Locked at sandbox creation. |
| Inference | Reroutes model API calls to controlled backends. | Hot-reloadable at runtime. |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ $ nemoclaw my-assistant channels remove telegram

As with `channels add`, `NEMOCLAW_NON_INTERACTIVE=1` skips the rebuild prompt and queues the change for a manual `nemoclaw <name> rebuild`.

Host-side removal is the supported path because `/sandbox/.openclaw/openclaw.json` is read-only at runtime; `openclaw channels remove` cannot modify the baked config from inside the sandbox.
Host-side removal is the supported path because `/sandbox/.openclaw/openclaw.json` is baked into the container image at build time; `openclaw channels remove` inside the sandbox would modify the running config but not persist changes across rebuilds.

### `nemoclaw <name> skill install <path>`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ For the security trade-offs, refer to Security Best Practices (use the `nemoclaw

This is expected.
The messaging channel list is frozen into the sandbox's container image when the image is built during `nemoclaw onboard` or `nemoclaw rebuild` (the selected channel names are passed to the `docker build` as `NEMOCLAW_MESSAGING_CHANNELS_B64` and written into `/sandbox/.openclaw/openclaw.json` as part of the image).
At runtime the sandbox mounts that path read-only and layers Landlock + filesystem hardening on top, so `openclaw channels` commands that mutate the config cannot write there.
Changes made inside the running sandbox do not persist across rebuilds, so `openclaw channels` commands that mutate the config are intercepted.
NemoClaw's sandbox entrypoint installs a guard that intercepts `openclaw channels <add|remove>` and prints an actionable error pointing at the host-side commands below, instead of letting the call fail deep in the binary with a raw `EACCES` trace.

Run the equivalent host-side command instead:
Expand All @@ -445,8 +445,8 @@ In non-interactive mode (`NEMOCLAW_NON_INTERACTIVE=1`), the commands stage the c
### `openclaw config set` or `unset` is blocked inside the sandbox

This is expected.
The sandbox's OpenClaw configuration (`/sandbox/.openclaw/openclaw.json`) is baked into the container image at build time and mounted read-only at runtime.
NemoClaw's sandbox entrypoint installs a guard that intercepts `openclaw config set` and `openclaw config unset` and prints an actionable error instead of letting the call fail with a raw permission error.
The sandbox's OpenClaw configuration (`/sandbox/.openclaw/openclaw.json`) is baked into the container image at build time.
NemoClaw's sandbox entrypoint installs a guard that intercepts `openclaw config set` and `openclaw config unset` and prints an actionable error, because changes made inside the running sandbox do not persist across rebuilds.

Rebuild the sandbox from the host to change its OpenClaw configuration:

Expand All @@ -457,7 +457,7 @@ $ nemoclaw <sandbox> rebuild
### `openclaw doctor --fix` cannot repair Discord channel config inside the sandbox

This is expected in NemoClaw-managed sandboxes.
NemoClaw bakes channel entries into `/sandbox/.openclaw/openclaw.json` at image build time, and OpenShell keeps that path read-only at runtime.
NemoClaw bakes channel entries into `/sandbox/.openclaw/openclaw.json` at image build time.

As a result, commands that try to rewrite the baked config from inside the sandbox, including `openclaw doctor --fix`, cannot repair Discord, Telegram, or Slack channel entries in place.

Expand Down
Loading
Loading