Skip to content

fix(shields): reconcile startup API key on first Hermes shields down (#6381) - #6384

Closed
yanyunl1991 wants to merge 6 commits into
mainfrom
fix/hermes-shields-strict-hash-reconcile-6381
Closed

fix(shields): reconcile startup API key on first Hermes shields down (#6381)#6384
yanyunl1991 wants to merge 6 commits into
mainfrom
fix/hermes-shields-strict-hash-reconcile-6381

Conversation

@yanyunl1991

@yanyunl1991 yanyunl1991 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

On a freshly built, OpenShell-managed (non-root) Hermes sandbox, nemohermes <sandbox> shields down always failed with [SECURITY] strict hash verification failed for Hermes restart seal, and shields up then failed with Config not locked: parent dir mode=755 (expected 1775), blocking the entire Hermes shields lifecycle that MCP mutations depend on. This PR fixes both so a full down → up cycle works on a fresh sandbox.

Closes #6381.

Reproduction

On our Ubuntu 24.04 x86_64 test host (no GPU), against a fresh Hermes sandbox onboarded non-interactively from latest main:

nemoclaw onboard --name <sb> --agent hermes --non-interactive --yes --no-gpu
nemohermes <sb> shields down --timeout 15m
nemohermes <sb> shields up

Environment

  • Our Ubuntu 24.04 x86_64 test host (no GPU); NemoClaw main; Hermes agent, OpenShell-managed (non-root) topology (no /run/nemoclaw/hermes-root-lifecycle marker).

Smoking gun (captured before running shields down) — the root-owned strict anchor /etc/nemoclaw/hermes.config-hash carries the stale build-time .env digest, while the in-tree compat anchor .config-hash and the actual .env agree on the current digest; config.yaml is byte-identical across both anchors. The only difference is the single 64-hex API_SERVER_KEY minted at first startup.

Observed on main (before fix)

$ nemohermes <sb> shields down --timeout 15m ; echo EXIT=$?
  [SECURITY] strict hash verification failed for Hermes restart seal
EXIT=1
$ nemohermes <sb> shields up ; echo EXIT=$?
  ERROR: Config not locked: parent dir mode=755 (expected 1775), parent dir owner=root:root (expected root:sandbox)
EXIT=1

Observed on fix/... (after fix) — a full cycle succeeds:

$ nemohermes <sb> shields down --timeout 15m ; echo EXIT=$?   # -> EXIT=0, config unlocked; strict anchor reconciled to current
$ nemohermes <sb> shields up ; echo EXIT=$?                    # -> EXIT=0; /sandbox=1775 root:sandbox, /sandbox/.hermes=755 root:root
$ nemohermes <sb> shields down --timeout 15m ; echo EXIT=$?   # -> EXIT=0
$ nemohermes <sb> shields up ; echo EXIT=$?                    # -> EXIT=0

Analysis

OpenShell launches the Hermes entrypoint as the sandbox user (agents/hermes/start.sh takes the non-root branch; the root-only /run/nemoclaw/hermes-root-lifecycle marker is never written). That non-root startup mints the per-sandbox API_SERVER_KEY into .env and refreshes the in-tree compat hash anchor, but cannot write the root-owned strict anchor /etc/nemoclaw/hermes.config-hash, which keeps its build-time digest.

  1. shields down runs begin-shields-transition (as root) → seal_restart(purpose="shields-mutable"), which verifies the current config/env against the stale strict anchor via _verify_strict_hash and fails on every fresh non-root sandbox. The guard already reconciles this exact drift for the config-write purpose via _reconcile_nonroot_startup_api_key_hash, but shields-mutable was not wired to use it.
  2. shields up then failed on a separate, adjacent issue: for the sealed Hermes transaction the parent (/sandbox) posture 1775 root:sandbox is deliberately the last persistent change and is applied by finish-shields-transition (the guard keeps /sandbox root-owned as its crash-consistency orphan marker until finish). lockAgentConfigUnderMutationLock verified parent protection between apply and finish, so it always saw the frozen 755 root:root posture and reported a false lock failure.

Fix

  • agents/hermes/runtime-config-guard.py: allow seal_restart's StrictHashMismatchError recovery for the shields-mutable purpose (not just config-write) when an expected config digest is supplied; thread a new optional expected_config_sha256 through begin_shields_transition; accept/validate --expected-config-sha256 (64-hex) in the CLI handler. The reconciliation itself is unchanged — it advances the strict anchor only when the compat anchor corroborates the frozen inputs, config.yaml is unchanged, and the sole .env delta is the single generated API_SERVER_KEY; every other difference is refused, and the posture must be mutable/never-locked.
  • src/lib/shields/index.ts:
    • When beginning a mutable Hermes transition, read the current config.yaml digest and pass it as --expected-config-sha256. Absent the digest the stale anchor still fails closed, so reconciliation is opt-in.
    • Defer parent-protection verification for the sealed Hermes lock to a post-finish re-verify, where the 1775 root:sandbox posture is actually in place. Locked files, config-dir mode, and chattr are still checked before finish; OpenClaw and legacy-Hermes paths (no sealed transaction) keep the inline check.

Tests: test/hermes-nonroot-strict-hash-reconciliation.test.ts gains three shields-down cases (reconciles the startup key and completes the transition; still fails closed with no digest; refuses config drift), reusing the existing fixture that already locks down the reconciliation's other refusals. The shields-up ordering fix is validated end-to-end on the test host (full down → up → down → up cycle) and against the existing shields unit suite (no regressions).

Changes

  • agents/hermes/runtime-config-guard.py: enable the existing non-root API-key reconciliation for the shields-mutable transition.
  • src/lib/shields/index.ts: pass the config digest to begin-shields-transition for mutable transitions; verify Hermes parent lock posture after finish.
  • test/hermes-nonroot-strict-hash-reconciliation.test.ts: cover the shields-down reconcile, fail-closed, and config-drift-refusal cases.

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)

Verification

  • npx prek run --all-files passes (on changed files)
  • npm test passes (touched files at minimum)
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

AI Disclosure

  • AI-assisted — tool: Claude Code

Signed-off-by: Yanyun Liao yanyunl@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added optional expected configuration SHA-256 support for shields transitions to enable controlled reconciliation during mutable transitions.
  • Bug Fixes

    • Strengthened strict-hash handling to remain fail-closed when the expected SHA is missing or when config drift is detected.
    • Improved sealed Hermes lock verification by performing a final parent-safety recheck after transition completion.
  • Tests

    • Added Hermes non-root reconciliation tests covering success, missing expected-SHA failure, and refusal on config-drift.

…6381)

On a freshly built, OpenShell-managed (non-root) Hermes sandbox, `nemohermes
<sandbox> shields down` always failed with "strict hash verification failed
for Hermes restart seal", and the aborted transition left /sandbox/.hermes in
a broken state that then blocked shields up too.

Root cause: OpenShell launches the Hermes entrypoint as the sandbox user. That
non-root startup mints the per-sandbox API_SERVER_KEY into .env and refreshes
the in-tree compatibility hash anchor, but it cannot advance the root-owned
strict anchor at /etc/nemoclaw/hermes.config-hash, which stays at its
build-time digest. The sealed shields "mutable" transition (shields down)
verifies the current config against that stale strict anchor and fails on
every fresh non-root sandbox.

The guard already knows how to reconcile exactly this drift: seal_restart's
`config-write` purpose calls _reconcile_nonroot_startup_api_key_hash, which
advances the strict anchor only when the sole difference is the single
generated API_SERVER_KEY, config.yaml is unchanged, and the compat anchor
corroborates the frozen inputs — refusing every other config or env change.
This change lets the `shields-mutable` purpose use that same reconciliation:
begin-shields-transition now accepts --expected-config-sha256 and threads it to
seal_restart, and the host passes the current config.yaml digest when starting
a mutable transition. Without the digest the stale anchor still fails closed,
so the reconciliation stays opt-in and every existing refusal is preserved.

Note: shields up on a fresh non-root Hermes sandbox has a separate, pre-existing
host-side lock-verification ordering issue (it reports "parent dir mode=755
(expected 1775)" even though the locked posture is ultimately applied). That is
independent of this strict-hash fix and is not addressed here.

Fixes #6381

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 7, 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: d37d24db-4ace-42ea-98f6-b040d837bac7

📥 Commits

Reviewing files that changed from the base of the PR and between e875cc4 and 3491c03.

📒 Files selected for processing (1)
  • test/hermes-nonroot-strict-hash-reconciliation.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/hermes-nonroot-strict-hash-reconciliation.test.ts

📝 Walkthrough

Walkthrough

Hermes shields transitions now accept and validate an expected config SHA-256 for mutable starts, pass it through the CLI and client wiring, adjust post-finish parent verification, and add tests for reconciliation success and failure cases.

Changes

Shields-down reconciliation

Layer / File(s) Summary
Runtime guard: reconciliation condition and parameter plumbing
agents/hermes/runtime-config-guard.py
seal_restart() now allows strict-hash reconciliation for config-write and shields-mutable only when expected_config_sha256 is present, and begin_shields_transition() forwards that value into seal_restart().
CLI wiring for --expected-config-sha256
agents/hermes/runtime-config-guard.py
main() normalizes --expected-config-sha256 to None when absent, validates it as a 64-hex digest when present, and passes it into begin_shields_transition().
Shields client: compute and pass expected config digest
src/lib/shields/index.ts
beginHermesConfigShields now builds extraArgs for begin-shields-transition, always includes --hash-file, and conditionally adds --expected-config-sha256 after hashing target.configPath in the privileged sandbox.
Post-finish parent verification
src/lib/shields/index.ts
lockAgentConfigUnderMutationLock defers parent-protection verification during an active Hermes transaction, clears transaction after finishHermesConfigShields, and reruns verifyShieldsLockState with parent protection enabled.
Tests for shields-down reconciliation
test/hermes-nonroot-strict-hash-reconciliation.test.ts
Adds helpers for invoking begin-shields-transition and three #6381 cases covering successful reconciliation, fail-closed behavior without an expected digest, and refusal of config drift.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as nemohermes CLI
  participant Client as beginHermesConfigShields
  participant Sandbox as privileged sandbox
  participant Guard as runtime-config-guard.py

  CLI->>Client: begin shields transition (mutable)
  Client->>Sandbox: hash target.configPath
  Sandbox-->>Client: SHA-256 digest
  Client->>Guard: begin-shields-transition --hash-file --expected-config-sha256
  Guard->>Guard: seal_restart() checks StrictHashMismatchError
  alt purpose=shields-mutable and digest present
    Guard-->>Client: reconcile
  else digest missing or config drift
    Guard-->>Client: fail closed
  end
Loading

Suggested labels: bug-fix

Suggested reviewers: ericksoa, jyaunches, prekshivyas, cjagwani

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #39 is not addressed; the PR adds no hindsight-memory skill files, CLI reference, or policy template required by that linked issue. Add the hindsight-memory skill documentation, CLI reference, and network policy template, or remove #39 from the linked issues if it is unrelated.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the Hermes shields-down reconciliation fix and matches the main change set.
Linked Issues check ✅ Passed Issue #6381 is satisfied: the PR reconciles mutable shields startup drift, threads the expected config digest, and adds coverage for success and fail-closed cases.
Out of Scope Changes check ✅ Passed The changes stay focused on Hermes shields reconciliation and related tests, with no unrelated feature work introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hermes-shields-strict-hash-reconcile-6381

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

@github-code-quality

github-code-quality Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in the fix/hermes-shields-s... branch is 96%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/hermes-shields-s... 33b1205 +/-
nemoclaw/src/se...cret-scanner.ts 100%
nemoclaw/src/commands/slash.ts 100%
nemoclaw/src/li...bprocess-env.ts 100%
nemoclaw/src/bl...eprint/state.ts 98%
nemoclaw/src/onboard/config.ts 98%
nemoclaw/src/bl...int/snapshot.ts 97%
nemoclaw/src/bl...print/runner.ts 95%
nemoclaw/src/co...ration-state.ts 94%
nemoclaw/src/bl...ate-networks.ts 94%
nemoclaw/src/index.ts 94%

TypeScript / code-coverage/cli

The overall coverage in the fix/hermes-shields-s... branch is 75%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/hermes-shields-s... 33b1205 +/-
src/lib/shields...nsition-lock.ts 85%
src/lib/onboard/preflight.ts 83%
src/lib/actions...all/run-plan.ts 81%
src/lib/state/o...oard-session.ts 80%
src/lib/actions...licy-channel.ts 79%
src/lib/state/sandbox.ts 75%
src/lib/onboard...er-gpu-patch.ts 69%
src/lib/policy/index.ts 66%
src/lib/shields/index.ts 58%
src/lib/onboard.ts 28%

Updated July 07, 2026 17:14 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: hermes-shields-config
Optional E2E: hermes-e2e, hermes-sandbox-secret-boundary

Dispatch hint: hermes-shields-config

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • hermes-shields-config (medium): Direct regression coverage for this change: fresh non-root Hermes onboard, startup API key minting, first shields-down strict-hash reconciliation, and two complete shields down/up cycles.

Optional E2E

  • hermes-e2e (medium): Useful broader confidence that the Hermes sandbox still onboards, reaches Ready, and supports normal Hermes assistant lifecycle behavior after runtime guard and shields orchestration changes.
  • hermes-sandbox-secret-boundary (medium): Adjacent confidence for the API_SERVER_KEY/startup secret boundary and Hermes production image behavior, since the PR changes reconciliation of startup-minted secret material.

New E2E recommendations

  • None.

Dispatch hint

  • Workflow: .github/workflows/e2e.yaml
  • jobs input: hermes-shields-config

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Recommendation

Required E2E targets: None
Optional E2E targets: hermes-gpu-startup

Workflow run

Full E2E target advisor summary

E2E Target Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E targets

  • None.

Optional E2E targets

  • hermes-gpu-startup: Optional adjacent coverage for Hermes startup on the special GPU runner; the change is not GPU-specific, so this is not required unless maintainers want platform-specific startup validation.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=hermes-gpu-startup

Relevant changed files

  • agents/hermes/runtime-config-guard.py
  • src/lib/shields/index.ts

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Changes requested

Merge posture: Do not merge yet
Primary next action: Resolve or justify PRA-1: Source-of-truth review needed: Hermes managed non-root strict-anchor reconciliation for `shields-mutable`.
Open items: 0 required · 4 warnings · 1 suggestion · 8 test follow-ups
Since last review: 0 prior items resolved · 5 still apply · 0 new items found

Action checklist

  • PRA-1 Resolve or justify: Source-of-truth review needed: Hermes managed non-root strict-anchor reconciliation for `shields-mutable`
  • PRA-2 Resolve or justify: Host shields-down digest forwarding is still not directly asserted in src/lib/shields/index.ts:498
  • PRA-3 Resolve or justify: Hermes shields-up parent verification ordering lacks host-level regression coverage in src/lib/shields/index.ts:2028
  • PRA-4 Resolve or justify: Source-of-truth note for strict-anchor reconciliation is incomplete in agents/hermes/runtime-config-guard.py:2660
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Runtime validation
  • PRA-T5 Add or justify test follow-up: Runtime validation
  • PRA-T6 Add or justify test follow-up: Host shields-down digest forwarding is still not directly asserted
  • PRA-T7 Add or justify test follow-up: Acceptance clause
  • PRA-T8 Add or justify test follow-up: Acceptance clause
  • PRA-5 In-scope improvement: Shrink the growing shields hotspot where the new security-coupled helpers permit in src/lib/shields/index.ts

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Resolve/justify tests src/lib/shields/index.ts:498 Update the existing mocked Hermes shields-down host test, or add a focused nearby host test, so the mock returns a valid `sha256sum /sandbox/.hermes/config.yaml` line and the captured `begin-shields-transition` command is asserted to include `--expected-config-sha256 <64-hex>`.
PRA-3 Resolve/justify acceptance src/lib/shields/index.ts:2028 Add a focused mocked `lockAgentConfig()` Hermes test that reports `/sandbox` as `755 root:root` before `finish-shields-transition`, reports `1775 root:sandbox` after finish, and asserts the host invokes finish before the parent-protection verification failure point.
PRA-4 Resolve/justify docs agents/hermes/runtime-config-guard.py:2660 Add one concise sentence near the `shields-mutable` reconciliation branch or `_reconcile_nonroot_startup_api_key_hash()` docstring stating that this path should be removed or disabled once startup can atomically update the root-owned strict anchor safely, or once the managed non-root startup topology is retired. If the source cannot be fixed in this PR for another concrete reason, state that reason in the same note.
PRA-5 Improvement architecture src/lib/shields/index.ts If feasible in this PR, extract the mutable-transition digest preparation and post-finish Hermes parent reverify into small local helpers, or trim duplicated explanatory text while preserving the security invariants in comments.
Review findings by urgency: 0 required fixes, 4 items to resolve/justify, 1 in-scope improvement

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Source-of-truth review needed: Hermes managed non-root strict-anchor reconciliation for `shields-mutable`

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Changed tests cover first shields-down reconciliation, missing digest fail-closed behavior, and config drift refusal; existing tests cover managed topology, mutable posture, locked-posture refusal, stale compat hash, malformed hashes, path binding, non-API-key env drift, and stale host digest checks.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: `seal_restart()` now allows `_reconcile_nonroot_startup_api_key_hash()` for `shields-mutable`; the helper still refuses unmanaged topology, non-mutable posture, config drift, compat mismatch, and any env drift beyond the single generated API key.

PRA-2 Resolve/justify — Host shields-down digest forwarding is still not directly asserted

  • Location: src/lib/shields/index.ts:498
  • Category: tests
  • Problem: The TypeScript host path now runs `sha256sum` on `target.configPath`, parses the first token, and conditionally appends `--expected-config-sha256` to `begin-shields-transition`. The changed tests call `agents/hermes/runtime-config-guard.py begin-shields-transition` directly, so they prove the guard works when a digest is supplied but not that `nemohermes shields down` supplies it. The nearby mocked host test in `test/repro-2681-group-writable.test.ts` still only asserts `--shields-mode mutable` / rollback args.
  • Impact: A regression in command ordering, digest parsing, or argument plumbing could make fresh Hermes `shields down` fail again with a stale strict anchor while the new guard-level reconciliation tests continue passing.
  • Recommended action: Update the existing mocked Hermes shields-down host test, or add a focused nearby host test, so the mock returns a valid `sha256sum /sandbox/.hermes/config.yaml` line and the captured `begin-shields-transition` command is asserted to include `--expected-config-sha256 <64-hex>`.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read `test/repro-2681-group-writable.test.ts` around the `shields-down restores Hermes sticky group-writable config root without group-writable config files` test and confirm it captures the `sha256sum` command plus a `begin-shields-transition` argv containing the parsed digest.
  • Missing regression test: Add `Hermes shields-down passes parsed config sha256 to begin-shields-transition`; also consider `Hermes shields-down omits expected-config-sha256 and fails closed when sha256sum output is unparsable` to pin the fail-closed fallback.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read `test/repro-2681-group-writable.test.ts` around the `shields-down restores Hermes sticky group-writable config root without group-writable config files` test and confirm it captures the `sha256sum` command plus a `begin-shields-transition` argv containing the parsed digest.
  • Evidence: `beginHermesConfigShields()` adds `privilegedSandboxExecCapture(sandboxName, ["sha256sum", target.configPath])` and `extraArgs.push("--expected-config-sha256", configSha)`, while the added tests use `runManagedNonrootBegin()` to invoke the Python guard with `beginShieldsArgs()` directly.

PRA-3 Resolve/justify — Hermes shields-up parent verification ordering lacks host-level regression coverage

  • Location: src/lib/shields/index.ts:2028
  • Category: acceptance
  • Problem: Issue [Ubuntu 24.04][Sandbox] nemohermes shields down fails on fresh Hermes sandbox — strict hash verification failed for Hermes restart seal #6381's Step 6 failure is a host-side ordering problem: `/sandbox` can still be `755 root:root` before `finish-shields-transition` commits the final `1775 root:sandbox` posture. The patch defers parent verification while a sealed Hermes transaction is active, finishes the transaction, clears it, and then re-runs parent verification, but no changed host test proves that sequence.
  • Impact: A later refactor could reintroduce the pre-finish parent check or accidentally drop the post-finish parent recheck, making `nemohermes shields up` fail again or silently skip a sandbox parent-posture check while guard-only tests still pass.
  • Recommended action: Add a focused mocked `lockAgentConfig()` Hermes test that reports `/sandbox` as `755 root:root` before `finish-shields-transition`, reports `1775 root:sandbox` after finish, and asserts the host invokes finish before the parent-protection verification failure point.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect `src/lib/shields/index.ts` around `deferParentProtectionToFinish` and the post-finish `verifyShieldsLockState(... verifyParentProtection: true)` call, then check nearby host tests for a stateful `/sandbox` stat mock and command-order assertion.
  • Missing regression test: Add `Hermes shields-up defers parent protection verification until after finish-shields-transition`; include a stateful stat mock so pre-finish parent verification would fail and post-finish verification passes. Add `Hermes shields-up post-finish parent verification failure does not attempt abort of released transaction` if the failure path is easy to isolate.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect `src/lib/shields/index.ts` around `deferParentProtectionToFinish` and the post-finish `verifyShieldsLockState(... verifyParentProtection: true)` call, then check nearby host tests for a stateful `/sandbox` stat mock and command-order assertion.
  • Evidence: The diff changes the first `verifyShieldsLockState()` call to disable parent protection when `transaction != null`, calls `finishHermesConfigShields()`, sets `transaction = null`, then performs a second `verifyShieldsLockState()` with `verifyParentProtection: true`; the changed test file only covers direct Python guard reconciliation.

PRA-4 Resolve/justify — Source-of-truth note for strict-anchor reconciliation is incomplete

  • Location: agents/hermes/runtime-config-guard.py:2660
  • Category: docs
  • Problem: The localized workaround now explains the invalid state and source boundary: non-root Hermes startup mints one API key into `.env` and cannot advance the root-owned strict anchor. It still does not state the source-fix constraint in code or when the `shields-mutable` reconciliation path should be removed or disabled.
  • Impact: Without a clear retirement condition, future maintainers may preserve or broaden a special trust-anchor reconciliation path after the startup topology changes, increasing the risk of accidental policy bypass in a high-risk sandbox guard.
  • Recommended action: Add one concise sentence near the `shields-mutable` reconciliation branch or `_reconcile_nonroot_startup_api_key_hash()` docstring stating that this path should be removed or disabled once startup can atomically update the root-owned strict anchor safely, or once the managed non-root startup topology is retired. If the source cannot be fixed in this PR for another concrete reason, state that reason in the same note.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read the comment above the `purpose not in ("config-write", "shields-mutable")` check and the `_reconcile_nonroot_startup_api_key_hash()` docstring; confirm they include invalid state, source boundary, source-fix constraint, regression coverage, and removal condition.
  • Missing regression test: Existing tests already cover the allowed startup-key reconciliation, missing-digest fail-closed behavior, config drift refusal, topology gate, mutable-posture gate, stale compat hash, malformed hashes, path binding, non-API-key env drift, and stale host digest checks; no additional automated test is required for the removal-condition sentence itself.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read the comment above the `purpose not in ("config-write", "shields-mutable")` check and the `_reconcile_nonroot_startup_api_key_hash()` docstring; confirm they include invalid state, source boundary, source-fix constraint, regression coverage, and removal condition.
  • Evidence: `seal_restart()` now calls `_reconcile_nonroot_startup_api_key_hash()` for `shields-mutable` as well as `config-write`, and comments describe why that is needed, but no inspected code comment states when to retire the workaround.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

PRA-5 Improvement — Shrink the growing shields hotspot where the new security-coupled helpers permit

  • Location: src/lib/shields/index.ts
  • Category: architecture
  • Problem: `src/lib/shields/index.ts` is already a large security-sensitive hotspot and this PR grows it by 41 lines. The added logic is legitimate trust-boundary code, but the mutable-transition digest preparation and post-finish parent reverify are cohesive enough to be extracted or tightened without weakening validation.
  • Impact: Continued growth in this monolith makes future sandbox posture changes harder to review and increases the chance that security ordering or caller/callee contracts are accidentally changed during refactors.
  • Suggested action: If feasible in this PR, extract the mutable-transition digest preparation and post-finish Hermes parent reverify into small local helpers, or trim duplicated explanatory text while preserving the security invariants in comments.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Review `beginHermesConfigShields()` and `lockAgentConfigUnderMutationLock()` after the patch; confirm the command ordering, digest validation, rollback behavior, and parent verification semantics remain unchanged if code is extracted.
  • Missing regression test: The extraction itself should be covered by the host tests requested above: digest forwarding for shields-down and finish-before-parent-verify for shields-up.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Synthetic drift reports `src/lib/shields/index.ts` at 3343 lines after this change with +41 net lines; prior advisor review also flagged this hotspot growth.
Simplification opportunities: 1 possible cut

These are safe simplification checks only. Do not remove validation, security controls, data-loss prevention, or required tests.

  • PRA-5 shrink (src/lib/shields/index.ts): Inline digest-preparation and post-finish parent-reverify blocks in `src/lib/shields/index.ts`.
    • Replacement: Small local helpers such as `hermesMutableTransitionDigestArgs()` and `verifyHermesParentAfterFinish()` or equivalent concise extraction.
    • Safety boundary: Do not remove array-form command execution, SHA-256 parsing/validation, fail-closed digest omission behavior, transaction clearing after finish, or the post-finish parent protection recheck.
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — Add `Hermes shields-down passes parsed config sha256 to begin-shields-transition` in a mocked host shields test.. The changed code crosses the host/guard/sandbox posture boundary. Guard-level tests are good, but host-level mocked coverage and targeted runtime validation are still advisable for the actual `nemohermes shields down/up` orchestration.
  • PRA-T2 Runtime validation — Add `Hermes shields-down omits expected-config-sha256 and fails closed when sha256sum output is unparsable` or identify equivalent existing coverage.. The changed code crosses the host/guard/sandbox posture boundary. Guard-level tests are good, but host-level mocked coverage and targeted runtime validation are still advisable for the actual `nemohermes shields down/up` orchestration.
  • PRA-T3 Runtime validation — Add `Hermes shields-up defers parent protection verification until after finish-shields-transition` with stateful `/sandbox` stat responses.. The changed code crosses the host/guard/sandbox posture boundary. Guard-level tests are good, but host-level mocked coverage and targeted runtime validation are still advisable for the actual `nemohermes shields down/up` orchestration.
  • PRA-T4 Runtime validation — Add `Hermes shields-up post-finish parent verification failure does not attempt abort of released transaction` if the post-finish failure path is locally mockable.. The changed code crosses the host/guard/sandbox posture boundary. Guard-level tests are good, but host-level mocked coverage and targeted runtime validation are still advisable for the actual `nemohermes shields down/up` orchestration.
  • PRA-T5 Runtime validation — Perform or identify targeted runtime validation of a fresh OpenShell-managed non-root Hermes `shields down → shields up` cycle; do not rely on external E2E status as code-review evidence.. The changed code crosses the host/guard/sandbox posture boundary. Guard-level tests are good, but host-level mocked coverage and targeted runtime validation are still advisable for the actual `nemohermes shields down/up` orchestration.
  • PRA-T6 Host shields-down digest forwarding is still not directly asserted — Update the existing mocked Hermes shields-down host test, or add a focused nearby host test, so the mock returns a valid `sha256sum /sandbox/.hermes/config.yaml` line and the captured `begin-shields-transition` command is asserted to include `--expected-config-sha256 <64-hex>`.
  • PRA-T7 Acceptance clause — Step 5: shields down succeeds; sandbox enters mutable config state; `/sandbox/.hermes` permissions transition correctly. — add test evidence or identify existing coverage. The guard now accepts `--expected-config-sha256` for `begin-shields-transition` and allows `seal_restart(purpose="shields-mutable")` to use the existing API-key reconciliation path. The changed test `reconciles the startup API key on the first shields-down and completes the transition ([Ubuntu 24.04][Sandbox] nemohermes shields down fails on fresh Hermes sandbox — strict hash verification failed for Hermes restart seal #6381)` proves direct guard success and strict-anchor repair. Host-level evidence that `nemohermes shields down` forwards the digest is still missing.
  • PRA-T8 Acceptance clause — Step 6: shields up succeeds; config locked back. — add test evidence or identify existing coverage. The host lock path now defers parent protection while the sealed Hermes transaction is active, calls `finishHermesConfigShields()`, and then re-runs `verifyShieldsLockState()` with parent protection enabled. No changed host test simulates the pre-finish `755 root:root` and post-finish `1775 root:sandbox` states.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Source-of-truth review needed: Hermes managed non-root strict-anchor reconciliation for `shields-mutable`

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Changed tests cover first shields-down reconciliation, missing digest fail-closed behavior, and config drift refusal; existing tests cover managed topology, mutable posture, locked-posture refusal, stale compat hash, malformed hashes, path binding, non-API-key env drift, and stale host digest checks.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: `seal_restart()` now allows `_reconcile_nonroot_startup_api_key_hash()` for `shields-mutable`; the helper still refuses unmanaged topology, non-mutable posture, config drift, compat mismatch, and any env drift beyond the single generated API key.

PRA-2 Resolve/justify — Host shields-down digest forwarding is still not directly asserted

  • Location: src/lib/shields/index.ts:498
  • Category: tests
  • Problem: The TypeScript host path now runs `sha256sum` on `target.configPath`, parses the first token, and conditionally appends `--expected-config-sha256` to `begin-shields-transition`. The changed tests call `agents/hermes/runtime-config-guard.py begin-shields-transition` directly, so they prove the guard works when a digest is supplied but not that `nemohermes shields down` supplies it. The nearby mocked host test in `test/repro-2681-group-writable.test.ts` still only asserts `--shields-mode mutable` / rollback args.
  • Impact: A regression in command ordering, digest parsing, or argument plumbing could make fresh Hermes `shields down` fail again with a stale strict anchor while the new guard-level reconciliation tests continue passing.
  • Recommended action: Update the existing mocked Hermes shields-down host test, or add a focused nearby host test, so the mock returns a valid `sha256sum /sandbox/.hermes/config.yaml` line and the captured `begin-shields-transition` command is asserted to include `--expected-config-sha256 <64-hex>`.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read `test/repro-2681-group-writable.test.ts` around the `shields-down restores Hermes sticky group-writable config root without group-writable config files` test and confirm it captures the `sha256sum` command plus a `begin-shields-transition` argv containing the parsed digest.
  • Missing regression test: Add `Hermes shields-down passes parsed config sha256 to begin-shields-transition`; also consider `Hermes shields-down omits expected-config-sha256 and fails closed when sha256sum output is unparsable` to pin the fail-closed fallback.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read `test/repro-2681-group-writable.test.ts` around the `shields-down restores Hermes sticky group-writable config root without group-writable config files` test and confirm it captures the `sha256sum` command plus a `begin-shields-transition` argv containing the parsed digest.
  • Evidence: `beginHermesConfigShields()` adds `privilegedSandboxExecCapture(sandboxName, ["sha256sum", target.configPath])` and `extraArgs.push("--expected-config-sha256", configSha)`, while the added tests use `runManagedNonrootBegin()` to invoke the Python guard with `beginShieldsArgs()` directly.

PRA-3 Resolve/justify — Hermes shields-up parent verification ordering lacks host-level regression coverage

  • Location: src/lib/shields/index.ts:2028
  • Category: acceptance
  • Problem: Issue [Ubuntu 24.04][Sandbox] nemohermes shields down fails on fresh Hermes sandbox — strict hash verification failed for Hermes restart seal #6381's Step 6 failure is a host-side ordering problem: `/sandbox` can still be `755 root:root` before `finish-shields-transition` commits the final `1775 root:sandbox` posture. The patch defers parent verification while a sealed Hermes transaction is active, finishes the transaction, clears it, and then re-runs parent verification, but no changed host test proves that sequence.
  • Impact: A later refactor could reintroduce the pre-finish parent check or accidentally drop the post-finish parent recheck, making `nemohermes shields up` fail again or silently skip a sandbox parent-posture check while guard-only tests still pass.
  • Recommended action: Add a focused mocked `lockAgentConfig()` Hermes test that reports `/sandbox` as `755 root:root` before `finish-shields-transition`, reports `1775 root:sandbox` after finish, and asserts the host invokes finish before the parent-protection verification failure point.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect `src/lib/shields/index.ts` around `deferParentProtectionToFinish` and the post-finish `verifyShieldsLockState(... verifyParentProtection: true)` call, then check nearby host tests for a stateful `/sandbox` stat mock and command-order assertion.
  • Missing regression test: Add `Hermes shields-up defers parent protection verification until after finish-shields-transition`; include a stateful stat mock so pre-finish parent verification would fail and post-finish verification passes. Add `Hermes shields-up post-finish parent verification failure does not attempt abort of released transaction` if the failure path is easy to isolate.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect `src/lib/shields/index.ts` around `deferParentProtectionToFinish` and the post-finish `verifyShieldsLockState(... verifyParentProtection: true)` call, then check nearby host tests for a stateful `/sandbox` stat mock and command-order assertion.
  • Evidence: The diff changes the first `verifyShieldsLockState()` call to disable parent protection when `transaction != null`, calls `finishHermesConfigShields()`, sets `transaction = null`, then performs a second `verifyShieldsLockState()` with `verifyParentProtection: true`; the changed test file only covers direct Python guard reconciliation.

PRA-4 Resolve/justify — Source-of-truth note for strict-anchor reconciliation is incomplete

  • Location: agents/hermes/runtime-config-guard.py:2660
  • Category: docs
  • Problem: The localized workaround now explains the invalid state and source boundary: non-root Hermes startup mints one API key into `.env` and cannot advance the root-owned strict anchor. It still does not state the source-fix constraint in code or when the `shields-mutable` reconciliation path should be removed or disabled.
  • Impact: Without a clear retirement condition, future maintainers may preserve or broaden a special trust-anchor reconciliation path after the startup topology changes, increasing the risk of accidental policy bypass in a high-risk sandbox guard.
  • Recommended action: Add one concise sentence near the `shields-mutable` reconciliation branch or `_reconcile_nonroot_startup_api_key_hash()` docstring stating that this path should be removed or disabled once startup can atomically update the root-owned strict anchor safely, or once the managed non-root startup topology is retired. If the source cannot be fixed in this PR for another concrete reason, state that reason in the same note.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read the comment above the `purpose not in ("config-write", "shields-mutable")` check and the `_reconcile_nonroot_startup_api_key_hash()` docstring; confirm they include invalid state, source boundary, source-fix constraint, regression coverage, and removal condition.
  • Missing regression test: Existing tests already cover the allowed startup-key reconciliation, missing-digest fail-closed behavior, config drift refusal, topology gate, mutable-posture gate, stale compat hash, malformed hashes, path binding, non-API-key env drift, and stale host digest checks; no additional automated test is required for the removal-condition sentence itself.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read the comment above the `purpose not in ("config-write", "shields-mutable")` check and the `_reconcile_nonroot_startup_api_key_hash()` docstring; confirm they include invalid state, source boundary, source-fix constraint, regression coverage, and removal condition.
  • Evidence: `seal_restart()` now calls `_reconcile_nonroot_startup_api_key_hash()` for `shields-mutable` as well as `config-write`, and comments describe why that is needed, but no inspected code comment states when to retire the workaround.

PRA-5 Improvement — Shrink the growing shields hotspot where the new security-coupled helpers permit

  • Location: src/lib/shields/index.ts
  • Category: architecture
  • Problem: `src/lib/shields/index.ts` is already a large security-sensitive hotspot and this PR grows it by 41 lines. The added logic is legitimate trust-boundary code, but the mutable-transition digest preparation and post-finish parent reverify are cohesive enough to be extracted or tightened without weakening validation.
  • Impact: Continued growth in this monolith makes future sandbox posture changes harder to review and increases the chance that security ordering or caller/callee contracts are accidentally changed during refactors.
  • Suggested action: If feasible in this PR, extract the mutable-transition digest preparation and post-finish Hermes parent reverify into small local helpers, or trim duplicated explanatory text while preserving the security invariants in comments.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Review `beginHermesConfigShields()` and `lockAgentConfigUnderMutationLock()` after the patch; confirm the command ordering, digest validation, rollback behavior, and parent verification semantics remain unchanged if code is extracted.
  • Missing regression test: The extraction itself should be covered by the host tests requested above: digest forwarding for shields-down and finish-before-parent-verify for shields-up.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Synthetic drift reports `src/lib/shields/index.ts` at 3343 lines after this change with +41 net lines; prior advisor review also flagged this hotspot growth.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor (Nemotron Ultra) — Changes requested

Merge posture: Do not merge yet
Primary next action: Resolve or justify PRA-1: Deferred parent protection verification creates subtle window between apply and finish.
Open items: 0 required · 2 warnings · 3 suggestions · 4 test follow-ups
Since last review: 0 prior items resolved · 0 still apply · 0 new items found

Action checklist

  • PRA-1 Resolve or justify: Deferred parent protection verification creates subtle window between apply and finish in src/lib/shields/index.ts:2027
  • PRA-2 Resolve or justify: Monolith growth: shields/index.ts grew by 41 lines (delta > 20) in src/lib/shields/index.ts:459
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Missing test for shields-down with rollback_mode=locked path
  • PRA-3 In-scope improvement: Missing test for shields-down with rollback_mode=locked path in test/hermes-nonroot-strict-hash-reconciliation.test.ts:460
  • PRA-4 In-scope improvement: Reconciliation is a documented workaround for non-root startup unable to update root-owned strict anchor in agents/hermes/runtime-config-guard.py:2650
  • PRA-5 In-scope improvement: Config digest computed inside sandbox via sha256sum; if sandbox compromised could return wrong digest in src/lib/shields/index.ts:1795

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify security src/lib/shields/index.ts:2027 Add an E2E test that mutates /sandbox permissions between apply and finish to verify the post-finish re-verify catches it or the transaction aborts cleanly. The logic is correct but subtle; test would lock in behavior.
PRA-2 Resolve/justify architecture src/lib/shields/index.ts:459 Consider extracting protocol detection and shields transaction helpers to a separate module in a follow-up PR. For this PR, the growth is justified and localized.
PRA-3 Improvement tests test/hermes-nonroot-strict-hash-reconciliation.test.ts:460 Add test case in hermes-nonroot-strict-hash-reconciliation.test.ts: create fixture with hermesMode=0o3770 (canonical mutable), run begin-shields-transition with mode=mutable, rollback_mode=locked, verify reconciliation succeeds.
PRA-4 Improvement correctness agents/hermes/runtime-config-guard.py:2650 Ensure code comments reference removal condition (#6257). No action needed in this PR — workaround is correct and well-documented.
PRA-5 Improvement correctness src/lib/shields/index.ts:1795 Consider computing config digest on host side (outside sandbox) in a follow-up for defense-in-depth. Current guard reconciliation provides sufficient backstop.
Review findings by urgency: 0 required fixes, 2 items to resolve/justify, 3 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Deferred parent protection verification creates subtle window between apply and finish

  • Location: src/lib/shields/index.ts:2027
  • Category: security
  • Problem: lockAgentConfigUnderMutationLock defers parent (/sandbox) posture verification until after finishHermesConfigShields commits the final 1775 root:sandbox posture. Transaction is cleared before post-finish verifyShieldsLockState with verifyParentProtection: true to prevent rollback re-entry. If finish fails partway, parent could be left in intermediate state. No test covers mutation race in this window.
  • Impact: An attacker with sandbox access who can mutate /sandbox between apply and finish could potentially leave the parent in an incorrect posture if finish fails, though the guard uses root ownership as crash-consistency marker.
  • Recommended action: Add an E2E test that mutates /sandbox permissions between apply and finish to verify the post-finish re-verify catches it or the transaction aborts cleanly. The logic is correct but subtle; test would lock in behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check lockAgentConfigUnderMutationLock lines 2027-2050: deferParentProtectionToFinish logic, transaction=null before post-finish verifyShieldsLockState with verifyParentProtection: true.
  • Missing regression test: E2E test: start shields-down on Hermes sandbox, after applyHermesConfigShields but before finishHermesConfigShields, mutate /sandbox mode/ownership, verify finish fails or post-finish verify catches it.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check lockAgentConfigUnderMutationLock lines 2027-2050: deferParentProtectionToFinish logic, transaction=null before post-finish verifyShieldsLockState with verifyParentProtection: true.
  • Evidence: Code at lines 2027-2050 implements deferred verification; comment explains rationale. No existing test for this race window.

PRA-2 Resolve/justify — Monolith growth: shields/index.ts grew by 41 lines (delta > 20)

  • Location: src/lib/shields/index.ts:459
  • Category: architecture
  • Problem: File grew from 3302 to 3343 lines. Growth from: (1) new resolveHermesShieldsProtocol function (~25 lines) consolidating protocol detection, (2) deferred parent verification logic (~15 lines). Both are necessary for the fix and security-context-coupled.
  • Impact: Continued growth in this hotspot makes maintenance harder. The new helper is a good consolidation but adds to the file.
  • Recommended action: Consider extracting protocol detection and shields transaction helpers to a separate module in a follow-up PR. For this PR, the growth is justified and localized.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: git diff --stat src/lib/shields/index.ts shows +41 lines. New resolveHermesShieldsProtocol at line ~459.
  • Missing regression test: Not applicable.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: git diff --stat src/lib/shields/index.ts shows +41 lines. New resolveHermesShieldsProtocol at line ~459.
  • Evidence: Drift tool flagged blocker severity for monolith growth >20 lines.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

PRA-3 Improvement — Missing test for shields-down with rollback_mode=locked path

  • Location: test/hermes-nonroot-strict-hash-reconciliation.test.ts:460
  • Category: tests
  • Problem: The PR adds reconciliation for shields-mutable (mode=mutable). The rollback_mode can be 'locked' or 'mutable'. No test verifies reconciliation works when rollback_mode=locked (i.e., shields-down on a previously locked sandbox).
  • Impact: Edge case not covered: if shields-down is run on a sandbox that was previously shields-up, the rollback_mode=locked path should still reconcile correctly.
  • Suggested action: Add test case in hermes-nonroot-strict-hash-reconciliation.test.ts: create fixture with hermesMode=0o3770 (canonical mutable), run begin-shields-transition with mode=mutable, rollback_mode=locked, verify reconciliation succeeds.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Run existing test suite; check beginShieldsArgs accepts rollback_shields_mode parameter. Add test calling runManagedNonrootBegin with rollback_mode=locked equivalent.
  • Missing regression test: Test case: createFixture(0o3770), append API key, refreshCompatOnly, runManagedNonrootBegin with rollback_mode=locked (via CLI arg), expect success and strictHashIsValid=true.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Three new tests cover mode=mutable, rollback_mode=mutable. rollback_mode=locked path not exercised in tests.

PRA-4 Improvement — Reconciliation is a documented workaround for non-root startup unable to update root-owned strict anchor

  • Location: agents/hermes/runtime-config-guard.py:2650
  • Category: correctness
  • Problem: The reconciliation advances the root-owned strict hash anchor for the single expected API_SERVER_KEY append by non-root startup. Root cause: OpenShell startup runs as sandbox user, strict anchor is root-owned in /etc/nemoclaw/. Source-of-truth fix would be OpenShell writing strict anchor as root or Hermes exposing authenticated applied-config digest (fix(hermes): reconcile mcp_servers configuration drift at startup #6257). Workaround gated tightly: only mutable non-root posture, only with expected config digest, only single API_SERVER_KEY append.
  • Impact: If OpenShell/runtime architecture changes to provide authenticated config digest, this workaround can be removed. Tests would fail if removed without replacement (they assert strictHashIsValid becomes true after reconciliation).
  • Suggested action: Ensure code comments reference removal condition (fix(hermes): reconcile mcp_servers configuration drift at startup #6257). No action needed in this PR — workaround is correct and well-documented.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check comment at seal_restart line 2650 and _reconcile_nonroot_startup_api_key_hash docstring. Both reference the workaround and its gating.
  • Missing regression test: Existing tests would fail if reconciliation path removed without replacement — they assert strictHashIsValid becomes true after reconciliation.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Workaround documented in code comments. Removal condition: OpenShell/runtime provides authenticated applied-config digest (fix(hermes): reconcile mcp_servers configuration drift at startup #6257).

PRA-5 Improvement — Config digest computed inside sandbox via sha256sum; if sandbox compromised could return wrong digest

  • Location: src/lib/shields/index.ts:1795
  • Category: correctness
  • Problem: beginHermesConfigShields reads config.yaml digest via privilegedSandboxExecCapture(['sha256sum', target.configPath]). If sandbox is compromised, it could return a stale or wrong digest. However, reconciliation re-validates actual config hash matches expected (line 1670 in guard), so mismatch would be caught.
  • Impact: Sandbox could lie about config digest to force a transition that shouldn't proceed, but the guard's reconciliation step validates actual config hash against expected digest, preventing silent acceptance of drifted config.
  • Suggested action: Consider computing config digest on host side (outside sandbox) in a follow-up for defense-in-depth. Current guard reconciliation provides sufficient backstop.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check beginHermesConfigShields at line ~1795: privilegedSandboxExecCapture(['sha256sum', target.configPath]). Guard _verify_strict_hash validates actual file contents.
  • Missing regression test: Not applicable — guard reconciliation already tested against config drift.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Guard's _verify_strict_hash reads actual config.yaml and .env from filesystem, not trusting host-provided digest.
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — E2E test: mutate /sandbox permissions between applyHermesConfigShields and finishHermesConfigShields, verify post-finish verify catches it. Three new unit tests cover reconciliation happy path and refusal cases using real filesystem operations (not mocks). Parent mutation race window and rollback_mode=locked path need E2E validation with real sandbox lifecycle.
  • PRA-T2 Runtime validation — Unit test: createFixture(0o3770), run begin-shields-transition with rollback_mode=locked, verify reconciliation succeeds. Three new unit tests cover reconciliation happy path and refusal cases using real filesystem operations (not mocks). Parent mutation race window and rollback_mode=locked path need E2E validation with real sandbox lifecycle.
  • PRA-T3 Runtime validation — E2E test: shields-down on previously shields-up sandbox (rollback_mode=locked) with stale strict anchor. Three new unit tests cover reconciliation happy path and refusal cases using real filesystem operations (not mocks). Parent mutation race window and rollback_mode=locked path need E2E validation with real sandbox lifecycle.
  • PRA-T4 Missing test for shields-down with rollback_mode=locked path — Add test case in hermes-nonroot-strict-hash-reconciliation.test.ts: create fixture with hermesMode=0o3770 (canonical mutable), run begin-shields-transition with mode=mutable, rollback_mode=locked, verify reconciliation succeeds.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Deferred parent protection verification creates subtle window between apply and finish

  • Location: src/lib/shields/index.ts:2027
  • Category: security
  • Problem: lockAgentConfigUnderMutationLock defers parent (/sandbox) posture verification until after finishHermesConfigShields commits the final 1775 root:sandbox posture. Transaction is cleared before post-finish verifyShieldsLockState with verifyParentProtection: true to prevent rollback re-entry. If finish fails partway, parent could be left in intermediate state. No test covers mutation race in this window.
  • Impact: An attacker with sandbox access who can mutate /sandbox between apply and finish could potentially leave the parent in an incorrect posture if finish fails, though the guard uses root ownership as crash-consistency marker.
  • Recommended action: Add an E2E test that mutates /sandbox permissions between apply and finish to verify the post-finish re-verify catches it or the transaction aborts cleanly. The logic is correct but subtle; test would lock in behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check lockAgentConfigUnderMutationLock lines 2027-2050: deferParentProtectionToFinish logic, transaction=null before post-finish verifyShieldsLockState with verifyParentProtection: true.
  • Missing regression test: E2E test: start shields-down on Hermes sandbox, after applyHermesConfigShields but before finishHermesConfigShields, mutate /sandbox mode/ownership, verify finish fails or post-finish verify catches it.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check lockAgentConfigUnderMutationLock lines 2027-2050: deferParentProtectionToFinish logic, transaction=null before post-finish verifyShieldsLockState with verifyParentProtection: true.
  • Evidence: Code at lines 2027-2050 implements deferred verification; comment explains rationale. No existing test for this race window.

PRA-2 Resolve/justify — Monolith growth: shields/index.ts grew by 41 lines (delta > 20)

  • Location: src/lib/shields/index.ts:459
  • Category: architecture
  • Problem: File grew from 3302 to 3343 lines. Growth from: (1) new resolveHermesShieldsProtocol function (~25 lines) consolidating protocol detection, (2) deferred parent verification logic (~15 lines). Both are necessary for the fix and security-context-coupled.
  • Impact: Continued growth in this hotspot makes maintenance harder. The new helper is a good consolidation but adds to the file.
  • Recommended action: Consider extracting protocol detection and shields transaction helpers to a separate module in a follow-up PR. For this PR, the growth is justified and localized.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: git diff --stat src/lib/shields/index.ts shows +41 lines. New resolveHermesShieldsProtocol at line ~459.
  • Missing regression test: Not applicable.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: git diff --stat src/lib/shields/index.ts shows +41 lines. New resolveHermesShieldsProtocol at line ~459.
  • Evidence: Drift tool flagged blocker severity for monolith growth >20 lines.

PRA-3 Improvement — Missing test for shields-down with rollback_mode=locked path

  • Location: test/hermes-nonroot-strict-hash-reconciliation.test.ts:460
  • Category: tests
  • Problem: The PR adds reconciliation for shields-mutable (mode=mutable). The rollback_mode can be 'locked' or 'mutable'. No test verifies reconciliation works when rollback_mode=locked (i.e., shields-down on a previously locked sandbox).
  • Impact: Edge case not covered: if shields-down is run on a sandbox that was previously shields-up, the rollback_mode=locked path should still reconcile correctly.
  • Suggested action: Add test case in hermes-nonroot-strict-hash-reconciliation.test.ts: create fixture with hermesMode=0o3770 (canonical mutable), run begin-shields-transition with mode=mutable, rollback_mode=locked, verify reconciliation succeeds.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Run existing test suite; check beginShieldsArgs accepts rollback_shields_mode parameter. Add test calling runManagedNonrootBegin with rollback_mode=locked equivalent.
  • Missing regression test: Test case: createFixture(0o3770), append API key, refreshCompatOnly, runManagedNonrootBegin with rollback_mode=locked (via CLI arg), expect success and strictHashIsValid=true.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Three new tests cover mode=mutable, rollback_mode=mutable. rollback_mode=locked path not exercised in tests.

PRA-4 Improvement — Reconciliation is a documented workaround for non-root startup unable to update root-owned strict anchor

  • Location: agents/hermes/runtime-config-guard.py:2650
  • Category: correctness
  • Problem: The reconciliation advances the root-owned strict hash anchor for the single expected API_SERVER_KEY append by non-root startup. Root cause: OpenShell startup runs as sandbox user, strict anchor is root-owned in /etc/nemoclaw/. Source-of-truth fix would be OpenShell writing strict anchor as root or Hermes exposing authenticated applied-config digest (fix(hermes): reconcile mcp_servers configuration drift at startup #6257). Workaround gated tightly: only mutable non-root posture, only with expected config digest, only single API_SERVER_KEY append.
  • Impact: If OpenShell/runtime architecture changes to provide authenticated config digest, this workaround can be removed. Tests would fail if removed without replacement (they assert strictHashIsValid becomes true after reconciliation).
  • Suggested action: Ensure code comments reference removal condition (fix(hermes): reconcile mcp_servers configuration drift at startup #6257). No action needed in this PR — workaround is correct and well-documented.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check comment at seal_restart line 2650 and _reconcile_nonroot_startup_api_key_hash docstring. Both reference the workaround and its gating.
  • Missing regression test: Existing tests would fail if reconciliation path removed without replacement — they assert strictHashIsValid becomes true after reconciliation.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Workaround documented in code comments. Removal condition: OpenShell/runtime provides authenticated applied-config digest (fix(hermes): reconcile mcp_servers configuration drift at startup #6257).

PRA-5 Improvement — Config digest computed inside sandbox via sha256sum; if sandbox compromised could return wrong digest

  • Location: src/lib/shields/index.ts:1795
  • Category: correctness
  • Problem: beginHermesConfigShields reads config.yaml digest via privilegedSandboxExecCapture(['sha256sum', target.configPath]). If sandbox is compromised, it could return a stale or wrong digest. However, reconciliation re-validates actual config hash matches expected (line 1670 in guard), so mismatch would be caught.
  • Impact: Sandbox could lie about config digest to force a transition that shouldn't proceed, but the guard's reconciliation step validates actual config hash against expected digest, preventing silent acceptance of drifted config.
  • Suggested action: Consider computing config digest on host side (outside sandbox) in a follow-up for defense-in-depth. Current guard reconciliation provides sufficient backstop.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check beginHermesConfigShields at line ~1795: privilegedSandboxExecCapture(['sha256sum', target.configPath]). Guard _verify_strict_hash validates actual file contents.
  • Missing regression test: Not applicable — guard reconciliation already tested against config drift.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Guard's _verify_strict_hash reads actual config.yaml and .env from filesystem, not trusting host-provided digest.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
agents/hermes/runtime-config-guard.py (1)

2656-2678: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

shields-locked still bypasses startup-hash reconciliation. begin_shields_transition(..., mode="locked") routes to _seal_shields_locked(), which never accepts expected_config_sha256, so a fresh sandbox whose first root transaction is shields-up will still fail on the startup-minted .env drift. Thread the expected hash through the locked path or make shields-up-first impossible.

🤖 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 `@agents/hermes/runtime-config-guard.py` around lines 2656 - 2678, The locked
shields transition still skips startup-hash reconciliation, so a fresh sandbox
can fail on the startup-minted .env drift. Update the `begin_shields_transition`
/ `_seal_shields_locked` flow to either accept and pass through
`expected_config_sha256` for the `mode="locked"` path or explicitly prevent
shields-up-first when that reconciliation cannot happen. Keep the reconciliation
logic aligned with `_reconcile_nonroot_startup_api_key_hash` and
`_verify_strict_hash` so the locked path handles the same startup hash drift as
`config-write`.
🤖 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.

Outside diff comments:
In `@agents/hermes/runtime-config-guard.py`:
- Around line 2656-2678: The locked shields transition still skips startup-hash
reconciliation, so a fresh sandbox can fail on the startup-minted .env drift.
Update the `begin_shields_transition` / `_seal_shields_locked` flow to either
accept and pass through `expected_config_sha256` for the `mode="locked"` path or
explicitly prevent shields-up-first when that reconciliation cannot happen. Keep
the reconciliation logic aligned with `_reconcile_nonroot_startup_api_key_hash`
and `_verify_strict_hash` so the locked path handles the same startup hash drift
as `config-write`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 37e82bec-481c-4dbd-b93e-5b0edfeefae1

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe4cb3 and ddf3289.

📒 Files selected for processing (3)
  • agents/hermes/runtime-config-guard.py
  • src/lib/shields/index.ts
  • test/hermes-nonroot-strict-hash-reconciliation.test.ts

Complements the shields-down fix: the first shields up on a fresh
OpenShell-managed Hermes sandbox failed with "Config not locked: parent dir
mode=755 (expected 1775), parent dir owner=root:root (expected root:sandbox)".

For the sealed Hermes transaction the parent (/sandbox) posture — 1775
root:sandbox — is deliberately the last persistent change, applied by
finish-shields-transition; the guard keeps /sandbox root-owned as its
crash-consistency orphan marker until finish. lockAgentConfigUnderMutationLock
verified parent protection between apply and finish, so it always observed the
frozen 755 root:root posture and reported a false lock failure (the catch path
then committed the correct posture but still surfaced exit 1 and left shields
state DOWN).

Defer parent-protection verification for the sealed Hermes path to a
post-finish re-verify, where the 1775 root:sandbox posture is in place. Locked
files, config-dir mode, and chattr are still checked before finish; OpenClaw
and legacy-Hermes paths (no sealed transaction) keep the inline check. A full
shields down -> up -> down -> up cycle now completes on a fresh sandbox.

Fixes #6381

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
Replace the `if (expectedDigest !== undefined)` push in the test helper with a
ternary spread so the changed test file adds no `if` statements, satisfying the
codebase-growth-guardrails "no added if statements" gate. No behavioral change.

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
@jyaunches

Copy link
Copy Markdown
Contributor

SWQA E2E follow-up: #6398 adds a dedicated CPU-only live regression for this fix. It performs a fresh non-root Hermes onboard followed by shields down → up → down → up.

Against current main, the corrected E2E reaches cycle 1 shields down and reproduces #6381 exactly:

[SECURITY] strict hash verification failed for Hermes restart seal

Failing run: https://github.com/NVIDIA/NemoClaw/actions/runs/28874817718
Test PR: #6398

#6398 is intentionally draft/blocked on this PR. After #6384 lands, we will rerun the same lane and expect both shields cycles to pass. No GPU or hosted inference secret is required.

@cv cv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The implementation looks security-correct, and I do not think the CodeRabbit shields-locked suggestion applies: locked mode intentionally routes through _seal_shields_locked, freezes the namespace, republishes bounded root-owned inputs, and synthesizes fresh strict/compat anchors so stale mutable state cannot veto containment. Please do not thread mutable reconciliation into that path.

Two in-scope host contracts still need deterministic coverage before approval:

  1. Add a TypeScript/host regression proving the mutable transition reads a valid sha256sum and forwards it as --expected-config-sha256 to begin-shields-transition (plus a malformed/missing hash fail-closed assertion if practical). The new Python tests bypass this wiring.
  2. Add a stateful host regression proving parent protection is skipped before finish-shields-transition, then enforced after finish, with finish occurring before the final parent check. The current tests do not cover this ordering.

Please also link a passing #6398 run on a combined/exact-fix head. Its current run 28874817718 reproduces the failure on the test-only head without #6384, but does not yet validate this fix. These are trust-anchor and sandbox lifecycle changes, so the exact host wiring and live cycle should be evidenced.

@wscurran wscurran added v0.0.77 and removed v0.0.76 labels Jul 7, 2026
jyaunches added a commit that referenced this pull request Jul 7, 2026
<!-- markdownlint-disable MD041 -->
## Summary
Adds a CPU-only live E2E that reproduces the fresh Hermes shields
lifecycle regression from #6381. The test onboards a new non-root Hermes
sandbox, runs shields down/up twice, and preserves the failure as a
dedicated E2E lane until #6384 lands.

## Related Issue
Relates to #6381.

Depends on #6384.

## Changes
- Add a fresh Hermes onboard and two-cycle shields down/up live
regression test.
- Assert the mutable and locked ownership/mode contracts after each
transition.
- Add a dedicated `hermes-shields-config` workflow job that requires no
GPU or hosted inference secret.
- Extend the E2E artifact workflow boundary for the new job.

## 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 exactly 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: test and CI coverage only; no
user-facing behavior changes
- [ ] 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:

## 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 the DCO sign-off declaration 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
- [ ] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — command/result or justification: E2E
workflow support tests passed (24/24), and the live target collects
successfully; the live run is intentionally expected to reproduce #6381
on current `main`
- [ ] 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: Julie Yaunches <jyaunches@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a new Hermes live end-to-end check that runs repeated shields
cycles in a fresh non-root sandbox.
* Updated PR reporting so the new live job is included in the results
summary.
* **Bug Fixes**
* Updated E2E artifact upload workflow boundary validations to match the
current number of expected E2E execution jobs and default callers.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
@jyaunches
jyaunches self-requested a review July 7, 2026 16:53
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All requested jobs passed

Run: 28883593471
Workflow ref: fix/hermes-shields-strict-hash-reconcile-6381
Requested targets: (default — all supported)
Requested jobs: hermes-shields-config
Summary: 1 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
hermes-shields-config ✅ success

@jyaunches

Copy link
Copy Markdown
Contributor

✅ SWQA follow-up: after updating this branch with current main (33b120581), the merged hermes-shields-config live E2E now passes on #6384.

This is the same test that reproduced [SECURITY] strict hash verification failed for Hermes restart seal on main, so the red-to-green result directly validates the #6384 fix.

@wscurran wscurran added area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery area: security Security controls, permissions, secrets, or hardening bug-fix PR fixes a bug or regression integration: hermes Hermes integration behavior security labels Jul 7, 2026
@cjagwani

cjagwani commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Closing as superseded by current main commit 7b0b3189 (merged via #6293), which already carries the #6381 behavior and the passing Hermes shields live cycle.

The mainline implementation is the safer final form: for the first mutable transition, the root-owned runtime guard parses the expected config.yaml digest from the existing strict anchor and passes that internally to seal_restart; it does not accept a host-observed current digest. Main also defers parent protection while the sealed transaction is rollback-capable, clears the transaction after finish, and performs the final parent verification post-commit.

I attempted a current-main merge to assess salvage. All three conflicts were these independently landed changes, and resolving toward this branch would restore redundant host digest forwarding instead of improving main. The contributor work and live validation remain reflected in the mainline fix; this PR no longer has a distinct safe delta for v0.0.78.

@cjagwani cjagwani closed this Jul 8, 2026
cv added a commit that referenced this pull request Jul 8, 2026
…6431)

<!-- markdownlint-disable MD041 -->
## Summary
Replaces NemoClaw's build-time mutation of the released Deep Agents
bootstrap with a first-party `deepagents.harness_profiles` plugin for
`deepagents-code==0.1.34` / `deepagents==0.7.0a6`. The two managed
OpenAI-compatible model keys continue to receive the released native
Nemotron 3 Ultra profile, with exact version/source gates and no
third-party source changes.

## Related Issue
Fixes #6424

## Changes
- Add and install `nemoclaw-deepagents-profile==0.1.0` through Deep
Agents' supported profile entry-point lifecycle.
- Register only the two NemoClaw-managed aliases against the released
canonical Ultra profile, atomically and idempotently.
- Fail the image build on missing or unimportable dependencies,
mismatched distribution/package roots, copied/installed adapter-source
drift, or released-profile/bootstrap drift.
- Run a DCode-only negative Docker build from the current hash-locked
base, strip both upstream distributions, and prove failure occurs at the
isolated import gate before the later dependency check.
- Build and install a real unreviewed-version plugin wheel and prove the
actual validator rejects it.
- Verify entry-point discovery, all 12 middleware entries,
unrelated-model isolation, graph compilation, and allowed/denied
execute-dispatch parity against the official wheels.
- Split image/runtime and credential-boundary contracts into balanced
756/755-line suites with a 113-line shared helper, preserving all 75
original tests and substantial per-file size headroom.
- Remove the installed-bootstrap patcher and document that the adapter
must be removed, not rehashed, once reviewed dependencies provide both
exact aliases.
- Preserve the merged DCode hardening and paced `/agents` first-run TUI
behavior from #6410 / #6418.

## Automated review dispositions

- **License metadata:** the production package keeps the PEP 639 SPDX
string and builds unchanged with lock-pinned `setuptools==82.0.1`; the
production validator now requires exact installed-wheel
`License-Expression: Apache-2.0` metadata, with a negative
metadata-drift test. The legacy conversion is a localized offline
wrong-version fixture with explicit source-boundary and
removal-condition documentation. Remove the fixture-only conversion once
runner setuptools accepts PEP 639 strings; production never uses it.
- **Plain-progress build output:** plain progress remains necessary to
prove the exact import-failure marker. Before Docker runs, the gate now
rejects every Docker `ARG` name outside a complete reviewed allowlist,
while tests pin the only passed build arguments to the two public
`BASE_IMAGE` references. Behavior tests inject unreviewed uppercase,
lowercase, and continued ARG declarations across all three Dockerfiles
and prove rejection occurs before any build; the targeted DCode E2E job
runs the same script with real Docker before live tests.
- **Adapter build-layer retention:** Docker can retain the copied
project tree in an image layer or failed local build cache. This is
accepted because it contains only public, first-party Apache-2.0 source
and metadata, while the installed Python module necessarily ships the
same source; revisit if any adapter input becomes secret-bearing or
non-public.
- **Credential redaction parity:** `PASS`/`PASSWD`,
quoted/space-separated assignments, punctuation-bearing values, and
bounded camel/acronym aliases now share the same fail-closed policy
across the Bash wrapper, managed Python runtime, observability scrubber,
config filter, full/sensitive-text redactors, structured-log classifier,
TUI sanitizer, and E2E redactors. The separator lookbehind is capped at
32 horizontal characters to prevent attacker-controlled scans;
private-key blocks are scrubbed before assignment matching. Positive
tests cover `customPass`, `DBPass`, and known secret `*Key` families,
while `COMPASS`/`BYPASS`, `TOPSECRET`/`SUBTOKEN`, pass-rate fields,
`publicKey`, and `customKey` remain untouched.
- **OpenShell TLS key provenance:** the canonical mounted path is
intentionally accepted only from the supervisor-owned runtime
environment and rejected from the mutable DCode `.env`. The split
credential suite now proves both sides explicitly, matching the existing
wrapper-identity coverage; allowing it in `.env` would weaken the
boundary.
- **Docker auth cleanup:** the shared workflow validator requires
exactly one canonical cleanup with `if: always()` as the final job step.
A DCode-specific mutation test now also rejects moving cleanup before
the import gate.
- **Private-key and fixture helpers:** multiline private-key matching is
consolidated into the live generic matcher with a required-newline mode,
preserving comment behavior while removing 12 lines. Profile-hash
fixture replacement is now whitespace/quote tolerant while still
requiring one exact reviewed constant and digest. A focused regression
covers both formatting variants and duplicate-definition rejection; use
an AST transform only if the current two-constant scope grows.

## 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
- [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: public CLI, configuration,
model IDs, and user-visible behavior are unchanged; the existing DCode
quickstart is implementation-neutral.
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — the prior security and supply-chain approval is
#6431 (review);
fresh exact-head re-review will be requested after the current full
fan-out because the head changed.
- [x] Non-success, skipped, or missing CI check accepted by maintainer —
`e2e-all` baseline failures accepted in
#6431 (review);
follow-ups #6381/#6384 and #6467/#6474.

## Verification
- [x] PR description includes the DCO sign-off declaration 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 — `npm run check:diff` passed on
`e78d3ef7`; the independently runnable image/runtime and
credential-boundary suites passed 18 of 18 and 123 of 123; the final
cross-surface security/parity audit passed 96 of 96; fresh-cache
real-wheel validation and the isolated three-package import probe
passed.
- [ ] Applicable broad gate passed — exact-head focused DCode run
[28971629788](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629788)
and cloud-onboard run
[28971629739](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629739)
passed on `e78d3ef7`; full fan-out run
[28972330045](https://github.com/NVIDIA/NemoClaw/actions/runs/28972330045)
is in progress. The prior full run
[28919686103](https://github.com/NVIDIA/NemoClaw/actions/runs/28919686103)
passed 77 of 79 applicable jobs; its two failures reproduced identically
on retry and `main` run
[28911441118](https://github.com/NVIDIA/NemoClaw/actions/runs/28911441118),
with the prior maintainer waiver recorded
[here](#6431 (review)).
- [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)

## Exact-head advisor evidence

- [`ubuntu-repo-cloud-langchain-deepagents-code` run
28971629788](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629788)
passed on exact head `e78d3ef7f43f3242486dedc4b5b2b42e0585d041`. The
production-image validator covered plugin discovery and
installed-distribution binding, official source hashes, both aliases and
all 12 middleware entries, unrelated-model isolation, graph compilation,
and allowed/denied execute dispatch parity.
- The same exact-head run passed the real-Docker stripped-dependency
import gate before live E2E, then passed image version checks
(`deepagents-code==0.1.34`, `deepagents==0.7.0a6`), direct and
login-shell headless `PONG`, and interactive TUI acceptance with the
optional name prompt and no model picker.
- The advisor-required [`cloud-onboard` run
28971629739](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629739)
also passed on that exact SHA.
- CodeRabbit is green with no unresolved threads. Exact-head advisor run
28971565095 reported zero GPT findings but requested the runtime
evidence above; Nemotron's two attempts were non-advisory JSON-parse
failures. Both advisors will be rerun against this updated evidence.
- The localized import-gate removal condition is tracked in #6424 rather
than a new cleanup issue.
- Exact-head full fan-out run
[28972330045](https://github.com/NVIDIA/NemoClaw/actions/runs/28972330045)
is in progress; the prior baseline waiver remains applicable only if the
same two unrelated failures recur.

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a first-party Nemotron 3 Ultra profile plugin that registers
managed model aliases.
* **Bug Fixes / Security**
* Strengthened fail-closed validation for the released profile,
including integrity checks and managed vs native dispatch parity (with
denied-shell behavior).
* Hardened secret/credential detection and redaction so `PASS`-keyed
values are treated as sensitive.
* **CI / Quality**
* Added build-time and workflow-boundary checks ensuring images reject
missing base dependencies.
* **Tests**
* Expanded plugin/profile-contract, image behavior, and
end-to-end/workflow coverage.
* **Chores**
* Updated the container build flow to install and validate the plugin
artifact at build time, removing the standalone patch approach.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: cjagwani <cjagwani@nvidia.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Co-authored-by: cjagwani <cjagwani@nvidia.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
<!-- markdownlint-disable MD041 -->
## Summary
Adds a CPU-only live E2E that reproduces the fresh Hermes shields
lifecycle regression from NVIDIA#6381. The test onboards a new non-root Hermes
sandbox, runs shields down/up twice, and preserves the failure as a
dedicated E2E lane until NVIDIA#6384 lands.

## Related Issue
Relates to NVIDIA#6381.

Depends on NVIDIA#6384.

## Changes
- Add a fresh Hermes onboard and two-cycle shields down/up live
regression test.
- Assert the mutable and locked ownership/mode contracts after each
transition.
- Add a dedicated `hermes-shields-config` workflow job that requires no
GPU or hosted inference secret.
- Extend the E2E artifact workflow boundary for the new job.

## 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 exactly 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: test and CI coverage only; no
user-facing behavior changes
- [ ] 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:

## 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 the DCO sign-off declaration 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
- [ ] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — command/result or justification: E2E
workflow support tests passed (24/24), and the live target collects
successfully; the live run is intentionally expected to reproduce NVIDIA#6381
on current `main`
- [ ] 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: Julie Yaunches <jyaunches@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a new Hermes live end-to-end check that runs repeated shields
cycles in a fresh non-root sandbox.
* Updated PR reporting so the new live job is included in the results
summary.
* **Bug Fixes**
* Updated E2E artifact upload workflow boundary validations to match the
current number of expected E2E execution jobs and default callers.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
…VIDIA#6431)

<!-- markdownlint-disable MD041 -->
## Summary
Replaces NemoClaw's build-time mutation of the released Deep Agents
bootstrap with a first-party `deepagents.harness_profiles` plugin for
`deepagents-code==0.1.34` / `deepagents==0.7.0a6`. The two managed
OpenAI-compatible model keys continue to receive the released native
Nemotron 3 Ultra profile, with exact version/source gates and no
third-party source changes.

## Related Issue
Fixes NVIDIA#6424

## Changes
- Add and install `nemoclaw-deepagents-profile==0.1.0` through Deep
Agents' supported profile entry-point lifecycle.
- Register only the two NemoClaw-managed aliases against the released
canonical Ultra profile, atomically and idempotently.
- Fail the image build on missing or unimportable dependencies,
mismatched distribution/package roots, copied/installed adapter-source
drift, or released-profile/bootstrap drift.
- Run a DCode-only negative Docker build from the current hash-locked
base, strip both upstream distributions, and prove failure occurs at the
isolated import gate before the later dependency check.
- Build and install a real unreviewed-version plugin wheel and prove the
actual validator rejects it.
- Verify entry-point discovery, all 12 middleware entries,
unrelated-model isolation, graph compilation, and allowed/denied
execute-dispatch parity against the official wheels.
- Split image/runtime and credential-boundary contracts into balanced
756/755-line suites with a 113-line shared helper, preserving all 75
original tests and substantial per-file size headroom.
- Remove the installed-bootstrap patcher and document that the adapter
must be removed, not rehashed, once reviewed dependencies provide both
exact aliases.
- Preserve the merged DCode hardening and paced `/agents` first-run TUI
behavior from NVIDIA#6410 / NVIDIA#6418.

## Automated review dispositions

- **License metadata:** the production package keeps the PEP 639 SPDX
string and builds unchanged with lock-pinned `setuptools==82.0.1`; the
production validator now requires exact installed-wheel
`License-Expression: Apache-2.0` metadata, with a negative
metadata-drift test. The legacy conversion is a localized offline
wrong-version fixture with explicit source-boundary and
removal-condition documentation. Remove the fixture-only conversion once
runner setuptools accepts PEP 639 strings; production never uses it.
- **Plain-progress build output:** plain progress remains necessary to
prove the exact import-failure marker. Before Docker runs, the gate now
rejects every Docker `ARG` name outside a complete reviewed allowlist,
while tests pin the only passed build arguments to the two public
`BASE_IMAGE` references. Behavior tests inject unreviewed uppercase,
lowercase, and continued ARG declarations across all three Dockerfiles
and prove rejection occurs before any build; the targeted DCode E2E job
runs the same script with real Docker before live tests.
- **Adapter build-layer retention:** Docker can retain the copied
project tree in an image layer or failed local build cache. This is
accepted because it contains only public, first-party Apache-2.0 source
and metadata, while the installed Python module necessarily ships the
same source; revisit if any adapter input becomes secret-bearing or
non-public.
- **Credential redaction parity:** `PASS`/`PASSWD`,
quoted/space-separated assignments, punctuation-bearing values, and
bounded camel/acronym aliases now share the same fail-closed policy
across the Bash wrapper, managed Python runtime, observability scrubber,
config filter, full/sensitive-text redactors, structured-log classifier,
TUI sanitizer, and E2E redactors. The separator lookbehind is capped at
32 horizontal characters to prevent attacker-controlled scans;
private-key blocks are scrubbed before assignment matching. Positive
tests cover `customPass`, `DBPass`, and known secret `*Key` families,
while `COMPASS`/`BYPASS`, `TOPSECRET`/`SUBTOKEN`, pass-rate fields,
`publicKey`, and `customKey` remain untouched.
- **OpenShell TLS key provenance:** the canonical mounted path is
intentionally accepted only from the supervisor-owned runtime
environment and rejected from the mutable DCode `.env`. The split
credential suite now proves both sides explicitly, matching the existing
wrapper-identity coverage; allowing it in `.env` would weaken the
boundary.
- **Docker auth cleanup:** the shared workflow validator requires
exactly one canonical cleanup with `if: always()` as the final job step.
A DCode-specific mutation test now also rejects moving cleanup before
the import gate.
- **Private-key and fixture helpers:** multiline private-key matching is
consolidated into the live generic matcher with a required-newline mode,
preserving comment behavior while removing 12 lines. Profile-hash
fixture replacement is now whitespace/quote tolerant while still
requiring one exact reviewed constant and digest. A focused regression
covers both formatting variants and duplicate-definition rejection; use
an AST transform only if the current two-constant scope grows.

## 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
- [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: public CLI, configuration,
model IDs, and user-visible behavior are unchanged; the existing DCode
quickstart is implementation-neutral.
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — the prior security and supply-chain approval is
NVIDIA#6431 (review);
fresh exact-head re-review will be requested after the current full
fan-out because the head changed.
- [x] Non-success, skipped, or missing CI check accepted by maintainer —
`e2e-all` baseline failures accepted in
NVIDIA#6431 (review);
follow-ups NVIDIA#6381/NVIDIA#6384 and NVIDIA#6467/NVIDIA#6474.

## Verification
- [x] PR description includes the DCO sign-off declaration 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 — `npm run check:diff` passed on
`e78d3ef7`; the independently runnable image/runtime and
credential-boundary suites passed 18 of 18 and 123 of 123; the final
cross-surface security/parity audit passed 96 of 96; fresh-cache
real-wheel validation and the isolated three-package import probe
passed.
- [ ] Applicable broad gate passed — exact-head focused DCode run
[28971629788](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629788)
and cloud-onboard run
[28971629739](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629739)
passed on `e78d3ef7`; full fan-out run
[28972330045](https://github.com/NVIDIA/NemoClaw/actions/runs/28972330045)
is in progress. The prior full run
[28919686103](https://github.com/NVIDIA/NemoClaw/actions/runs/28919686103)
passed 77 of 79 applicable jobs; its two failures reproduced identically
on retry and `main` run
[28911441118](https://github.com/NVIDIA/NemoClaw/actions/runs/28911441118),
with the prior maintainer waiver recorded
[here](NVIDIA#6431 (review)).
- [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)

## Exact-head advisor evidence

- [`ubuntu-repo-cloud-langchain-deepagents-code` run
28971629788](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629788)
passed on exact head `e78d3ef7f43f3242486dedc4b5b2b42e0585d041`. The
production-image validator covered plugin discovery and
installed-distribution binding, official source hashes, both aliases and
all 12 middleware entries, unrelated-model isolation, graph compilation,
and allowed/denied execute dispatch parity.
- The same exact-head run passed the real-Docker stripped-dependency
import gate before live E2E, then passed image version checks
(`deepagents-code==0.1.34`, `deepagents==0.7.0a6`), direct and
login-shell headless `PONG`, and interactive TUI acceptance with the
optional name prompt and no model picker.
- The advisor-required [`cloud-onboard` run
28971629739](https://github.com/NVIDIA/NemoClaw/actions/runs/28971629739)
also passed on that exact SHA.
- CodeRabbit is green with no unresolved threads. Exact-head advisor run
28971565095 reported zero GPT findings but requested the runtime
evidence above; Nemotron's two attempts were non-advisory JSON-parse
failures. Both advisors will be rerun against this updated evidence.
- The localized import-gate removal condition is tracked in NVIDIA#6424 rather
than a new cleanup issue.
- Exact-head full fan-out run
[28972330045](https://github.com/NVIDIA/NemoClaw/actions/runs/28972330045)
is in progress; the prior baseline waiver remains applicable only if the
same two unrelated failures recur.

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a first-party Nemotron 3 Ultra profile plugin that registers
managed model aliases.
* **Bug Fixes / Security**
* Strengthened fail-closed validation for the released profile,
including integrity checks and managed vs native dispatch parity (with
denied-shell behavior).
* Hardened secret/credential detection and redaction so `PASS`-keyed
values are treated as sensitive.
* **CI / Quality**
* Added build-time and workflow-boundary checks ensuring images reject
missing base dependencies.
* **Tests**
* Expanded plugin/profile-contract, image behavior, and
end-to-end/workflow coverage.
* **Chores**
* Updated the container build flow to install and validate the plugin
artifact at build time, removing the standalone patch approach.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: cjagwani <cjagwani@nvidia.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Co-authored-by: cjagwani <cjagwani@nvidia.com>
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 bug-fix PR fixes a bug or regression integration: hermes Hermes integration behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Ubuntu 24.04][Sandbox] nemohermes shields down fails on fresh Hermes sandbox — strict hash verification failed for Hermes restart seal

6 participants