Skip to content

fix(security): harden post-merge state handling - #8074

Merged
cv merged 12 commits into
mainfrom
codex/postmerge-security-hardening-7995-7847
Aug 3, 2026
Merged

fix(security): harden post-merge state handling#8074
cv merged 12 commits into
mainfrom
codex/postmerge-security-hardening-7995-7847

Conversation

@cv

@cv cv commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up review of merged #7995 and #7847 found two independent state-handling gaps. Shields recovery now remains bound to its original configuration target. Migration preparation now installs only verified, credential-filtered configuration bytes through a pinned directory descriptor.

Changes

  • Follow-up to fix(shields): write an absent config hash before locking a config #7995:
    • Persist the agent name, config path, and config directory in each Shields timer marker.
    • Use that persisted target for detached-timer and expired-marker recovery when registry state is unavailable or resolves to a different target.
    • Give Deep Agents a canonical protected-file set and a descriptor-sealed config-lock transaction.
    • Fail closed when the transaction cannot restore or confirm a protected posture.
    • Document recovery from a critical Deep Agents config-lock failure.
  • Follow-up to fix(security): fail closed while scrubbing migration backups #7847:
    • Exclude the copied openclaw.json from the general recursive copy.
    • Read the copied config through the descriptor-bound snapshot scanner.
    • Strip credential fields and contextual secret assignments in memory.
    • Install the sanitized config with exclusive, no-follow creation at mode 0600.
    • Verify the installed inode, link count, size, and digest before accepting it.
    • Reject unsafe config path segments that could modify object prototypes.
    • Pin the plugin contextual secret patterns to the canonical CLI patterns with a parity test.

Type of Change

  • 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

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • 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: Independent nine-category security review passed for commit SHA 662d4ad020369dfcad3a21da225090cce507b111 against base SHA d6ac4027b75b15b8acd1456984acbbfb623cb231. No findings in secrets and credentials; input validation and data sanitization; authentication and authorization; dependencies and third-party libraries; error handling and logging; cryptography and data protection; configuration and security headers; security testing; or system security. The three addressed review threads are resolved.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: docs-updated
  • Evidence: Independent review passed for commit SHA 662d4ad020369dfcad3a21da225090cce507b111 against base SHA d6ac4027b75b15b8acd1456984acbbfb623cb231. Reviewed docs/reference/commands.mdx, docs/reference/troubleshooting.mdx, and changed explanatory text against the NemoClaw Writing Guide, Controlled Word List, documentation guidance, implementation, tests, and Deep Agents variant routing. Required checks docs-review-receipt, cli-parity, and preview pass for this commit. No blocking finding remains.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • 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
  • Targeted behavior tests pass for the current change set — Required check checks concluded SUCCESS for commit SHA 662d4ad020369dfcad3a21da225090cce507b111.
  • Applicable broad gate passed — Required check E2E / PR Gate is running for commit SHA 662d4ad020369dfcad3a21da225090cce507b111.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (documentation changes only) — Required documentation checks pass for commit SHA 662d4ad020369dfcad3a21da225090cce507b111.
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Carlos Villela cvillela@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added fail-closed protection for Deep Agents configuration-lock failures.
    • Improved automatic recovery by preserving agent and configuration details.
    • Added secure snapshot-based configuration recovery and verification.
    • Expanded detection of embedded credential patterns.
  • Bug Fixes

    • Prevented unsafe repairs, symlink swaps, hard-link mutations, and incomplete rollbacks.
    • Improved handling of invalid or empty configuration files.
  • Documentation

    • Added guidance for diagnosing, recovering from, and verifying critical configuration-lock failures.
  • Tests

    • Expanded coverage for security, recovery, locking, rollback, and timer behavior.

cv added 2 commits August 2, 2026 01:55
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1ee18043-2eae-4c07-8fd5-9946529ceac0

📥 Commits

Reviewing files that changed from the base of the PR and between e96aa39 and 662d4ad.

📒 Files selected for processing (5)
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • nemoclaw/src/commands/migration-state-security.test.ts
  • nemoclaw/src/commands/migration-state.ts
  • src/lib/shields/policy-transition.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • nemoclaw/src/commands/migration-state-security.test.ts
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • nemoclaw/src/commands/migration-state.ts

📝 Walkthrough

Walkthrough

The change adds secure snapshot installation, Deep Agents fail-closed configuration locking, recovery-aware timer metadata, expanded security tests, and operator guidance for critical lock failures.

Changes

Deep Agents security and recovery

Layer / File(s) Summary
Secure snapshot configuration handling
nemoclaw/src/commands/*, nemoclaw/src/security/*, nemoclaw/src/shared/*, test/nemoclaw-plugin-secret-pattern-parity.test.ts
Snapshot handling now uses centralized parsing, credential filtering, descriptor-boundary installation, restrictive permissions, and filesystem-race checks.
Deep Agents lock transaction
src/lib/shields/seal.ts, src/lib/shields/seal.test.ts, scripts/state-dir-guard.py, test/state-dir-guard.test.ts
Deep Agents locking now validates paths, replaces configuration and hash files atomically, detects races, restores state on failure, and applies fail-closed containment.
Shields policy and recovery integration
src/lib/shields/index.ts, src/lib/shields/policy-transition.test.ts, docs/reference/commands.mdx, docs/reference/troubleshooting.mdx
Shields now parses bounded lock-failure statuses, verifies rollback posture, preserves containment, and documents trusted recovery procedures.
Timer target persistence and authorization
src/lib/shields/timer.ts, src/lib/shields/timer-control.ts, src/lib/shields/timer.test.ts, src/lib/shields/flow.test.ts, src/lib/shields/auto-restore-target.test.ts
Timer markers and arguments now preserve agent identity and configuration paths for target matching and auto-restore.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MigrationState
  participant SnapshotBoundary
  participant TrustedHelper
  MigrationState->>SnapshotBoundary: Install sanitized snapshot configuration
  SnapshotBoundary->>TrustedHelper: Validate and install pinned file
  TrustedHelper-->>SnapshotBoundary: Return installation status
  SnapshotBoundary-->>MigrationState: Continue or abort preparation
Loading
sequenceDiagram
  participant Shields
  participant LockTransaction
  participant StateDirectory
  Shields->>LockTransaction: Lock Deep Agents configuration
  LockTransaction->>StateDirectory: Apply containment or rollback posture
  StateDirectory-->>LockTransaction: Return verification status
  LockTransaction-->>Shields: Return structured lock result
Loading

Possibly related PRs

Suggested labels: area: security, area: sandbox, area: policy, area: docs, bug-fix, integration: dcode

Suggested reviewers: laitingsheng, apurvvkumaria

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR's primary focus on security hardening for post-merge state handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/postmerge-security-hardening-7995-7847

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

@github-code-quality

github-code-quality Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 662d4ad in the codex/postmerge-secu... branch remains at 96%, unchanged from commit d6ac402 in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit 662d4ad in the codex/postmerge-secu... branch remains at 81%, unchanged from commit d6ac402 in the main branch.

Show a code coverage summary of the most impacted files.
File main d6ac402 codex/postmerge-secu... 662d4ad +/-
src/lib/sandbox...vileged-exec.ts 87% 78% -9%
src/lib/policy/...y-validation.ts 100% 95% -5%
src/lib/platform.ts 89% 84% -5%
src/lib/policy/index.ts 61% 59% -2%
src/lib/shields/index.ts 68% 69% +1%
src/lib/policy/...ne-exclusion.ts 92% 96% +4%
src/lib/onboard...shboard-port.ts 90% 96% +6%
src/lib/onboard...ndbox-create.ts 83% 91% +8%
src/lib/onboard...-create-plan.ts 75% 88% +13%
src/lib/onboard...ndbox-create.ts 33% 83% +50%

Updated August 03, 2026 15:21 UTC

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 1 warning · 0 suggestions
  • Model comparison: normalized findings differ; normalized terminology decisions differ; normalized E2E selections differ; Nemotron reported the same number of blockers, 1 more warning, the same number of suggestions.
5 terminology differences from the second opinion

Advisory only. These are normalized differences from the primary terminology receipt.

  • CRITICAL at docs/reference/commands.mdx:1165: selected only by the second-opinion lane as define.
  • config-lock at docs/reference/commands.mdx:1165: selected only by the second-opinion lane as established.
  • Deep Agents at docs/reference/commands.mdx:1165: selected only by the second-opinion lane as established.
  • descriptor-bound at nemoclaw/src/commands/migration-state.ts:622: selected only by the second-opinion lane as established.
  • fail-closed containment at docs/reference/troubleshooting.mdx:1263: selected only by the second-opinion lane as define.
2 additional E2E selections from the second opinion

Advisory only. The primary lane did not select these E2E jobs or targets.

  • rebuild-hermes: The completed second-opinion lane identified E2E coverage that the primary lane omitted.
  • sandbox-survival: The completed second-opinion lane identified E2E coverage that the primary lane omitted.

Second-opinion terminology and E2E selections are advisory. They do not change the primary assessment or E2E / PR Gate.

4 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • define — config-root posture at docs/reference/troubleshooting.mdx:1268: Keep the first-use definition and retain the explicit contrast with sandbox-parent posture.
  • define — sandbox-parent posture at docs/reference/troubleshooting.mdx:1270: Keep the definition adjacent to config-root posture because recovery actions depend on their distinction.
  • justified — containment posture at docs/reference/troubleshooting.mdx:1277: Keep the grouping term with the stated confirmed-state and non-confirmed-result distinction.
  • established — trusted snapshot at docs/reference/commands.mdx:1168: Keep using trusted snapshot where the procedure requires a pre-failure accepted recovery source.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: cloud-inference, security-posture, rebuild-openclaw, state-backup-restore

2 optional E2E recommendations
  • shields-config
  • snapshot-commands

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

Comment thread nemoclaw/src/commands/migration-state-security.test.ts Fixed
Comment thread nemoclaw/src/security/snapshot-sanitizer-failure.test.ts Fixed
Comment thread src/lib/shields/seal.test.ts Fixed
Comment thread src/lib/shields/seal.test.ts Fixed
Comment thread src/lib/shields/seal.test.ts Fixed
Comment thread src/lib/shields/seal.test.ts Fixed
Comment thread src/lib/shields/seal.test.ts Fixed
Comment thread src/lib/shields/seal.test.ts Fixed
Comment thread src/lib/shields/seal.test.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
src/lib/shields/index.ts (1)

2379-2405: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve the underlying error in the Deep Agents rollback diagnostics.

Both catch blocks discard the caught error and substitute a fixed "command failed" string. The CRITICAL line then tells the operator that rollback failed without saying why. The OpenClaw branch at lines 2328-2352 keeps the cause through openClawRollbackIssue. Match that treatment so the two rollback paths give equivalent detail.

♻️ Proposed change
         try {
           rollbackIssues.push(
             ...restoreStateDirLockPosture(stateDirLockExec(sandboxName), target.configDir, true),
           );
-        } catch {
-          rollbackIssues.push("state-directory rollback failed: command failed");
+        } catch (rollbackError) {
+          const message =
+            rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
+          rollbackIssues.push(`state-directory rollback failed: ${message}`);
         }

Apply the same change to the verifyShieldsLockState catch below it.

🤖 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 `@src/lib/shields/index.ts` around lines 2379 - 2405, Preserve the underlying
exceptions in both Deep Agents rollback catch blocks within the rollback
handling branch: capture each caught error and pass it through the same
rollback-issue formatting or cause-preserving treatment used by
openClawRollbackIssue. Apply this consistently to the state-directory
restoration and verifyShieldsLockState catches so the final CRITICAL diagnostic
includes the original failure details instead of only “command failed.”
🤖 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 `@nemoclaw/src/commands/migration-state-security.test.ts`:
- Around line 89-90: Remove the dead null-check following the non-null assertion
in the migration-state security test. Update the cleanupSnapshotBundle call to
use the narrowed snapshot value, following the as NonNullable<typeof ...>
pattern used in snapshot-sanitizer-failure.test.ts, while preserving the
existing assertion.

In `@nemoclaw/src/security/credential-filter.test.ts`:
- Around line 100-102: Replace the duplicated apiKey assertion in the
valueLooksLikeSecret tests with a distinct input that exercises another
CONTEXT_SECRET_PATTERNS shape, such as a camelCase Secret suffix or bare KEY
assignment, while keeping the expected result true.

In `@nemoclaw/src/security/snapshot-sanitizer-failure.test.ts`:
- Around line 128-157: Replace the unbounded shell spin loops in the race-test
wrappers with one shared bounded-wait helper. In
nemoclaw/src/security/snapshot-sanitizer-failure.test.ts lines 128-157, update
the wait around installDescriptorSnapshotFile to use waitUntil and ensure the
subshell exits when the cap is reached; in lines 159-191, use the same helper
for both target and alias size waits, passing the 20,000 ms timeout for the size
check and adding the helper’s short sleep. Preserve the existing race-test
behavior.

In `@src/lib/shields/index.ts`:
- Around line 804-833: Unify the persisted fallback-target construction and
registry-match behavior across src/lib/shields/index.ts lines 804-833 and
src/lib/shields/timer.ts lines 341-370: extract the shared helper from
resolvePersistedAutoRestoreTarget, explicitly choose consistent handling for
missing marker.agentName, and reuse the same configHashPath-derived
sensitive-file list. Update the timer recovery path to call this helper instead
of rebuilding persistedLockTarget inline; both sites must produce the same
target for identical markers, including markers without agentName.

In `@src/lib/shields/seal.test.ts`:
- Around line 845-862: Move the outcome.status and outcome.stderr assertions
into the existing try block in the runLock test, before the filesystem stat
checks, so any assertion failure still reaches the finally cleanup that restores
directory permissions.

In `@src/lib/shields/timer.ts`:
- Around line 352-364: Update the comment above the lockTarget conditional in
the timer logic to describe that the resolved target is used only when its
configPath, configDir, and optional agentName match the timer arguments;
otherwise persistedLockTarget is retained, while preserving the existing
explanation about sensitiveFiles and content-seal hash completeness.

In `@test/nemoclaw-plugin-secret-pattern-parity.test.ts`:
- Around line 12-16: Strengthen the test in “matches every canonical context
pattern source and flag” by asserting that CONTEXT_SECRET_PATTERNS is non-empty
before comparing fingerprints, while retaining the existing parity assertion
against CONTEXT_PATTERNS.

---

Nitpick comments:
In `@src/lib/shields/index.ts`:
- Around line 2379-2405: Preserve the underlying exceptions in both Deep Agents
rollback catch blocks within the rollback handling branch: capture each caught
error and pass it through the same rollback-issue formatting or cause-preserving
treatment used by openClawRollbackIssue. Apply this consistently to the
state-directory restoration and verifyShieldsLockState catches so the final
CRITICAL diagnostic includes the original failure details instead of only
“command failed.”
🪄 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: 7b434bec-0bbc-4513-8043-43671b1aaa66

📥 Commits

Reviewing files that changed from the base of the PR and between d5b64a7 and 3e3040a.

📒 Files selected for processing (20)
  • docs/reference/commands.mdx
  • docs/reference/troubleshooting.mdx
  • nemoclaw/src/commands/migration-state-security.test.ts
  • nemoclaw/src/commands/migration-state.test.ts
  • nemoclaw/src/commands/migration-state.ts
  • nemoclaw/src/security/credential-filter.test.ts
  • nemoclaw/src/security/credential-filter.ts
  • nemoclaw/src/security/snapshot-sanitizer-failure.test.ts
  • nemoclaw/src/shared/snapshot-sanitizer-boundary.cts
  • scripts/state-dir-guard.py
  • src/lib/shields/flow.test.ts
  • src/lib/shields/index.ts
  • src/lib/shields/policy-transition.test.ts
  • src/lib/shields/seal.test.ts
  • src/lib/shields/seal.ts
  • src/lib/shields/timer-control.ts
  • src/lib/shields/timer.test.ts
  • src/lib/shields/timer.ts
  • test/nemoclaw-plugin-secret-pattern-parity.test.ts
  • test/state-dir-guard.test.ts

Comment thread nemoclaw/src/commands/migration-state-security.test.ts Outdated
Comment thread nemoclaw/src/security/credential-filter.test.ts Outdated
Comment thread nemoclaw/src/security/snapshot-sanitizer-failure.test.ts
Comment thread src/lib/shields/index.ts
Comment thread src/lib/shields/seal.test.ts
Comment thread src/lib/shields/timer.ts Outdated
Comment thread test/nemoclaw-plugin-secret-pattern-parity.test.ts
cv added 3 commits August 3, 2026 05:04
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
cv and others added 2 commits August 3, 2026 06:47
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/lib/shields/policy-transition.test.ts (1)

192-210: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename reportMissingTimerProcess.

reportMissingTimerProcess is the dispatcher. Its default branch reports a running process. The name states the opposite of the default behavior. Use a neutral name, for example respondToProcessKill, and keep reportTimerProcessMissing for the ESRCH handler.

🤖 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 `@src/lib/shields/policy-transition.test.ts` around lines 192 - 210, Rename the
dispatcher function reportMissingTimerProcess to a neutral name such as
respondToProcessKill, updating all references while preserving its lookup and
default-running behavior. Keep reportTimerProcessMissing unchanged as the ESRCH
handler.
src/lib/shields/index.ts (1)

2191-2193: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse the computed deepAgentsProtocol flag.

Line 2148 already stores isDeepAgentsTarget(target) in deepAgentsProtocol. Line 2191 recomputes the same predicate. Use the flag so both the preflight skip at Line 2165 and the lock branch cannot diverge.

♻️ Proposed change
-      if (isDeepAgentsTarget(target)) {
+      if (deepAgentsProtocol) {
         lockDeepAgentsTopConfig(sandboxName, target, !rollbackLocked);
         deepAgentsLockSucceeded = true;
🤖 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 `@src/lib/shields/index.ts` around lines 2191 - 2193, Update the lock branch
around lockDeepAgentsTopConfig to use the existing deepAgentsProtocol flag
instead of recomputing isDeepAgentsTarget(target), keeping the preflight skip
and locking decision consistent.
src/lib/shields/timer.ts (1)

177-181: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the agentName comparison with the path comparisons.

Line 178 requires exact equality for agentName. Lines 179-180 accept an absent marker.configPath or marker.configDir. The marker is the authority in both cases, so the two rules should read the same way.

The mismatch is not reachable today, because Line 170 also requires marker.pid === process.pid, and marker and argv come from the same timer generation. Treat this as consistency only.

♻️ Proposed change
-    marker.agentName === args.agentName &&
+    (marker.agentName === undefined || marker.agentName === args.agentName) &&
🤖 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 `@src/lib/shields/timer.ts` around lines 177 - 181, Update the marker matching
predicate to treat agentName like configPath and configDir: accept an undefined
marker.agentName, or require it to equal args.agentName. Preserve the existing
exact comparisons for leaseOwnerStartIdentity and the path fields.
src/lib/shields/timer.test.ts (1)

55-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not copy the production derivation into the mock.

Lines 55-73 reproduce the sensitiveFiles derivation from src/lib/shields/index.ts Lines 811-819, including the trailing-slash stripping and the Hermes .env entry. If the production rule changes, this mock keeps the old rule and the timer tests still pass, so the lock-target assertions stop proving anything.

src/lib/shields/auto-restore-target.test.ts already covers the derivation against the real resolver. Return a fixed target per scenario here instead, and assert the target that reaches lockAgentConfig rather than the resolver call at Line 706.

♻️ Proposed direction
-    shieldsIndexMock.resolvePersistedAutoRestoreTarget = vi.fn(
-      (
-        _sandboxName: string,
-        marker: { agentName?: string; configPath?: string; configDir?: string },
-      ) =>
-        marker.configPath && marker.configDir
-          ? {
-              ...(marker.agentName ? { agentName: marker.agentName } : {}),
-              configPath: marker.configPath,
-              configDir: marker.configDir,
-              sensitiveFiles: [
-                `${marker.configDir.replace(/\/+$/, "")}/.config-hash`,
-                ...(marker.agentName === "hermes"
-                  ? [`${marker.configDir.replace(/\/+$/, "")}/.env`]
-                  : []),
-              ],
-            }
-          : undefined,
-    );
+    // Each test sets the exact target it expects the timer to lock.
+    shieldsIndexMock.resolvePersistedAutoRestoreTarget = vi.fn(() => undefined);

As per path instructions: "Prefer observable outcomes through the public boundary over source-text, private-shape, or mock-call assertions" and "Flag copied production algorithms".

🤖 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 `@src/lib/shields/timer.test.ts` around lines 55 - 73, Replace the
production-like `sensitiveFiles` derivation inside
`shieldsIndexMock.resolvePersistedAutoRestoreTarget` with fixed
scenario-specific target values, leaving derivation coverage to the real
resolver tests. Update the timer assertions around `lockAgentConfig` to verify
the resolved target passed to that public boundary rather than asserting the
resolver call.

Source: Path instructions

src/lib/shields/auto-restore-target.test.ts (1)

85-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the deny-path test for an incomplete marker.

The guard at src/lib/shields/index.ts Line 809 returns undefined when configPath or configDir is absent. src/lib/shields/timer.ts Lines 337-346 depend on that undefined to fail closed and emit the shields_auto_restore_lock_warning audit record. No test covers it.

Add a case that proves the resolver returns undefined and never consults the registry for an incomplete marker.

💚 Proposed test
  it("returns no target and skips the registry when the marker has no configDir (`#8074`)", () => {
    const resolveConfig = vi.fn();

    expect(
      resolvePersistedAutoRestoreTarget(
        "incomplete-marker",
        { agentName: "openclaw", configPath: "/sandbox/.openclaw/openclaw.json" },
        resolveConfig,
      ),
    ).toBeUndefined();
    expect(resolveConfig).not.toHaveBeenCalled();
  });

Import vi from vitest at Line 4 for this case.

As per path instructions: "Require negative-path tests that prove the boundary rejects bypasses and does not leak secrets in errors, logs, state, or process arguments."

🤖 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 `@src/lib/shields/auto-restore-target.test.ts` around lines 85 - 105, Add a
deny-path test alongside the existing resolvePersistedAutoRestoreTarget tests
for a marker missing configDir, asserting the resolver returns undefined and the
injected registry resolver is not called. Import vi from vitest to create the
mock resolver, preserving the fail-closed behavior without exposing marker
details.

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.

Inline comments:
In `@src/lib/shields/policy-transition.test.ts`:
- Around line 184-190: Update rejectConfigLock to delegate non-lock commands to
runPythonFixtureCommand, while still throwing the supplied failure for
LOCK_COMMAND_KEY. In the rollback test, remove or narrow the
restoreStateDirLockPosture mock so the real state-directory restore executes,
then assert the resulting restored state rather than only verifying a mocked
call.

---

Nitpick comments:
In `@src/lib/shields/auto-restore-target.test.ts`:
- Around line 85-105: Add a deny-path test alongside the existing
resolvePersistedAutoRestoreTarget tests for a marker missing configDir,
asserting the resolver returns undefined and the injected registry resolver is
not called. Import vi from vitest to create the mock resolver, preserving the
fail-closed behavior without exposing marker details.

In `@src/lib/shields/index.ts`:
- Around line 2191-2193: Update the lock branch around lockDeepAgentsTopConfig
to use the existing deepAgentsProtocol flag instead of recomputing
isDeepAgentsTarget(target), keeping the preflight skip and locking decision
consistent.

In `@src/lib/shields/policy-transition.test.ts`:
- Around line 192-210: Rename the dispatcher function reportMissingTimerProcess
to a neutral name such as respondToProcessKill, updating all references while
preserving its lookup and default-running behavior. Keep
reportTimerProcessMissing unchanged as the ESRCH handler.

In `@src/lib/shields/timer.test.ts`:
- Around line 55-73: Replace the production-like `sensitiveFiles` derivation
inside `shieldsIndexMock.resolvePersistedAutoRestoreTarget` with fixed
scenario-specific target values, leaving derivation coverage to the real
resolver tests. Update the timer assertions around `lockAgentConfig` to verify
the resolved target passed to that public boundary rather than asserting the
resolver call.

In `@src/lib/shields/timer.ts`:
- Around line 177-181: Update the marker matching predicate to treat agentName
like configPath and configDir: accept an undefined marker.agentName, or require
it to equal args.agentName. Preserve the existing exact comparisons for
leaseOwnerStartIdentity and the path fields.
🪄 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: 907cd4d8-680b-4272-89cb-0939cec9108f

📥 Commits

Reviewing files that changed from the base of the PR and between 3e3040a and fb05698.

📒 Files selected for processing (13)
  • docs/reference/troubleshooting.mdx
  • nemoclaw/src/commands/migration-state-security.test.ts
  • nemoclaw/src/commands/migration-state.test.ts
  • nemoclaw/src/security/credential-filter.test.ts
  • nemoclaw/src/security/snapshot-sanitizer-failure.test.ts
  • nemoclaw/src/shared/snapshot-sanitizer-boundary.cts
  • src/lib/shields/auto-restore-target.test.ts
  • src/lib/shields/index.ts
  • src/lib/shields/policy-transition.test.ts
  • src/lib/shields/seal.test.ts
  • src/lib/shields/timer.test.ts
  • src/lib/shields/timer.ts
  • test/nemoclaw-plugin-secret-pattern-parity.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • nemoclaw/src/security/credential-filter.test.ts
  • nemoclaw/src/shared/snapshot-sanitizer-boundary.cts
  • nemoclaw/src/security/snapshot-sanitizer-failure.test.ts
  • nemoclaw/src/commands/migration-state.test.ts
  • docs/reference/troubleshooting.mdx

Comment thread src/lib/shields/policy-transition.test.ts
Comment thread nemoclaw/src/commands/migration-state-security.test.ts Fixed
Comment thread nemoclaw/src/security/snapshot-sanitizer-failure.test.ts Fixed
cv and others added 5 commits August 3, 2026 07:09
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@apurvvkumaria apurvvkumaria added the v0.0.102 Release target label Aug 3, 2026
@apurvvkumaria apurvvkumaria self-assigned this Aug 3, 2026
@cv
cv merged commit a7237ea into main Aug 3, 2026
77 of 78 checks passed
@cv
cv deleted the codex/postmerge-security-hardening-7995-7847 branch August 3, 2026 23:48
@wscurran wscurran added area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery area: security Security controls, permissions, secrets, or hardening area: skills Skills, agent behaviors, prompts, or skill packaging bug-fix PR fixes a bug or regression integration: dcode LangChain Deep Code integration behavior labels Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery area: security Security controls, permissions, secrets, or hardening area: skills Skills, agent behaviors, prompts, or skill packaging bug-fix PR fixes a bug or regression integration: dcode LangChain Deep Code integration behavior v0.0.102 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants