Skip to content

fix(security): address NC-2227-01 through NC-2227-05 from security review - #2439

Merged
ericksoa merged 4 commits into
remove-config-immutabilityfrom
fix/2227-security-review-followup
Apr 24, 2026
Merged

fix(security): address NC-2227-01 through NC-2227-05 from security review#2439
ericksoa merged 4 commits into
remove-config-immutabilityfrom
fix/2227-security-review-followup

Conversation

@ericksoa

Copy link
Copy Markdown
Contributor

Summary

Follow-up to PR #2227 (refactor(sandbox): default to mutable config, make shields opt-in). Addresses all five findings from the 2026-04-24 security and vulnerability review so #2227 can proceed to merge without blocking on these concerns.

Fixes #2300 (partial — shared concern about mutable config attack surface)

Changes

NC-2227-01 (Critical): Legacy migration can undo shields up

migrate_legacy_layout() in scripts/nemoclaw-start.sh now has three guards:

  1. Sentinel.migration-complete marker prevents re-running on already-migrated sandboxes
  2. Root-only — refuses to run if not root (blocks agent from triggering it)
  3. Ownership — rejects data directories owned by sandbox user (detects agent-planted triggers; legitimate legacy dirs are root-owned from the image build)

Additionally: replaced blanket chown -R sandbox:sandbox "$config_dir" with targeted subdirectory-only chown, preserving root ownership on openclaw.json, .config-hash, .env. Reapplies shields-up ownership after migration if shields were previously active.

NC-2227-02 (High): Fresh mutable sandboxes report Shields: UP

Introduced a three-state model in src/lib/shields.ts: mutable_default, locked, temporarily_unlocked. Fresh sandboxes (no state file) now report NOT CONFIGURED (default mutable state) instead of falsely claiming lockdown is active. isShieldsDown() returns true for both mutable_default and temporarily_unlocked — only locked (after explicit shields up) returns false.

NC-2227-03 (High): Auto-restore does not re-lock sensitive sidecar files

src/lib/shields-timer.ts now calls resolveAgentConfig(sandboxName) to get the full lock target (including .config-hash, .env) before calling lockAgentConfig. Falls back to bare configPath/configDir from argv if resolution fails.

NC-2227-04 (High): Backup dereferences untrusted symlinks

Removed tar -h flag from both backup and restore in src/lib/sandbox-state.ts. Added a pre-backup find-based audit that rejects symlinks (-type l) and special files before creating the tar, preventing exfiltration of config contents via agent-planted symlinks.

NC-2227-05 (High): shields up leaves co-located state directories writable

Added HIGH_RISK_STATE_DIRS constant (skills, hooks, cron, agents, extensions, plugins). lockAgentConfig() now recursively chown -R root:root and chmod 755 these directories during shields-up. unlockAgentConfig() restores sandbox:sandbox ownership during shields-down.

Type of Change

  • Code change (feature, bug fix, or refactor)

Verification

  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed

Tests added:

  • test/nemoclaw-start.test.ts — 6 tests for migration guards (sentinel, root-only, ownership, no blanket chown, shields reapplication, sentinel permissions)
  • test/shields.test.ts — 10 tests covering three-state model derivation, tar flag regression, and state directory locking

Note: NC-2227-02 unit tests replicate deriveShieldsMode logic inline (CJS module not directly importable in vitest). Full code path is exercised by E2E tests in test/e2e/test-shields-config.sh.


Signed-off-by: Andrew Erickson aerickson@nvidia.com

…view

Follow-up to PR #2227 (mutable-by-default refactor). Fixes all five
findings from the 2026-04-24 security and vulnerability review:

- NC-2227-01 (Critical): Guard migrate_legacy_layout with sentinel,
  root-only, and ownership checks; never chown -R the config dir
  itself; reapply shields-up after migration if previously active
- NC-2227-02 (High): Three-state shields model (mutable_default /
  locked / temporarily_unlocked) so fresh sandboxes report NOT
  CONFIGURED instead of falsely claiming Shields: UP
- NC-2227-03 (High): Timer auto-restore now resolves the full agent
  config target including sensitive sidecar files before re-locking
- NC-2227-04 (High): Remove tar -h from backup and restore; add
  pre-backup lstat audit rejecting symlinks and special files
- NC-2227-05 (High): Lock high-risk state dirs (skills, hooks, cron,
  agents, extensions, plugins) during shields up; restore on down

Signed-off-by: Andrew Erickson <aerickson@nvidia.com>
@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 40ea0ddb-5f00-4749-8995-8b18f00b58d0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/2227-security-review-followup

Comment @coderabbitai help to get the list of available commands and usage tips.

@ericksoa ericksoa self-assigned this Apr 24, 2026
… test

The existing nemoclaw-start.test.ts asserts every `echo` line in the
non-root block redirects to stderr. The migration sentinel write
(`echo "..." > "$sentinel"`) is a file write, not a diagnostic, so
use printf instead to avoid matching the echo-grep pattern.
Auto-formatting from shfmt (shell) and prettier (TypeScript) to
satisfy the CI "files were modified by following hooks" check.
@ericksoa
ericksoa merged commit 8579c08 into remove-config-immutability Apr 24, 2026
18 of 19 checks passed
@cv cv added the v0.0.25 label Apr 24, 2026
@wscurran wscurran added the bug-fix PR fixes a bug or regression label Jun 8, 2026
@cv
cv deleted the fix/2227-security-review-followup branch June 28, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PR fixes a bug or regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants