feat(sandbox-mgmt): experimental shields and config commands#1976
Conversation
Add shields down/up/status and config get/set/rotate-token for sandbox management. These commands are experimental — interfaces may change and no documentation is published. The feature is exercised by a new E2E test (test/e2e/test-shields-config.sh) but is not formally supported. Shields: - Time-bounded policy relaxation with automatic restore timer - State namespaced per sandbox (shields-<name>.json) - Rollback on timer fork failure (never leaves sandbox unguarded) - Timer checks policy restore result; reverts state on failure - Audit trail (shields-audit.jsonl) with credential redaction Config: - config get: read-only inspection with credential stripping - config set: host-initiated mutation with SSRF validation - config rotate-token: credential rotation via env, stdin, or prompt - All commands use --name flag for openshell sandbox exec Security: - Endpoint URL redaction in config-show (plugin slash command) - Audit reason/error fields redacted via runner.ts patterns - shields down flag validation (reject missing values, unknown flags) Testing: - E2E: full shields lifecycle, config CRUD, timer auto-restore - Unit: duration parsing, state management, audit format, URL redaction - Plugin branch coverage 86.28% (above 86% ratchet) Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds host-side shields and sandbox-config features: persisted shields state fields with backfill, detached auto-restore timer, JSONL audit logging, policy snapshot/restore and config permission toggles, duration and sandbox-config utilities, CLI wiring for Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as Host CLI
participant State as Local State File
participant Policy as Policy Engine
participant Timer as Auto-Restore Timer
participant Audit as Audit Log
CLI->>State: read state (merge blankState() with persisted)
State-->>CLI: current shields state
CLI->>Policy: capture current policy (snapshot)
Policy-->>CLI: policy YAML
CLI->>State: persist snapshot path & shieldsDown metadata
State-->>CLI: persisted
CLI->>Policy: apply relaxed policy (permissive or provided)
Policy-->>CLI: applied
CLI->>Timer: fork detached timer with restoreAt
Timer-->>CLI: started (PID marker)
CLI->>Audit: append shields_down entry
Audit-->>CLI: appended
Note over Timer: waits until restoreAt
Timer->>State: read snapshot path & state
State-->>Timer: state data
Timer->>Policy: verify snapshot exists
Policy-->>Timer: snapshot OK
Timer->>Policy: restore policy from snapshot
Policy-->>Timer: restored
Timer->>State: clear shieldsDown fields
State-->>Timer: persisted
Timer->>Audit: append shields_auto_restore entry
Audit-->>Timer: appended
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml (1)
9-9:⚠️ Potential issue | 🟡 MinorStale comment:
include_workdir: trueno longer matches actual value.Line 9 states "Filesystem: include_workdir: true makes the sandbox home directory writable" but the actual value on line 21 is now
false. This comment should be updated to reflect the current behavior.📝 Suggested fix
-# Filesystem: include_workdir: true makes the sandbox home directory writable. +# Filesystem: include_workdir must be false (see openclaw-sandbox.yaml for rationale). +# Config mutability is handled by shields down/up via kubectl exec (bypasses Landlock).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml` at line 9, Update the stale inline comment describing Filesystem behavior so it matches the current setting of include_workdir (which is false); locate the comment that reads "Filesystem: include_workdir: true makes the sandbox home directory writable" and change it to state that include_workdir: false makes the sandbox home directory read-only (or otherwise describe the current behavior), ensuring the comment and the include_workdir setting in openclaw-sandbox-permissive.yaml are consistent.nemoclaw/src/commands/slash.test.ts (1)
64-81:⚠️ Potential issue | 🟡 MinorTest fixture
blankState()is missinglastRebuildAtandlastRebuildBackupPathfields.The
NemoClawStateinterface (per context snippet fromnemoclaw/src/blueprint/state.ts) includeslastRebuildAtandlastRebuildBackupPath, but this test fixture omits them. This could cause type mismatches or incomplete state mocking.💡 Proposed fix
function blankState(): NemoClawState { return { lastRunId: null, lastAction: null, blueprintVersion: null, sandboxName: null, migrationSnapshot: null, hostBackupPath: null, createdAt: null, updatedAt: new Date().toISOString(), + lastRebuildAt: null, + lastRebuildBackupPath: null, shieldsDown: false, shieldsDownAt: null, shieldsDownTimeout: null, shieldsDownReason: null, shieldsDownPolicy: null, shieldsPolicySnapshotPath: null, }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nemoclaw/src/commands/slash.test.ts` around lines 64 - 81, The test fixture blankState() returns an incomplete NemoClawState; add the missing fields lastRebuildAt and lastRebuildBackupPath to the returned object so the shape matches the NemoClawState interface: include lastRebuildAt: null (or ISO string when appropriate) and lastRebuildBackupPath: null (or an empty string if your code expects a string) alongside the other properties in the blankState() function to prevent type mismatches in tests.
🧹 Nitpick comments (3)
agents/hermes/policy-permissive.yaml (1)
14-17: Soften the “must match exactly” claim to avoid policy drift confusion.Line 14 says this section must match
policy-additions.yamlexactly, but this file also includes an extra write path at Line 33 (/sandbox/.nemoclaw). That wording can mislead future edits.Proposed wording tweak
- # Filesystem section MUST match the default Hermes policy (policy-additions.yaml) - # exactly — OpenShell rejects include_workdir changes and read_only path + # Filesystem section should mirror the default Hermes policy + # (policy-additions.yaml), except explicitly documented NemoClaw state paths. + # OpenShell rejects include_workdir changes and read_only path # removals on live sandboxes. Config file mutability is handled separately # by shields down/up via kubectl exec (bypasses Landlock).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@agents/hermes/policy-permissive.yaml` around lines 14 - 17, Update the comment that currently reads "Filesystem section MUST match the default Hermes policy (policy-additions.yaml) exactly" to a softer phrasing that conveys intent without implying absolute exactness—e.g., "Filesystem section should generally match the default Hermes policy (policy-additions.yaml); avoid changing include_workdir or removing read_only paths on live sandboxes." Ensure the new text also calls out the known exception (/sandbox/.nemoclaw write path) so future editors aren't misled, and retain the warning about using kubectl exec for config mutability and Landlock-related restrictions.src/lib/sandbox-config.ts (1)
504-512: Consider:readStdinhas no timeout.If stdin never closes (e.g., piped from a process that hangs), this promise will never resolve. For interactive CLI usage this is acceptable, but if
--from-stdinis used in automated pipelines, a timeout might be valuable.This is a minor robustness consideration for future hardening, not blocking.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/sandbox-config.ts` around lines 504 - 512, readStdin currently can hang indefinitely; update the readStdin function to accept an optional timeoutMillis (or use a sensible default) and implement a Promise.race between the stdin-reading promise and a timeout promise; when the timeout fires, reject with a descriptive error, and ensure you remove stdin listeners and clear any timers on both success and timeout to avoid leaks (refer to the readStdin function name and its "data"/"end"/"error" listeners when making these changes).test/e2e/test-shields-config.sh (1)
466-481: Auto-restore test has a potential race condition.The test waits 15 seconds for a 10-second timeout, which should be sufficient. However, if the timer process is delayed (e.g., slow CI runner, policy set --wait taking longer), the test may flap. The fallback at line 479 (
nemoclaw shields up || true) is good for cleanup, but consider increasing the sleep margin or adding a retry loop.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/e2e/test-shields-config.sh` around lines 466 - 481, The auto-restore check using STATUS_AFTER_TIMER and the command `nemoclaw "${SANDBOX_NAME}" shields status` can flake because it sleeps a fixed 15s for a 10s timer; modify the test to avoid the race by replacing the single sleep+check with a short retry loop: after the initial sleep, poll `nemoclaw "${SANDBOX_NAME}" shields status` up to a configurable timeout (e.g., 30s total) with small intervals and break when output contains "Shields: UP"; keep the existing cleanup fallback using `nemoclaw "${SANDBOX_NAME}" shields up || true` and ensure any failure still calls `fail "Auto-restore timer did not restore shields within Xs"` with the total wait reflected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@nemoclaw/src/commands/config-show.ts`:
- Around line 32-35: The current rendering of credentialEnv in the config-show
command can echo unsafe or empty values; update the logic in the config-show
handler (where redactedCredential and lastFour are computed) to only render
`$NAME` when config.credentialEnv matches a strict env-var name regexp (e.g.
/^[A-Z0-9_]+$/); if it does not match or is empty, set redactedCredential to a
generic label like "(configured via env var)" or "(not configured)" and adjust
lastFour accordingly so raw tokens or malformed strings from loadOnboardConfig()
are never printed.
In `@nemoclaw/src/commands/shields-status.test.ts`:
- Around line 16-33: The blankState() test fixture returns an incomplete
NemoClawState missing the lastRebuildAt and lastRebuildBackupPath fields; update
blankState() to include these two properties (initialize them similarly to other
timestamp/backup fields—e.g., null) so the fixture matches the full
NemoClawState shape used in tests like slash.test.ts and avoids type/coverage
gaps when using lastRebuildAt or lastRebuildBackupPath in tests.
In `@src/lib/shields-timer.ts`:
- Around line 114-125: The catch block that calls
appendAudit("shields_up_failed", ...) currently lets execution fall into finally
which unconditionally calls process.exit(0), masking failures; modify
shields-timer.ts so the process exits with a non-zero status when an exception
occurs—e.g. set a local flag or call process.exit(1) from the catch (or set
process.exitCode = 1) and ensure the finally block uses that code or only
performs cleanup via cleanupMarker() without forcing exit(0); update the catch
handling around appendAudit(...) and the finally that calls cleanupMarker() /
process.exit(...) so errors produce a non-zero exit (use the function/variable
names appendAudit, cleanupMarker, and process.exit/process.exitCode to locate
changes).
In `@test/config-set.test.ts`:
- Around line 88-90: The test description string "accepts public http URLs" in
the it(...) block does not match the URL passed to validateUrlValue (currently
"https://example.com"); update the test so the description and input agree:
either change the description to "accepts public https URLs" or modify the URL
to "http://example.com" to match "http" — adjust the it(...) description or the
validateUrlValue argument accordingly.
---
Outside diff comments:
In `@nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml`:
- Line 9: Update the stale inline comment describing Filesystem behavior so it
matches the current setting of include_workdir (which is false); locate the
comment that reads "Filesystem: include_workdir: true makes the sandbox home
directory writable" and change it to state that include_workdir: false makes the
sandbox home directory read-only (or otherwise describe the current behavior),
ensuring the comment and the include_workdir setting in
openclaw-sandbox-permissive.yaml are consistent.
In `@nemoclaw/src/commands/slash.test.ts`:
- Around line 64-81: The test fixture blankState() returns an incomplete
NemoClawState; add the missing fields lastRebuildAt and lastRebuildBackupPath to
the returned object so the shape matches the NemoClawState interface: include
lastRebuildAt: null (or ISO string when appropriate) and lastRebuildBackupPath:
null (or an empty string if your code expects a string) alongside the other
properties in the blankState() function to prevent type mismatches in tests.
---
Nitpick comments:
In `@agents/hermes/policy-permissive.yaml`:
- Around line 14-17: Update the comment that currently reads "Filesystem section
MUST match the default Hermes policy (policy-additions.yaml) exactly" to a
softer phrasing that conveys intent without implying absolute exactness—e.g.,
"Filesystem section should generally match the default Hermes policy
(policy-additions.yaml); avoid changing include_workdir or removing read_only
paths on live sandboxes." Ensure the new text also calls out the known exception
(/sandbox/.nemoclaw write path) so future editors aren't misled, and retain the
warning about using kubectl exec for config mutability and Landlock-related
restrictions.
In `@src/lib/sandbox-config.ts`:
- Around line 504-512: readStdin currently can hang indefinitely; update the
readStdin function to accept an optional timeoutMillis (or use a sensible
default) and implement a Promise.race between the stdin-reading promise and a
timeout promise; when the timeout fires, reject with a descriptive error, and
ensure you remove stdin listeners and clear any timers on both success and
timeout to avoid leaks (refer to the readStdin function name and its
"data"/"end"/"error" listeners when making these changes).
In `@test/e2e/test-shields-config.sh`:
- Around line 466-481: The auto-restore check using STATUS_AFTER_TIMER and the
command `nemoclaw "${SANDBOX_NAME}" shields status` can flake because it sleeps
a fixed 15s for a 10s timer; modify the test to avoid the race by replacing the
single sleep+check with a short retry loop: after the initial sleep, poll
`nemoclaw "${SANDBOX_NAME}" shields status` up to a configurable timeout (e.g.,
30s total) with small intervals and break when output contains "Shields: UP";
keep the existing cleanup fallback using `nemoclaw "${SANDBOX_NAME}" shields up
|| true` and ensure any failure still calls `fail "Auto-restore timer did not
restore shields within Xs"` with the total wait reflected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ff620ec6-3c66-4952-920e-6950e758f7dd
📒 Files selected for processing (25)
.github/workflows/nightly-e2e.yamlagents/hermes/policy-permissive.yamlnemoclaw-blueprint/policies/openclaw-sandbox-permissive.yamlnemoclaw/src/blueprint/state.test.tsnemoclaw/src/blueprint/state.tsnemoclaw/src/commands/config-show.test.tsnemoclaw/src/commands/config-show.tsnemoclaw/src/commands/shields-status.test.tsnemoclaw/src/commands/shields-status.tsnemoclaw/src/commands/slash.test.tsnemoclaw/src/commands/slash.tsnemoclaw/src/onboard/config.test.tsnemoclaw/src/onboard/config.tssrc/lib/duration.tssrc/lib/sandbox-config.tssrc/lib/shields-audit.tssrc/lib/shields-timer.tssrc/lib/shields.tssrc/nemoclaw.tstest/config-rotate-token.test.tstest/config-set.test.tstest/duration.test.tstest/e2e/test-shields-config.shtest/shields-audit.test.tstest/shields.test.ts
…e, stale-registry gate
- Validate credentialEnv matches safe env-var pattern before echoing in
config-show (prevents leaking malformed persisted data)
- Exit with status 1 in shields-timer catch block instead of falling
through to finally { process.exit(0) }
- Add "shields" and "config" to the stale-registry recovery gate so
they work when the local registry is out of date
- Fix test description/URL mismatch in config-set test
- Add missing lastRebuildAt/lastRebuildBackupPath to blankState() test
fixtures in shields-status.test.ts and slash.test.ts
- Fix stale comments in permissive policy YAML files
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
…ds-config) Both branches added new E2E jobs to the notify-on-failure needs list — keep both token-rotation-e2e (main) and shields-config-e2e (this branch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refresh user-facing docs against the 34 commits merged between v0.0.17 and v0.0.18. Highlights: - Replace the Ollama 0.0.0.0 binding guidance with the new authenticated reverse proxy on 127.0.0.1:11435 (#1922). - Document the compatible-endpoint provider defaulting to /v1/chat/completions and the NEMOCLAW_PREFERRED_API=openai-responses opt-in (#1984). - Add the new nemoclaw upgrade-sandboxes command with --check, --auto, and --yes flags (#1943). - Note the cross-sandbox messaging overlap warning and 409 detection in nemoclaw <name> status (#1953). - Document the messaging-token rotation auto-rebuild flow (#1967). - Cover new troubleshooting entries for the Ollama auth proxy, IPv6 localhost resolution, orphan SSH port-forward cleanup on re-onboard, and rotated messaging credentials (#1978, #1950). - Note tar failure exit code for nemoclaw debug --output (#1770) and the orphaned openshell process cleanup in nemoclaw uninstall (#1940). Also: - Extend docs/.docs-skip to exclude the experimental sandbox-mgmt shields and config commands (#1976). - Fix a sphinx-autobuild infinite rebuild loop in docs/conf.py by writing docs/project.json only when its contents change. - Bump the docs version switcher preferred entry to 0.0.18. - Regenerate nemoclaw-user-* agent skills from docs/. Signed-off-by: Miyoung Choi <miyoungc@nvidia.com> Made-with: Cursor
## Summary Refresh user-facing documentation against the 34 commits merged between v0.0.17 and v0.0.18, bump the docs version switcher to v0.0.18, and fix a `sphinx-autobuild` infinite-rebuild loop triggered by `docs/conf.py`. ## Changes - **Ollama authenticated reverse proxy** (#1922): Replace the `0.0.0.0:11434` guidance in `docs/inference/use-local-inference.md` with the new token-gated proxy on `127.0.0.1:11435`, including persisted token, health-check exemption, and sandbox provider wiring. Replace the matching troubleshooting entry in `docs/reference/troubleshooting.md`. - **Compatible-endpoint default API path** (#1984): Document that the compatible-endpoint provider now defaults to `/v1/chat/completions` and update `NEMOCLAW_PREFERRED_API` to describe `openai-responses` as the opt-in instead of `openai-completions`. Updates in `use-local-inference.md`, `switch-inference-providers.md`, and `troubleshooting.md`. - **`nemoclaw upgrade-sandboxes` command** (#1943): Add a new reference entry in `docs/reference/commands.md` covering `--check`, `--auto`, and `--yes` flags. - **Messaging token rotation auto-rebuild** (#1967, #1953): Note the automatic rebuild behavior and cross-sandbox overlap warning in `docs/deployment/set-up-telegram-bridge.md`, `commands.md`, and `troubleshooting.md`. - **Other troubleshooting additions**: - `localhost` → `127.0.0.1` IPv6 note (#1978) - Orphan SSH port-forward cleanup on re-onboard (#1950) - Orphan `openshell` process cleanup in `nemoclaw uninstall` (#1940) - Non-zero exit on tar failure in `nemoclaw debug --output` (#1770) - **Skip list**: Extend `docs/.docs-skip` to exclude the experimental sandbox-mgmt shields and config commands feature (#1976), which was explicitly merged as not-yet-documented. - **Build stability**: `docs/conf.py` now writes `docs/project.json` only when contents change, so `make docs-live` / `sphinx-autobuild` no longer detects its own generated file as a source change and enters an infinite rebuild loop. - **Version switcher**: Bump `docs/versions1.json` and `docs/project.json` preferred entry to v0.0.18 so this refresh renders under the new version. - **Agent skills**: Regenerate `nemoclaw-user-*` skills from `docs/` with `scripts/docs-to-skills.py`. ## Type of Change - [ ] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [x] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Verification - [x] `npx prek run --all-files` passes (ran via pre-commit hook on staged files) - [ ] `npm test` passes - [ ] Tests added or updated for new or changed behavior - [x] No secrets, API keys, or credentials committed - [x] Docs updated for user-facing behavior changes - [x] `make docs` builds without warnings (doc changes only) - [x] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) ## AI Disclosure - [x] AI-assisted — tool: Cursor --- Signed-off-by: Miyoung Choi <miyoungc@nvidia.com> Made with [Cursor](https://cursor.com) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added `nemoclaw upgrade-sandboxes` command to rebuild sandboxes when base-image digests change. * Introduced authenticated reverse proxy for local Ollama inference with token-based access control. * Automatic sandbox backup, recreation, and restore when messaging credentials are updated. * Cross-sandbox messaging token overlap detection with status warnings. * **Improvements** * Compatible-endpoint provider now defaults to `/v1/chat/completions` API path. * Enhanced troubleshooting documentation with new diagnostics sections. * **Documentation** * Updated onboarding and configuration guides. * Expanded version documentation to 0.0.18. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Miyoung Choi <miyoungc@nvidia.com>
Summary
Experimental sandbox management commands for shields and config operations. This feature is not formally documented or advertised — it is gated behind the same experimental-feature conventions used elsewhere in the codebase.
shields down/up/status— time-bounded policy relaxation with automatic restoreconfig get/set/rotate-token— host-initiated config inspection and mutationShields
shields-<name>.json)fork()with explicit IPC disconnectConfig
config get: read-only with credential stripping and gateway section removalconfig set: SSRF validation, gateway write blocking, kubectl exec for Landlock bypassconfig rotate-token: env var, stdin, or interactive promptopenshell sandbox execcalls use--nameflag (not positional)Security hardening (CodeRabbit review)
/nemoclaw configslash commandredact()before writingshields downrejects missing flag values and unknown flagsparseCurrentPolicy()strips metadata header from policy snapshotsTest plan
test/e2e/test-shields-config.sh— full 12-phase lifecycle (37/37 pass on CI)Supersedes #1849.
Summary by CodeRabbit
New Features
shields down|up|statuswith timeout, reason, policy snapshot, audit logging, auto-restore, and CLI + slash command/status outputs.config get|set|rotate-tokenwith redacted displays, validation, and optional in-sandbox apply.Tests
Chores