fix(shields): keep UP when Hermes config path is unsafe (#8804) - #8830
Conversation
Refuse unsafe Hermes config paths before shields-down weakens policy, and clear a provisional DOWN state if unlock still fails so status stays UP. Signed-off-by: Aarav Sharma <aarav1109s@gmail.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:
📝 WalkthroughWalkthrough
ChangesHermes Shields safety
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ShieldsDown
participant ConfigPathPreflight
participant SandboxFilesystem
participant ShieldState
ShieldsDown->>ConfigPathPreflight: Validate Hermes configuration paths
ConfigPathPreflight->>SandboxFilesystem: Open protected paths without following symlinks
SandboxFilesystem-->>ConfigPathPreflight: Return path safety result
ConfigPathPreflight-->>ShieldsDown: Return validation result
ShieldsDown->>ShieldState: Apply or retain transition state
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
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 `@src/lib/shields/index.ts`:
- Around line 835-849: Update the config validation flow around open_nofollow to
keep the trusted config_dir descriptor open through protected-file checks, and
open each file using descriptor-relative no-follow traversal rather than
absolute paths. Close the directory descriptor only after all checks complete,
preserving existing rejection of non-directories and non-regular files; add
coverage for replacing config_dir with a symlink while its final file remains
regular.
🪄 Autofix
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: 2a8037ca-aa4e-4224-bc96-45a69fc91776
📒 Files selected for processing (3)
docs/reference/commands.mdxsrc/lib/shields/index.tssrc/lib/shields/openclaw-transition.test.ts
PR Review Advisor — No blocking findings reportedAdvisor assessment: No blocking advisor findings reported Model lanes
1 additional E2E selection from the second opinionAdvisory only. The primary lane did not select these E2E jobs or targets.
Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests. 3 semantic terminology decisionsTerminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.
E2E guidanceAdvisory only. A maintainer can dispatch the default E2E suite for the commit under review. Recommended E2E: None Manual-only E2E: 1 optional E2E recommendation
This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge. |
Only clear provisional DOWN when unlock never completed, open preflight files through a held config dir fd, and cover unlock-then-unsafe re-lock. Signed-off-by: Aarav Sharma <aarav1109s@gmail.com>
Use NodeJS.Require so the helper typechecks without a createRequire seam. Signed-off-by: Aarav Sharma <aarav1109s@gmail.com>
cv
left a comment
There was a problem hiding this comment.
test/helpers/hermes-unsafe-config-shields-harness.ts:99-100 still injects the expected symlink refusal instead of executing the Python no-follow preflight added in src/lib/shields/index.ts. This bypasses the component that enforces the new filesystem security boundary. A regression in descriptor-relative traversal, O_NOFOLLOW, or file-type validation could pass src/lib/shields/openclaw-transition.test.ts:789 and let shields down weaken policy for an unsafe path. Add a filesystem-backed regression test that executes the embedded preflight against a real Hermes config symlink and a replaced config-directory symlink. Verify that policy, timer, audit, and Shields state remain unchanged.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
test/helpers/hermes-unsafe-config-shields-harness.ts (1)
309-316: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse the existing convergence spy.
beforeEachHookalready spies onwaitForHermesInferenceRouteConvergence. Update that spy withmockReturnValueinstead of callingvi.spyOnagain.🤖 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/helpers/hermes-unsafe-config-shields-harness.ts` around lines 309 - 316, Update failHermesInferenceConvergence to reuse the waitForHermesInferenceRouteConvergence spy created by beforeEachHook, calling mockReturnValue on that existing spy instead of invoking vi.spyOn again. Preserve the current failed convergence response values.
🤖 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/openclaw-transition.test.ts`:
- Around line 818-823: Update the rollback test around
expectHermesShieldsUpRecord to also assert getShieldsPosture("hermes-shields",
false), matching the preflight test, and verify that Hermes is restored to a
locked, immutable posture while retaining the existing state-record and cleanup
assertions.
In `@test/helpers/hermes-unsafe-config-shields-harness.ts`:
- Around line 56-64: Update isPathPreflight and the related preflight handling
in the harness so detection uses a shared production marker or an explicit
assertion of the expected marker, rather than silently falling through when the
marker changes. Ensure marker mismatches fail the relevant scenarios, including
unlock-symlink and unlock-ok-relock-symlink, instead of allowing prior(cmd) to
return a default successful result.
- Around line 173-175: Update the cache cleanup in the harness teardown hooks to
use requireSource.cache with requireSource.resolve(...) instead of bare
require.cache, including the cleanup around indexModule and the corresponding
location near line 291. Ensure afterEachHook also removes timer-bound-lock.js
and transition-lock.js so all harness modules are cleared after each test.
- Around line 242-271: Update the vi.spyOn(childProcess, "fork") mock to satisfy
the ChildProcess return type by either adding the required members such as
stdin, stdout, stderr, and stdio to the fake or explicitly casting the
intentional test double to ChildProcess. Preserve the existing pid, lifecycle
methods, and send authorization behavior.
---
Nitpick comments:
In `@test/helpers/hermes-unsafe-config-shields-harness.ts`:
- Around line 309-316: Update failHermesInferenceConvergence to reuse the
waitForHermesInferenceRouteConvergence spy created by beforeEachHook, calling
mockReturnValue on that existing spy instead of invoking vi.spyOn again.
Preserve the current failed convergence response values.
🪄 Autofix
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: 63070be8-b83e-4ecc-905f-9dd57b93c120
📒 Files selected for processing (4)
docs/reference/commands.mdxsrc/lib/shields/index.tssrc/lib/shields/openclaw-transition.test.tstest/helpers/hermes-unsafe-config-shields-harness.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/reference/commands.mdx
- src/lib/shields/index.ts
|
Good catch, Addressed the requested changes in commit
Validation passed: four focused Hermes security tests, strict CLI type checking, repository checks, source-shape checks, and test-size checks. GitHub growth guardrails pass, and CodeRabbit reports no new actionable comments. |
…ds-unsafe-config-path
|
The remaining Nemotron check failed because the NVIDIA inference endpoint returned HTTP 429 (shared rate limit exhausted), not because of the PR changes. Could a maintainer please rerun the failed job once the limit resets? |
|
I inspected the failed advisor artifact before retrying. Although the advisor itself failed canonical-receipt validation, it preserved one actionable finding: missing sensitive-file preflight coverage. Commit 1089696 adds a real |
prekshivyas
left a comment
There was a problem hiding this comment.
The new configUnlocked flag proves only that unlockAgentConfig() returned; false does not prove that no mutation occurred. The unlock path can fail after a state-directory or top-level mutation has started, and its internal rollback can also fail when a protected path is replaced or disappears. rollbackShieldsDown() then sees configUnlocked=false plus an unsafe-path relock error, restores the pre-transition state record, and reports UP without independently proving that every protected path is locked. Keep the provisional DOWN/manual-intervention state unless lockdown is positively re-verified, or return explicit rollback proof from the unlock transaction. Add a regression where the unsafe replacement occurs after mutation begins and verify status never reports UP for a partially unlocked tree.
Signed-off-by: Aarav Sharma <aarav1109s@gmail.com>
|
Addressed the rollback-proof concern in d067893. The provisional DOWN state is now cleared only after re-lock succeeds and is re-confirmed; an unlock exception is no longer treated as proof that no mutation occurred. If rollback remains incomplete, the transition is persisted as active so status remains DOWN/manual-intervention instead of being reinterpreted as UP. Added a regression covering an unsafe replacement after state-directory mutation begins, including failed internal rollback and failed outer re-lock. Focused #8804 tests pass (6/6), CLI typecheck, repository checks, formatting, and test-size checks pass. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/shields/index.ts (1)
5104-5122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract incomplete-transition persistence into a helper.
Lines 5104-5122 add nested transition persistence to
shieldsDownWithoutHostLock. This function already coordinates policy, timer, state, and provider operations. Move this branch into a focused helper that returns or reports the persistence result.As per coding guidelines,
**/*.{ts,tsx,js,jsx}requires: “Keep function complexity low.”🤖 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 5104 - 5122, Extract the guarded incomplete-transition persistence branch from shieldsDownWithoutHostLock into a focused helper that accepts the required transition, timer authority, rollback, and sandbox context, performs both freshness assertions and writeShieldsDownTransition calls, and returns or reports success/failure. Replace the inline branch with a call to this helper while preserving the existing conditions and critical error reporting.Source: Coding guidelines
🤖 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 `@src/lib/shields/index.ts`:
- Around line 5104-5122: Extract the guarded incomplete-transition persistence
branch from shieldsDownWithoutHostLock into a focused helper that accepts the
required transition, timer authority, rollback, and sandbox context, performs
both freshness assertions and writeShieldsDownTransition calls, and returns or
reports success/failure. Replace the inline branch with a call to this helper
while preserving the existing conditions and critical error reporting.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 2632cd9b-14e2-4793-82d7-c31e558c8c36
📒 Files selected for processing (4)
docs/reference/commands.mdxsrc/lib/shields/index.tssrc/lib/shields/openclaw-transition.test.tstest/helpers/hermes-unsafe-config-shields-harness.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/reference/commands.mdx
- test/helpers/hermes-unsafe-config-shields-harness.ts
|
The Nemotron PR Review Advisor failed because its SDK exceeded the 15-minute timeout after an earlier connection-refused transport error. The GPT-5.6 advisor, advisor publishing, CodeRabbit, and growth guardrails all passed, so this appears to be CI/infrastructure-related rather than a PR failure. Could a maintainer please rerun the failed Nemotron job? |
The latest PR commit executes the production preflight against real unsafe-path fixtures and verifies that protected state does not change. Dismissing this resolved review before a full current-commit review.
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
Review-advisor follow-up:
Fresh CI and automated review are running. Security review, human approval, and all required checks remain required. |
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
The failed CLI shards exposed two test fixtures that did not model the new Shields preflight boundary.
|
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
Follow-up for the growth guardrail: the preflight setup now uses the test file’s existing linear The 91 focused Shields tests pass with ordinary host process access. Their failures under the restricted local runner were caused by denied child-process identity inspection; no production or additional test behavior was changed in response. |
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
The remaining static-check failure was PR-related: the unsafe-path preflight added two points to an already constrained Shields transition function. I moved the existing failure handling into a dedicated helper without changing the preflight order, emitted guidance, or termination behavior. Biome now passes, the three focused Shields suites pass (99 tests), repository hooks pass, and the documentation follow-up found no user-facing change. The shard 7 failure was a timeout in an unrelated OpenClaw transition hook after prolonged runner activity; this push provides a fresh run rather than retrying that stale workflow. |
|
Sensitive-path security review completed for commit under review
Validation: all required CI checks pass; the focused Shields suites pass 99 tests; CLI type checking, repository checks, source/test budgets, documentation validation, and secret scanning pass. Size flag: this is a large review surface at 714 additions and 4 deletions across six files. Most of the increase is the typed adversarial test harness and regression coverage. |
prekshivyas
left a comment
There was a problem hiding this comment.
Reviewed the current head, config-path preflight change, required CI, advisor feedback, and resolved threads. Required checks are green and no blocking code findings remain. The incomplete second-opinion lane is a nonblocking maintainer-disposition item.
## Summary Restore the checks that currently fail on `main`. This change preserves Shields behavior while reducing its lint complexity, gives the coverage-instrumented setup enough time, and moves the Deep Agents Code image contract below the repository's test-file limit. The regressions entered through #8821, #8830, and #8937 after those PRs passed against stale base commits. ## Changes - Extract incomplete Shields-down posture persistence from `shieldsDownWithoutHostLock` without changing rollback state transitions or recovery output. - Allow 30 seconds for the OpenClaw transition setup hook under V8 coverage. - Move the existing Deep Agents Code `dos2unix` contract into its focused base-image test and protect its digest, executable, and step-order requirements. - Select the DCode and OpenClaw contract tests when the base-image platform action changes. ## 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 - [ ] Tests added or updated for changed behavior - [x] Existing tests cover changed behavior — justification: the existing Shields unsafe-path rollback tests protect the extracted state transitions, and the moved Deep Agents Code workflow contract protects its existing digest, executable, security-option, and step-order requirements. - [ ] Tests not applicable — justification: - [ ] Docs updated for user-facing behavior changes - [x] Docs not applicable — justification: this PR changes internal structure, test organization, a test timeout, and local test selection; product behavior and user-visible text are 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: the fail-closed Shields rollback state transitions and unchanged recovery output were reviewed against the security rubric; all seven unsafe-path rollback tests pass under coverage. - [ ] 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: `no-docs-needed` - Evidence: Commit `5bf13daab` changes no public API, CLI command, configuration, UI, runtime default, user-visible error, image content, publication behavior, or supported workflow. Existing documentation already covers `dos2unix` in the Deep Agents Code image. - Agent: Codex Desktop <!-- docs-review-head-sha: 5bf13da --> <!-- docs-review-agents-blob-sha: c4923a3 --> ## DGX Station Hardware Evidence - [ ] Tested on DGX Station - Tested commit: - Station profile/scenario: - Result: - Supporting evidence: ## 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: 12 OpenClaw top-config tests and 7 Hermes unsafe-path tests pass under V8 coverage; 26 managed-image and watch-trigger tests pass; `npm run test-size:check`, `npm run typecheck:cli`, and `npm run checks:repository` pass. - [ ] 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) --- Signed-off-by: San Dang <sdang@nvidia.com>
Summary
shields downcould reject an unsafe Hermes config symlink and still leave Shields DOWN with a timer. It now refuses unsafe config paths before weakening policy, and if unlock fails after a provisional DOWN record, it restores the restrictive policy and clears that provisional state so status stays UP.Related Issue
Fixes #8804
Changes
shields downstarts the timer, writes provisional DOWN, or applies permissive policy. Keep the config directory fd open and open protected files relative to it.docs/reference/commands.mdx.Type of Change
Quality Gates
Documentation Writer Review
docs-updateddocs/reference/commands.mdxDGX Station Hardware Evidence
Verification
Signed-off-by:line and every commit appears asVerifiedin GitHubpre-commit,commit-msg, andpre-pushhooks passed, ornpm run validate:prpassed after refreshingorigin/mainwhen hooks were skipped or unavailablenpx vitest run --project cli src/lib/shields/openclaw-transition.test.ts -t '8804'→ 3 passed;npx vitest run --project cli src/lib/shields/policy-transition.test.ts -t '8198'→ 3 passed;npm run test-size:checkandnpm run checks:repositorypassednpm testfor broad runtime/test-harness changes;npm run checkfor repo-wide validation/coverage changes — command/result:npm run docsbuilds without warnings (doc changes only)Change size
This is a large review surface: 714 additions and 4 deletions across six files. Most of the increase is the typed adversarial test harness and regression coverage.
Signed-off-by: Aarav Sharma aarav1109s@gmail.com
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
shields downsafeguards and failure behavior.