fix(shields): unlock idempotently when config already mutable (#7430) - #7629
Conversation
On DGX Spark/Station and macOS an in-sandbox OpenClaw reconciler re-permissions the config back to the sandbox-owned mutable posture after a host shields lock returns, and a freshly onboarded or snapshot-restored sandbox boots mutable before the lock settles. The config guard's `unlock` transition required the exact shields-locked posture, so the first `nemoclaw <sandbox> shields down` on a clone/restore (or fresh) sandbox failed with `config-not-locked` even though the config already held the mutable target posture; the rollback then re-locked it, so a retry succeeded. Make `unlock` idempotent, mirroring the already-locked short-circuits in the lock branch: when the config already holds the exact mutable posture, verify the mutable files and return a no-op success. Security-safe — it returns early only for the sandbox-owned mutable target, so nothing locked is unlocked. Reproduced and verified on a real DGX Spark (GB10, arm64, OpenShell 0.0.85) with the worktree CLI: before the fix `shields down` returned `[config-not-locked]`; after, it returns "Config unlocked". Signed-off-by: Yimo Jiang <yimoj@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Coverage OverviewLanguages: TypeScript TypeScript / code-coverage/pluginThe overall coverage in commit 5b5cee9 in the TypeScript / code-coverage/cliThe overall coverage in commit 5b5cee9 in the Show a code coverage summary of the most impacted files.
Updated |
|
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:
📝 WalkthroughWalkthroughThe OpenClaw config guard now treats ChangesOpenClaw unlock transition
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant ConfigGuard
participant ConfigFilesystem
CLI->>ConfigGuard: request unlock
ConfigGuard->>ConfigFilesystem: snapshot config and hash
ConfigGuard->>ConfigFilesystem: verify mutable posture
ConfigGuard-->>CLI: success or config-not-mutable issue
Possibly related issues
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/openclaw-config-guard.test.ts (1)
417-424: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert that the complete config pair remains unchanged.
This test only verifies the directory mode. Capture
openclaw.jsonand.config-hashbytes and modes beforerunGuard, then compare them afterward; otherwise a regression that rewrites configuration data or file permissions could still pass.As per path instructions, this test should prove behavioral confidence; the PR objective also requires the mutable configuration to remain unchanged.
Proposed test strengthening
- const { configDir } = fixture(); + const { configDir, configPath, hashPath } = fixture(); + const before = { + config: fs.readFileSync(configPath), + hash: fs.readFileSync(hashPath), + configMode: mode(configPath), + hashMode: mode(hashPath), + dirMode: mode(configDir), + }; const r = runGuard("unlock", configDir); expect(r.status, JSON.stringify(r.lines)).toBe(0); expect(r.lines.at(-1)).toMatchObject({ action: "unlock", status: "ok" }); - expect(mode(configDir)).toBe(0o2770); + expect(fs.readFileSync(configPath)).toEqual(before.config); + expect(fs.readFileSync(hashPath)).toEqual(before.hash); + expect(mode(configPath)).toBe(before.configMode); + expect(mode(hashPath)).toBe(before.hashMode); + expect(mode(configDir)).toBe(before.dirMode);🤖 Prompt for 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. In `@test/openclaw-config-guard.test.ts` around lines 417 - 424, Strengthen the idempotent unlock test around runGuard("unlock", configDir) by capturing the bytes and permission modes of both openclaw.json and .config-hash before execution, then asserting all four values remain unchanged afterward; retain the existing directory-mode and successful unlock assertions.Source: Path instructions
🤖 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.
Nitpick comments:
In `@test/openclaw-config-guard.test.ts`:
- Around line 417-424: Strengthen the idempotent unlock test around
runGuard("unlock", configDir) by capturing the bytes and permission modes of
both openclaw.json and .config-hash before execution, then asserting all four
values remain unchanged afterward; retain the existing directory-mode and
successful unlock assertions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: c2564c6a-d3fc-4e0f-b98e-45d3bab86f30
📒 Files selected for processing (2)
scripts/openclaw-config-guard.pytest/openclaw-config-guard.test.ts
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
PR Review Advisor — Blocking findings reportedAdvisor assessment: Blockers require maintainer review Model lanes
Nemotron output stays in workflow artifacts and does not change the assessment above. E2E guidanceAdvisory only. E2E / PR Gate selects and runs jobs independently. Recommended E2E: None 2 optional E2E recommendations
Blockers
|
#7430) Address CodeRabbit review on #7629: the idempotent-unlock regression test verified only the config directory mode. Also assert the openclaw.json and .config-hash file modes stay sandbox-owned 0660 so a regression that re-permissions config files during the no-op unlock cannot pass. Kept within the 1500-line test-file-size budget by dropping the redundant result-shape assertion. Signed-off-by: Yimo Jiang <yimoj@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @coderabbitai — addressed in 33b70e9. The idempotent-unlock test now asserts all three modes ( |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git show --stat --oneline 33b70e9c8
git show --format=fuller --find-renames --find-copies -- \
scripts/openclaw-config-guard.py test/openclaw-config-guard.test.ts 33b70e9c8
ast-grep outline scripts/openclaw-config-guard.py --items all --match '_transition|_verify_mutable_files'
ast-grep outline test/openclaw-config-guard.test.ts --items all --match 'unlock|mutable'
rg -n -C 6 '_is_mutable_dir_posture|_verify_mutable_files|def _transition' scripts/openclaw-config-guard.py
rg -n -C 8 'idempotent|already.*mutable|openclaw\.json|\.config-hash|0o2770|0o660' test/openclaw-config-guard.test.tsLength of output: 49138
🐇 ✅ |
prekshivyas
left a comment
There was a problem hiding this comment.
Security/correctness review at exact head 33b70e9c88bf98bf70d2052b4bddbb2a23974b78.
Blocking finding: the idempotent unlock path checks the mutable directory posture before _snapshot_raw_pair(), then validates only file metadata. Because the tree is already sandbox-owned and writable, a concurrent sandbox process can change either directory during the pair capture. The branch still returns success even though the final posture is no longer the exact mutable target. I reproduced this by injecting a config-directory chmod to 0700 after the snapshot: _transition("unlock", ...) returned successfully with final mode 0700.
Please capture the pair and then revalidate the full mutable posture, for example with _verify_mutable_posture(opened, pair, identity), and add a regression that injects parent or config directory drift during capture. This also resolves advisor warning PRA-1.
Gate evidence:
- Product scope: PASS. This repairs existing Shields behavior linked to issue #7430 and adds no supported surface.
- Contributor compliance: PASS. The PR body has the author DCO declaration and all 3 commits are GitHub Verified.
- CI: PASS for the current PR SHA; all 53 current checks are green, including
E2E / PR Gate. - Focused verification: PASS, 45/45
openclaw-config-guardtests at this head. - Current-main compatibility: NOT READY. The PR records base
a47ddd896e, while currentmainisa8748ab1af. A synthetic merge is clean, but the branch must be refreshed and its new SHA rechecked before approval.
Nine-category security review:
- Secrets and credentials: PASS — no secrets or credential handling changed.
- Input validation and sanitization: PASS — descriptor-safe file validation remains in place.
- Authentication and authorization: PASS — root and startup authorization paths are unchanged.
- Dependencies: PASS — no dependency changes.
- Error handling and logging: PASS — no sensitive logging added.
- Cryptography and data protection: PASS — no cryptographic behavior changed.
- Configuration and security defaults: PASS — no ports, privileges, or defaults changed.
- Security testing: WARNING — the new writable-tree race is not covered.
- System security: FAIL — the reproduced TOCTOU lets unlock report success after the verified directory posture has drifted.
| if _is_mutable_dir_posture(opened, identity): | ||
| # Unlock is idempotent, mirroring the already-locked short-circuits in | ||
| # the lock branch above. A config already in the exact mutable posture | ||
| # is the unlock target: on some platforms (DGX Spark/Station, macOS) an | ||
| # in-sandbox OpenClaw reconciler re-permissions the config back to | ||
| # sandbox-owned mutable *after* a host lock returns, and a freshly | ||
| # onboarded or snapshot-restored sandbox boots mutable before the lock | ||
| # settles. Requiring the locked posture there rejected a legitimate | ||
| # `shields down` with config-not-locked even though the config already | ||
| # holds the mutable target posture. Verify the exact mutable file | ||
| # posture and treat the transition as a no-op instead of failing. | ||
| _verify_mutable_files(opened, _snapshot_raw_pair(opened), identity) | ||
| return |
There was a problem hiding this comment.
The directory check happens before _snapshot_raw_pair(), but this tree is already sandbox-writable. If a sandbox process changes the parent or config directory during capture, _verify_mutable_files() still passes and this branch returns success without rechecking the directory. I reproduced _transition("unlock", ...) succeeding with final config-directory mode 0700. Capture the pair, then validate the full mutable posture (for example _verify_mutable_posture(opened, pair, identity)) and add a directory-drift regression.
There was a problem hiding this comment.
Addressed at exact signed/Verified head 32c1b9698. The already-mutable unlock path now captures with _snapshot_raw_pair() and then calls _verify_mutable_posture(opened, pair, identity) before returning. The regression mutates the config directory to 0700 during capture and requires fail-closed config-not-mutable; the companion rejected-mode coverage also proves a 0600 file remains unchanged. The focused guard suite passes 45/45 and exact-range hooks pass. Leaving this thread open for your re-review.
There was a problem hiding this comment.
Exact head update: aec2516 is now current-main synced and GitHub Verified; the feature diff is unchanged. The full mutable-posture revalidation and directory-drift regression remain in place. Exact-head guard tests pass 45/45 under the repository Python 3.11 environment, exact-range hooks pass, and the fresh WSL, macOS, CLI, CodeQL, and E2E gates are green. Leaving the thread open for your re-review.
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@test/openclaw-config-guard.test.ts`:
- Around line 427-434: Update the invalid-permissions loop in the unlock guard
test to assert that mode(invalidPath) remains 0o600 after the rejected run. Move
restoration of invalidPath to 0o660 into a finally block so cleanup occurs even
if the assertion or runGuard call fails, while preserving the existing identity
and status assertions.
🪄 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: 58b4efc7-bbce-4406-9ed6-a404b96ca447
📒 Files selected for processing (1)
test/openclaw-config-guard.test.ts
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
<!-- markdownlint-disable MD041 --> ## Summary <!-- 1-3 plain sentences: what changes and why. Describe before-and-after behavior when it applies. Follow the NemoClaw Writing Guide: https://github.com/NVIDIA/NemoClaw/blob/main/WRITING.md. Do not add unrelated prose cleanup. --> Replace the mutable NodeSource bootstrap in both WSL workflows with checksum-verified official Node.js 22.23.1 archives. This removes the shared installer failure affecting current PR lanes while keeping the runtime on the supported, security-current Node 22 LTS line. ## Changes <!-- List concrete changes. If this adds an abstraction, configuration, fallback, migration, or compatibility path, name its current requirement and consumer, explain why a direct change is insufficient, and identify the test that protects it. --> - Install official Node.js 22.23.1 archives for `x64` and `arm64`, with exact SHA-256 pins, HTTPS-only bounded downloads, temporary-directory cleanup, and fail-closed architecture handling. - Verify each archive before extraction and assert the installed runtime version. - Protect both workflow consumers with `test/platform-vitest-main-workflow.test.ts`, including architecture-to-digest bindings and the repository's approved security source-shape contract. - Address the same WSL bootstrap failure observed on [#7590](https://github.com/NVIDIA/NemoClaw/actions/runs/30312091098/job/90131121904), [#7629](https://github.com/NVIDIA/NemoClaw/actions/runs/30311922489/job/90131122471), [#7603](https://github.com/NVIDIA/NemoClaw/actions/runs/30311696352/job/90131123359), and [#7626](https://github.com/NVIDIA/NemoClaw/actions/runs/30312801807/job/90132378380). ## Type of Change - [x] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [ ] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates <!-- Check one tests line and one docs line. Check other lines when applicable. Add every requested justification or approval reference. --> - [x] Tests added or updated for changed behavior - [ ] Existing tests cover changed behavior — justification: - [ ] Tests not applicable — justification: - [ ] Docs updated for user-facing behavior changes - [x] Docs not applicable — justification: This changes internal CI bootstrap behavior only; public installation requirements remain unchanged. - [x] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [x] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Codex security review PASS across all nine categories on exact diff fingerprint `2981cff1a79a0c0acfaf5b0a8e55353809ec472c0565fa1f168996f92107b7ae`. - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Documentation Writer Review <!-- Required for code and documentation changes after the changes and applicable validation are complete. Keep one review checkbox and one instance of each visible or hidden field. For Evidence, list changed documentation paths. For documentation-only changes, also state that the writing rules and documentation style were reviewed. For other results, explain why no documentation change is needed or why the review is blocked. For Agent, use a consistent product and surface name, such as Codex Desktop, Codex CLI, Claude Code, or Cursor. After committing all review changes, put `git rev-parse --short HEAD` and `git rev-parse --short HEAD:AGENTS.md` in the hidden metadata below. Rerun the review and refresh that metadata after any new commit. This receipt is advisory during the data-collection pilot. --> - [x] Documentation writer subagent reviewed the completed changes - Result: `no-docs-needed` - Evidence: Exact-head review confirmed the change is limited to CI bootstrap workflows and their executable contract; public Node.js requirements do not change. - Agent: Codex Desktop <!-- docs-review-head-sha: 062779c --> <!-- docs-review-agents-blob-sha: be20a09 --> ## DGX Station Hardware Evidence <!-- Required only when scripts/prepare-dgx-station-host.sh changes. Maintainers must review the linked evidence before approving or merging. This is human-reviewed evidence, not authenticated hardware provenance. Exceptional bypasses use existing repository governance and must be documented on the PR. --> - [ ] Tested on DGX Station - Tested commit: - Station profile/scenario: - Result: - Supporting evidence: ## Verification <!-- Check each applicable item only when supported by the requested evidence. Run targeted tests once per relevant change set and rerun after later edits or hook autofixes that can affect the tested behavior. Do not rerun hook-covered checks. --> - [x] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub - [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run check:diff` passed when hooks were skipped or unavailable - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: `npx vitest run --project integration test/platform-vitest-main-workflow.test.ts` (3 passed); both embedded WSL installers pass `bash -n`. - [ ] Applicable broad gate passed — `npm test` for broad runtime/test-harness changes; `npm run check` for repo-wide validation/coverage changes — command/result: - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) - [ ] 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) --- <!-- DCO sign-off is required in this PR description, and every commit must appear as Verified in GitHub. Run: git config user.name && git config user.email --> Signed-off-by: Apurv Kumaria <akumaria@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **CI Improvements** * Standardized Node.js 22 installation across WSL workflows with a pinned version and architecture-specific downloads. * Added archive checksum verification, version validation, cleanup, and explicit failure handling. * **Tests** * Added coverage to verify secure, deterministic Node.js installation behavior in both WSL workflows. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
Babysitting status for exact head 210f7c8 (plain comment; no Changes Requested review from me): the later commits appear to address the earlier mutable-posture race, and all 53 current check contexts are green. Approval is still blocked because the branch is based on c4c020c rather than current main da1b103, maintainer edits are disabled, and the prior review has not been cleared on current-base evidence. Please refresh; I will re-review the next quiet head. |
|
Correction to my prior handoff: conflict-free base refreshes are explicitly waived. Please do not merge main solely for base currency; preserving exact-head evidence is preferred unless GitHub reports a real conflict or reviewed behavior requires a change. The substantive blocker or missing evidence described in the earlier handoff remains, but base age by itself is not a blocker. This is a plain coordination comment, not Changes Requested. |
cjagwani
left a comment
There was a problem hiding this comment.
Exact-head approval for 210f7c8 after the quiet-window review. The prior mutable-posture race is resolved: the idempotent unlock path captures the raw config/hash pair, then revalidates sandbox ownership and exact modes on both live directory descriptors and both files before returning. The injected config-directory drift regression fails closed. The exact focused suite passes 45/45, Python compilation passes, all 53 GitHub checks are green, all 14 commits are Verified, and no unresolved major findings remain. Nine-category security review is PASS: no credential, authorization, dependency, logging, cryptographic, policy, or privilege expansion; descriptor-bound validation and immutable/append-flag checks remain intact. GitHub reports no conflict, and the user waiver applies to base-only refreshes.
|
@prekshivyas The blocking review is attached to old head |
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
<!-- markdownlint-disable MD041 --> ## Summary Adds the canonical dated changelog entry for `v0.0.100` so the maintainer release plan can verify the pre-tag documentation prerequisite. The entry summarizes the user-facing changes merged since `v0.0.99` and links to the relevant guides. ## Changes - Add `docs/changelog/2026-07-31.mdx` with the exact `## v0.0.100` heading. - Cover restored OpenClaw pairing, transactional replacement, Deep Agents Code, onboarding recovery, lifecycle cleanup, Hermes builds, host provenance, documentation, and trusted E2E evidence. - Distinguish active Docker and Kubernetes runtime-bundle enforcement from the still-inactive managed shared-state transaction foundation. ## Source Coverage The release entry maps the doc-impacting merged PRs in the `v0.0.99..main` release range to `docs/changelog/2026-07-31.mdx`: #8021, #8024, #7973, #8028, #7947, #7788, #7884, #8023, #7969, #8020, #7989, #8000, #7907, #7942, #7567, #8013, #7955, #8017, #8014, #8015, #7629, #7644, #7821, #7971, and #7991. PR #7974 was reviewed after the final rebase and excluded because it changes internal maintainer-skill attribution policy and tests only; it does not change a user-facing product or documentation surface. ## 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) ## Quality Gates - [ ] Tests added or updated for changed behavior - [x] Existing tests cover changed behavior — justification: the changelog contract test validates the dated entry, version heading, SPDX form, and route constraints. - [ ] Tests not applicable — justification: - [x] Docs updated for user-facing behavior changes - [ ] Docs not applicable — justification: - [ ] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [ ] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Documentation Writer Review - [x] Documentation writer subagent reviewed the completed changes - Result: `docs-updated` - Evidence: `docs/changelog/2026-07-31.mdx`; exact-head review passed for `6093f44f`; writing rules and documentation style reviewed; `npx vitest run test/changelog-docs.test.ts` passed 6/6; `npm run docs` passed with zero Fern errors and two generic Fern upgrade notices. - Agent: Codex Desktop <!-- docs-review-head-sha: 6093f44 --> <!-- docs-review-agents-blob-sha: 3dd7c24 --> ## DGX Station Hardware Evidence - [ ] Tested on DGX Station - Tested commit: Not applicable; no DGX Station host script changed. - Station profile/scenario: Not applicable. - Result: Not applicable. - Supporting evidence: Not applicable. ## Verification - [x] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub - [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run validate:pr` passed after refreshing `origin/main` when hooks were skipped or unavailable - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: `npx vitest run test/changelog-docs.test.ts` passed 6/6 at `6093f44f`. - [ ] Applicable broad gate passed — `npm test` for broad runtime/test-harness changes; `npm run check` for repo-wide validation/coverage changes — command/result: Not applicable to a dated prose-only release entry. - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) — validation passed with zero errors; Fern emitted two generic upgrade notices. - [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) — the changelog entry has the required parser-safe MDX SPDX header; dated changelog entries intentionally do not use page frontmatter. --- Signed-off-by: Senthil Ravichandran <senthilr@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added release notes for v0.0.100. * Documented improvements to restore pairing, sandbox replacement, onboarding recovery, lifecycle cleanup, runtime handling, build support, host readiness, and end-to-end validation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Senthil Ravichandran <senthilr@nvidia.com>
Summary
nemoclaw <sandbox> shields downfailed on the first attempt on DGX Spark/Station and macOS clone/restore (and freshly onboarded) sandboxes withOpenClaw config guard unlock [config-not-locked] ... restart seal requires the exact shields-locked directory posture. On those platforms an in-sandbox OpenClaw reconciler re-permissions the config back to the sandbox-owned mutable posture after a host shields lock returns, and a freshly onboarded or snapshot-restored sandbox boots mutable before the lock settles, so the first hostshields downranunlockagainst an already-mutable config. The config guard'sunlocktransition required the exact shields-locked posture and rejected it; the rollback then re-locked the config, so a retry succeeded. After this changeunlockis idempotent: an already-mutable config is a no-op success, mirroring the already-locked short-circuit in thelockbranch.Related Issue
Fixes #7430
Changes
scripts/openclaw-config-guard.py: in_transition, theunlockbranch now returns a no-op success when the config already holds the exact mutable posture. After the raw config/hash pair is captured,_verify_mutable_posturerevalidates the sandbox-owned parent, config directory, and both files before returning, so directory drift during capture fails closed. A genuinely locked or drifted posture still takes the strict path. No new abstraction, configuration, or fallback is introduced.test/openclaw-config-guard.test.ts: regression coverage verifies idempotent success for the exact mutable posture, fail-closed rejection when the config directory drifts during pair capture, and that a rejected0600unlock leaves the invalid mode and file identities unchanged before fixture cleanup restores the mutable posture.Type of Change
Quality Gates
5b5cee9efacross all nine categories; exact diff fingerprintccedc3a08262471a16265d0e9782eec7a9d8c5d8eaaacb99ca178528854f9269. The change removes the reported TOCTOU gap by capturing the pair and then revalidating the complete mutable posture; focused race and rejected-mode coverage passes.Documentation Writer Review
no-docs-needed5b5cee9ef310dd316064e8546fda6fe0b2467f13preserves a byte-identical two-file diff with regression-only coverage proving that a rejected unlock preserves an invalid0600mode and always restores the test fixture during cleanup. The production contract is unchanged: unlock is idempotent only for the exact mutable posture and fails closed on drift.docs/manage-sandboxes/runtime-controls.mdx,docs/reference/commands.mdx,docs/reference/troubleshooting.mdx, anddocs/security/tcb-boundary.mdxalready describe the mutable2770/660contract, unsupported700/600drift, shields transitions, and fail-closed config-guard boundary.Verification
Signed-off-by:line and every commit appears asVerifiedin GitHubpre-commit,commit-msg, andpre-pushhooks passed, ornpm run check:diffpassed when hooks were skipped or unavailable — exact-head5b5cee9efrepository hooks passed; the push hook's CLI typecheck and package/tag sync passed210f7c802; exact current-main head5b5cee9efkeeps the same two-file fingerprint, and build/validation/typecheck/hooks pass. Fresh GitHub CI is required for the exact-head Linux rerunnpm testfor broad runtime/test-harness changes;npm run checkfor repo-wide validation/coverage changes — command/result: not applicable (single guard-branch change with focused coverage)npm run docsbuilds without warnings (doc changes only)Real-hardware E2E (reporter workflow, worktree CLI)
macOS (Apple M4 Pro, arm64, macOS 26.5.2, OpenShell 0.0.85, Colima Docker 29.2.1) — exact head
210f7c802Worktree CLI built from a fresh clone of this branch on the host (
./bin/nemoclaw.js --version→nemoclaw v0.0.96-54-g210f7c8). Reporter workflow: onboarded sandboxsb-7430→snapshot restoreclone →shields down.Failing precondition captured raw on the clone before any shields command — config already mutable while shields state is not down:
Pre-fix (baked guard without this change) — reproduces:
The failed unlock's rollback re-locks the config (
/sandbox1775 root:sandbox,/sandbox/.openclaw755 root:root,openclaw.json444 root:root), which is exactly why a retry appears to succeed and masks the defect. The post-fix run therefore uses a fresh clone, not a retry.Post-fix (this change's guard, first attempt on a fresh clone) — resolved:
Config stays at the mutable target (
/sandbox/.openclaw2770 sandbox:sandbox,openclaw.jsonand.config-hash660 sandbox:sandbox) — the idempotent branch changed nothing.Locked transition and strict path unaffected (same clone, same head):
A genuinely locked config still takes the strict
_verify_locked_posturepath and unlocks normally; only the already-mutable posture short-circuits.DGX Spark (
horizon-46, NVIDIA GB10, aarch64, Ubuntu 24.04, OpenShell 0.0.85, Docker 29.2.1)Same reporter workflow on the reporter's platform, worktree CLI, failing state = config mutable (
2770 sandbox:sandbox) + host shields state UP (shieldsDown:false):nemoclaw sb-7430-clone shields down --timeout 2m --policy permissive→ERROR: ... [config-not-locked] ...(reproduces).Config unlocked for sb-7430-clone (auto-lockdown in: 2m); config stays2770 sandbox:sandbox.Both platforms accept the OpenShell-supervised PID-1 topology on current
main(the reporter's v0.0.91requires NemoClaw PID 1no longer fires); the remaining first-attempt failure was thisconfig-not-lockedidempotency gap.Signed-off-by: Yimo Jiang yimoj@nvidia.com
Summary by CodeRabbit
Bug Fixes
unlockfully idempotent when the configuration already has the expected mutable permissions.Tests
unlocksucceeds with the correct mutable posture and that generated artifacts remain unchanged.