fix(sandbox): preserve custom policy presets across snapshot restore#5104
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (2)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughBackup now captures sandbox ChangesCustom Policy Snapshot Capture and Restore
Sequence DiagramsequenceDiagram
participant SnapshotRestore
participant RebuildManifest
participant Registry
participant PolicyEngine
SnapshotRestore->>RebuildManifest: read resolvedSnapshot.customPolicies
SnapshotRestore->>Registry: list current customPolicies by name
SnapshotRestore->>Registry: remove policy(name) for each extra
SnapshotRestore->>PolicyEngine: policies.applyPresetContent(content, { custom: { sourcePath } })
PolicyEngine-->>Registry: persist applied custom policy entry
Registry-->>SnapshotRestore: return removal/apply results (accumulate warnings)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/sandbox/snapshot.ts`:
- Around line 604-609: The current reconciliation only diffs snapshotCustom vs
currentCustom by name (variables toRemove/toAdd), so policies with the same name
but different content won’t be updated; change the comparison to detect content
differences (e.g., deep equality or a deterministic JSON/string hash of the
policy body) and treat a name-matching but content-different entry as needing
replacement: include it in toRemove and toAdd (or call an update path). Update
the logic that builds toRemove/toAdd (references: snapshotCustom, currentCustom,
toRemove, toAdd) to compare full policy content (not just .name) so restore
re-applies snapshot policy bodies; use a canonical serializer or deepEqual
helper to avoid false positives from key ordering.
In `@src/lib/state/sandbox.ts`:
- Around line 1011-1013: The code currently omits the customPolicies field when
sb.customPolicies is empty, causing snapshots with zero policies to be
indistinguishable from legacy snapshots and preventing reconciliation; change
the initialization of customPolicies (the CustomPolicyEntry[] assigned from sb)
to always persist the property even when empty (i.e., set customPolicies to an
empty array when sb.customPolicies is undefined/null or length 0) so restore
logic can detect an explicit empty list; update the declaration that reads from
sb (and any similar occurrences) and keep the _log call as-is to reflect the
empty array.
🪄 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: Enterprise
Run ID: 45b2d990-094c-43f2-ac4a-ddc43f9e6ecf
📒 Files selected for processing (3)
src/lib/actions/sandbox/snapshot.tssrc/lib/state/sandbox.tstest/snapshot.test.ts
70fd6f6 to
a6f7e14
Compare
prekshivyas
left a comment
There was a problem hiding this comment.
Reviewed (code + security checklist). Captures custom --from-file/--from-dir policy presets (name + content) into the rebuild manifest and reconciles them on snapshot restore, mirroring the existing built-in policyPresets path. Fixes #5103.
✅ Approve. Verified the load-bearing pieces against source rather than the diff:
CustomPolicyEntry+getCustomPolicies()(state/registry.ts),applyPresetContent(..., {custom})+removePreset(policy/index.ts) exist and match the call sites; the manifest validator (isCustomPolicyEntryArray) is wired and rejects entries missingcontent.- The reconcile is full-replacement — it restores exactly the snapshotted presets and removes any not in the snapshot, so it can't grant egress beyond what was already captured. It also fixes a latent posture bug where custom policy narrowing silently vanished on restore. Security: all 9 categories pass (name-only logging, no content leakage; manifests are local, same trust boundary as the snapshot).
Non-blocking notes:
- Same-name / different-content presets aren't reconciled (the add/remove keys on
nameonly) — so an edited-in-place custom preset keeps the live content rather than restoring the snapshot's. This matches the built-in reconcile's name-only semantics (built-ins can't diverge, custom content can), so it's a known limitation, not a regression. A one-line code comment noting the gap would help; acontent !==re-apply would close it if you want. - PR body says the capture is in
createBackup; the function is actuallybackupSandboxState. - The custom reconcile block closely duplicates the built-in one — extractable, but they diverge on apply-by-name vs apply-by-content, so duplication is tolerable.
Tests adequate (manifest round-trip + malformed-entry rejection); an explicit same-name-different-content test would document the gap above.
snapshot create/restore reconciled built-in policy presets (policyPresets) but dropped custom presets applied via `policy --from-file`/`--from-dir`. Custom presets live only in the gateway policy engine, not on the sandbox filesystem, so restore (including `--to <dst>`) silently lost them: the restored sandbox kept its built-in presets but none of the custom ones. Capture customPolicies (each CustomPolicyEntry carries full content) into the rebuild manifest and reconcile them on restore the same way policyPresets are: re-apply by content via applyPresetContent (which re-records them in the target registry) and remove any custom presets the snapshot did not record. Reconcile by content, not only by name: a same-name preset whose body or source path changed is re-applied. Always persist customPolicies in the manifest (even an empty array) so restore can tell a zero-custom snapshot (reconcile, removing stale custom presets on the target) from a legacy snapshot that predates the field (skip reconcile). Closes NVIDIA#5103 Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
a6f7e14 to
ceddddc
Compare
## Summary - Add v0.0.64 release notes from the release announcement and link them to the relevant deeper docs. - Document that custom policy presets recorded through `policy-add --from-file` and `--from-dir` survive snapshot restore and sandbox recreation. - Refresh generated NemoClaw user skills from the current source docs. ## Source summary - #5104 -> `docs/manage-sandboxes/backup-restore.mdx`, `docs/network-policy/customize-network-policy.mdx`: Documents custom policy presets preserved through snapshot restore. - #4955 -> `docs/about/release-notes.mdx`: Adds release-note coverage for Brave web-search pinning and `BRAVE_API_KEY` placeholder preservation. - #5116, #5269 -> `docs/about/release-notes.mdx`: Adds release-note coverage for Docker-driver gateway health and rootfs guard stability. - #5241, #5085 -> `docs/about/release-notes.mdx`: Adds release-note coverage for chat-completions provider selection and Nemotron Ultra 550B tool-less request compatibility. - #5268, #5210, #5257 -> `docs/about/release-notes.mdx`: Adds release-note coverage for messaging render plan refresh, OpenClaw scope-upgrade approval recovery, and Hermes WhatsApp bridge dependency setup. - Current source docs -> `.agents/skills/`: Regenerates user-skill references so agent-facing guidance matches the source documentation. ## Verification - `python3 scripts/docs-to-skills.py docs/ .agents/skills/ --prefix nemoclaw-user --doc-platform fern-mdx` - `npm run docs` - `npm run build:cli` - `npm run typecheck:cli` - Commit/pre-push hooks: markdownlint, gitleaks, docs-to-skills verification, TypeScript CLI, and skills YAML checks passed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified sandbox snapshot restore preserves custom policy presets and restores them without original files. * Switched sandbox setup and remote deployment guidance to Docker-based workflows and emphasized remote onboarding flow. * Expanded troubleshooting for gateway recovery, Docker GPU/WSL issues, and onboarding resume. * Added/updated CLI docs: advanced maintenance, session export, upload/download wrappers, and status recovery guidance. * Added v0.0.64 release notes and links to NemoClaw Community; fixed command reference formatting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
A sandbox with custom policy presets applied via
policy --from-file/--from-dirlost them aftersnapshot restore(includingsnapshot restore --to <dst>): the restored sandbox kept its built-in presets but none of the custom ones.snapshot createrecords applied presets into the rebuild manifest so they survive destroy/recreate, because presets live in the gateway policy engine rather than on the sandbox filesystem. But it only captured the built-in names (policyPresets, fromregistry.policies). Custom presets are tracked separately inregistry.customPoliciesand live in the same gateway-only location, so they were subject to the same loss but were never captured or replayed.Each
CustomPolicyEntryalready stores the preset's fullcontent, so the data needed to replay is available — it just was not carried in the manifest.Related Issue
Closes #5103.
Changes
src/lib/state/sandbox.ts: addcustomPolicies?: CustomPolicyEntry[]toRebuildManifest(+ validator), and capturesb.customPoliciesincreateBackupalongsidepolicyPresets.src/lib/actions/sandbox/snapshot.ts: on restore, reconcile custom policies the same way built-in presets are reconciled — re-apply snapshot entries by content viaapplyPresetContent(which re-records them in the target registry) and remove any the snapshot did not record.test/snapshot.test.ts: cover thatcustomPoliciesround-trips through the manifest and that a malformed entry (missingcontent) is rejected by the manifest validator.Type of Change
Verification
npx vitest run --project cli test/snapshot.test.ts test/rebuild-policy-presets.test.ts test/policies.test.ts test/snapshot-restore-existing-dest.test.ts— 207/207 passnpm run typecheck:cliandnpm run build:cli— cleanSigned-off-by: latenighthackathon latenighthackathon@users.noreply.github.com
Summary by CodeRabbit
New Features
Tests