Skip to content
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6462e22
fix(state): restore Hermes cron scripts before enabling restored jobs
laitingsheng Jul 30, 2026
7da56a0
test(state): keep the staged restore test body linear
laitingsheng Jul 30, 2026
617ea5d
fix(state): remove the restore archive copy when publishing fails
laitingsheng Jul 30, 2026
66e47a6
test(state): assert the failed publication attempt in staged restore
laitingsheng Jul 30, 2026
dfde003
Merge remote-tracking branch 'origin/main' into fix/hermes-cron-scrip…
laitingsheng Jul 30, 2026
a5a1de4
merge: sync Hermes cron restore with main
apurvvkumaria Jul 30, 2026
507a354
merge: sync Hermes cron restore with main
apurvvkumaria Jul 30, 2026
bef478a
fix(shields): lock restored Hermes cron scripts
apurvvkumaria Jul 30, 2026
191d7d1
Merge remote-tracking branch 'origin/main' into codex/pr7880-hermes-l…
apurvvkumaria Jul 30, 2026
cef29e3
fix(state): roll back failed staged restores
apurvvkumaria Jul 30, 2026
447a759
test(state): keep rollback fixtures linear
apurvvkumaria Jul 30, 2026
446f157
merge(main): sync PR #7880 with current main
apurvvkumaria Jul 31, 2026
ae5963b
Merge remote-tracking branch 'origin/main' into codex/pr7880-hermes-l…
apurvvkumaria Jul 31, 2026
25e00ce
fix(state): drain Hermes scheduled work during restore
apurvvkumaria Jul 31, 2026
9ec10b3
test(state): linearize Hermes restore fixtures
apurvvkumaria Jul 31, 2026
e198654
test(hermes): cover restore guard image contracts
apurvvkumaria Jul 31, 2026
88f6b0c
merge(main): sync Hermes state restore coverage
apurvvkumaria Jul 31, 2026
b52ec4f
Merge branch 'main' into fix/hermes-cron-script-state-restore
senthilr-nv Jul 31, 2026
7824b3f
merge: resolve conflicts with main
github-actions[bot] Aug 2, 2026
a5a698f
merge: resolve conflicts with main
github-actions[bot] Aug 3, 2026
6949d3e
fix(state): close the staged restore command builder
laitingsheng Aug 4, 2026
664b91f
merge(main): refresh #7880 branch
laitingsheng Aug 4, 2026
9affeda
fix(state): hold Hermes restore drain ownership atomically
laitingsheng Aug 4, 2026
2056cc6
merge(main): refresh #7880 branch
laitingsheng Aug 4, 2026
2cd6d4e
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
bc0391d
merge(main): refresh #7880 branch
laitingsheng Aug 5, 2026
6a74ed6
fix(hermes): pin the current restore cron guard digest
laitingsheng Aug 5, 2026
af6abcc
merge(main): refresh #7880 branch
laitingsheng Aug 5, 2026
8f029ed
merge(main): refresh #7880 branch
laitingsheng Aug 5, 2026
f7cba25
Merge branch 'main' into fix/hermes-cron-script-state-restore
cv Aug 5, 2026
a7544ee
fix(hermes): reject cron scripts the gateway cannot reach
laitingsheng Aug 5, 2026
554aa38
merge(main): refresh #7880 branch
laitingsheng Aug 5, 2026
61b91a3
merge(main): refresh #7880 branch
laitingsheng Aug 5, 2026
1e263e6
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ COPY agents/hermes/runtime-config-guard.py /usr/local/lib/nemoclaw/hermes-runtim
COPY agents/hermes/finalize-tirith-marker.py /usr/local/lib/nemoclaw/finalize-tirith-marker.py
COPY agents/hermes/build-mcp-digest.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py
COPY agents/hermes/mcp-config-transaction.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py
COPY agents/hermes/restore-cron-guard.py /usr/local/lib/nemoclaw/hermes-restore-cron-guard.py
COPY src/lib/actions/sandbox/openshell-child-visible-credentials.v0.0.85.json /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json
COPY scripts/state-dir-guard.py /usr/local/lib/nemoclaw/state-dir-guard.py
COPY nemoclaw-blueprint/scripts/*.js /usr/local/lib/nemoclaw/preloads/
Expand Down Expand Up @@ -224,9 +225,10 @@ RUN chmod -R a+rX /opt/nemoclaw-blueprint/
# minimum supported Hermes sandbox base tag guarantees those artifacts and
# test/sandbox-rlimit-hooks.test.ts covers that base.
RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/lib/nemoclaw/sandbox-init.sh /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py /usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py /usr/local/lib/nemoclaw/seed-hermes-dashboard-config.py /usr/local/lib/nemoclaw/hermes-runtime-config-guard.py /usr/local/lib/nemoclaw/finalize-tirith-marker.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py \
&& chown root:root /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
&& chown root:root /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/hermes-restore-cron-guard.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
&& chmod 700 /usr/local/bin/nemoclaw-gateway-control \
&& chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 555 /usr/local/lib/nemoclaw/hermes-restore-cron-guard.py \
&& chmod 444 /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py \
&& chmod 444 /usr/local/lib/nemoclaw/patch-hermes-langfuse-credentials.mts \
&& chmod 444 /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
Expand Down Expand Up @@ -402,12 +404,13 @@ RUN node --experimental-strip-types \
# accompanied by an updated hash below; otherwise the build fails. This blocks
# silent supply-chain tampering of the build context (an attacker rewriting a
# file has to also rewrite the Dockerfile-committed hash, which reviewers gate).
# Regenerate with `sha256sum agents/hermes/{hermes-wrapper.py,hermes-cli-adapter-v1.json,validate-cli-adapter.py,validate-env-secret-boundary.py,finalize-tirith-marker.py}`.
# Regenerate with `sha256sum agents/hermes/{hermes-wrapper.py,hermes-cli-adapter-v1.json,validate-cli-adapter.py,validate-env-secret-boundary.py,finalize-tirith-marker.py,restore-cron-guard.py}`.
ARG NEMOCLAW_HERMES_WRAPPER_SHA256=a841a3673cd2543dd53559e513741dd66929361b8ca94b9196b72badf7827d3c
ARG NEMOCLAW_HERMES_CLI_ADAPTER_SHA256=989edf54a8c09c6efb348600a8aa2f264c0b71408eb9d7bcd579b92cbeccf9b1
ARG NEMOCLAW_HERMES_CLI_ADAPTER_VALIDATOR_SHA256=db4046e79e513eab67b069a8eda20167b8b65529cf26842531d2ad673c670330
ARG NEMOCLAW_HERMES_VALIDATOR_SHA256=822c7e63d068c5d09f3291350771c1a42c9686f51bfa9bc9a1f41fbe15d163b1
ARG NEMOCLAW_HERMES_TIRITH_FINALIZER_SHA256=a1e6b1c53ab297569abb87c29d15c294d729e46005bfd022136b4c447a791819
ARG NEMOCLAW_HERMES_RESTORE_CRON_GUARD_SHA256=c224191fac19e4c7bc1d4416e2d1b243d7bd925af02fd25fa4d2c4cf6777345d
# hadolint ignore=DL4006
RUN printf '%s %s\n' \
"$NEMOCLAW_HERMES_VALIDATOR_SHA256" /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py \
Expand All @@ -418,6 +421,11 @@ RUN printf '%s %s\n' \
"$NEMOCLAW_HERMES_TIRITH_FINALIZER_SHA256" /usr/local/lib/nemoclaw/finalize-tirith-marker.py \
| sha256sum -c - \
|| { echo "ERROR: finalize-tirith-marker.py hash mismatch (update NEMOCLAW_HERMES_TIRITH_FINALIZER_SHA256)" >&2; exit 1; }
# hadolint ignore=DL4006
RUN printf '%s %s\n' \
"$NEMOCLAW_HERMES_RESTORE_CRON_GUARD_SHA256" /usr/local/lib/nemoclaw/hermes-restore-cron-guard.py \
| sha256sum -c - \
|| { echo "ERROR: hermes-restore-cron-guard.py hash mismatch (update NEMOCLAW_HERMES_RESTORE_CRON_GUARD_SHA256)" >&2; exit 1; }

# Wrap the hermes CLI so the runtime env secret boundary is enforced for
# `hermes gateway` no matter how it is invoked. The entrypoint guard alone left
Expand Down Expand Up @@ -916,6 +924,7 @@ RUN check_metadata() { \
&& check_metadata /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py 'root:root 755' \
&& check_metadata /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py 'root:root 755' \
&& check_metadata /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py 'root:root 755' \
&& check_metadata /usr/local/lib/nemoclaw/hermes-restore-cron-guard.py 'root:root 555' \
&& check_metadata /usr/local/bin/nemoclaw-gateway-control 'root:root 700' \
&& check_metadata /usr/local/lib/nemoclaw/preloads/sandbox-safety-net.js 'root:root 444' \
&& check_metadata /usr/local/lib/nemoclaw/hermes-wrapper.py 'root:root 755' \
Expand Down
4 changes: 4 additions & 0 deletions agents/hermes/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ state_dirs:
- sessions
- skills
- plugins
# Hermes confines the scripts that no_agent cron jobs run to
# ~/.hermes/scripts and rejects any path outside it, so cron job definitions
# are only restorable together with this directory.
- scripts
- cron
- logs
- skins
Expand Down
186 changes: 186 additions & 0 deletions agents/hermes/restore-cron-guard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/opt/hermes/.venv/bin/python -I
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Quiesce Hermes cron dispatch while NemoClaw restores scheduled work state."""

from __future__ import annotations

import argparse
import json
import os
import secrets
import sys
import time
from pathlib import Path
from typing import Any

_OWNER_PREFIX = "nemoclaw-state-restore:"
_POLL_INTERVAL_SECONDS = 0.1


def _configure_home(raw_home: str) -> Path:
home = Path(raw_home)
if not home.is_absolute():
raise ValueError("Hermes restore guard requires an absolute --home path")
os.environ["HERMES_HOME"] = str(home)
return home


def _gateway_modules() -> tuple[Any, Any]:
from gateway import drain_control, status

return drain_control, status


def _runtime_is_safely_drained(status: Any, pid: int) -> bool:
runtime = status.read_runtime_status()
return bool(
isinstance(runtime, dict)
and runtime.get("pid") == pid
and runtime.get("gateway_state") == "draining"
and status.parse_active_agents(runtime.get("active_agents")) == 0
)


def _owned_marker_present(drain_control: Any, home: Path, token: str) -> bool:
marker = drain_control.read_drain_request(home=home)
return bool(isinstance(marker, dict) and marker.get("principal") == token)


def _release_owned_marker(drain_control: Any, home: Path, token: str) -> None:
if not _owned_marker_present(drain_control, home, token):
return
if not drain_control.clear_drain_request(home=home):
raise RuntimeError("Hermes restore guard could not clear its drain marker")


def begin_drain(home: Path, timeout_seconds: float) -> str:
drain_control, status = _gateway_modules()
pid = status.get_running_pid()
if pid is None:
return "inactive"

token = ""
if not drain_control.drain_requested(home=home):
token = f"{_OWNER_PREFIX}{secrets.token_hex(16)}"
drain_control.write_drain_request(principal=token, home=home)

deadline = time.monotonic() + timeout_seconds
try:
while time.monotonic() < deadline:
live_pid = status.get_running_pid()
if live_pid is None or _runtime_is_safely_drained(status, live_pid):
return token or "preserved"
time.sleep(_POLL_INTERVAL_SECONDS)
except BaseException:
if token:
_release_owned_marker(drain_control, home, token)
raise

if token:
_release_owned_marker(drain_control, home, token)
raise TimeoutError(
f"Hermes gateway did not drain active messaging, API, and cron work within {timeout_seconds:g}s"
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def assert_safely_drained(home: Path) -> None:
drain_control, status = _gateway_modules()
pid = status.get_running_pid()
if pid is None:
return
if not drain_control.drain_requested(home=home) or not _runtime_is_safely_drained(
status, pid
):
raise RuntimeError("Hermes gateway is not safely drained for scheduled-work restore")


def _load_jobs(jobs_file: Path) -> list[Any]:
if not jobs_file.exists():
return []
data = json.loads(jobs_file.read_text(encoding="utf-8-sig"))
jobs = data.get("jobs", []) if isinstance(data, dict) else data
if not isinstance(jobs, list):
raise ValueError("Hermes cron database must contain a jobs list")
return jobs


def validate_enabled_scripts(home: Path) -> None:
scripts_dir = (home / "scripts").resolve()
for index, job in enumerate(_load_jobs(home / "cron" / "jobs.json")):
if not isinstance(job, dict):
raise ValueError(f"Hermes cron job at index {index} is not an object")
if not job.get("enabled", True) or job.get("state") == "paused":
continue
script = job.get("script")
if script in {None, ""}:
if job.get("no_agent"):
raise ValueError(
f"Enabled no-agent Hermes cron job at index {index} has no script"
)
continue
if not isinstance(script, str):
raise ValueError(f"Enabled Hermes cron job at index {index} has a non-string script")
raw_path = Path(script).expanduser()
script_path = (
raw_path.resolve()
if raw_path.is_absolute()
else (scripts_dir / raw_path).resolve()
)
try:
script_path.relative_to(scripts_dir)
except ValueError as error:
raise ValueError(
f"Enabled Hermes cron job at index {index} resolves outside the scripts directory"
) from error
if not script_path.is_file() or not os.access(script_path, os.R_OK):
raise ValueError(
f"Enabled Hermes cron job at index {index} references a missing or unreadable script"
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def validate_restore(home: Path) -> None:
assert_safely_drained(home)
validate_enabled_scripts(home)


def release_drain(home: Path, token: str) -> None:
if not token.startswith(_OWNER_PREFIX) or len(token) != len(_OWNER_PREFIX) + 32:
raise ValueError("Invalid Hermes restore drain ownership token")
drain_control, _status = _gateway_modules()
_release_owned_marker(drain_control, home, token)


def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("action", choices=("begin", "assert-safe", "validate", "release"))
parser.add_argument("--home", required=True)
parser.add_argument("--timeout", type=float, default=60.0)
parser.add_argument("--token")
return parser


def main() -> int:
args = _parser().parse_args()
try:
home = _configure_home(args.home)
if args.action == "begin":
if args.timeout <= 0:
raise ValueError("Hermes restore drain timeout must be positive")
print(begin_drain(home, args.timeout))
elif args.action == "assert-safe":
assert_safely_drained(home)
elif args.action == "validate":
validate_restore(home)
else:
if not args.token:
raise ValueError("Hermes restore drain release requires --token")
release_drain(home, args.token)
return 0
except Exception as error:
print(f"Hermes restore guard failed: {error}", file=sys.stderr)
return 1


if __name__ == "__main__":
raise SystemExit(main())
11 changes: 10 additions & 1 deletion docs/manage-sandboxes/backup-restore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ If a copied file or parent directory changes identity during the operation, snap
<AgentOnly variant="hermes">
Hermes snapshots include `SOUL.md`, the Web Dashboard profile under `.hermes/dashboard-home/`, the SQLite database behind `.hermes/state.db`, and the default kanban board in `.hermes/kanban.db`.
The default-profile snapshot also includes cron execution history in `.hermes/runtime/cron-executions.db` and Discord replay state in `.hermes/gateway/discord_message_recovery.db`.
NemoClaw captures cron job definitions from `.hermes/cron` as directory state.
NemoClaw captures cron job definitions from `.hermes/cron` as directory state, together with the scripts they run from `.hermes/scripts`.
A restore moves each state directory into place as a unit and applies `.hermes/cron` after `.hermes/scripts`.
Before replacing either directory, NemoClaw asks the running Hermes gateway to drain and waits until messaging, API, and cron work reaches zero.
It validates every enabled job's referenced script before releasing a drain that NemoClaw created; a drain already owned by an operator remains in place.
If the gateway cannot drain, a referenced script is missing or unreadable, or rollback cannot recover the prior state, the restore fails closed without resuming scheduled work.
NemoClaw uses SQLite's online backup API and restores these databases through SQLite instead of copying live raw database files.
After it replaces a database, NemoClaw opens a write transaction against the result and fails the restore when the database cannot be written.
Named-profile cron and Discord databases under `.hermes/profiles/<name>/` use raw directory capture and can be inconsistent if a write overlaps the snapshot.
Expand Down Expand Up @@ -116,6 +120,11 @@ $$nemoclaw my-assistant snapshot restore before-upgrade
$$nemoclaw my-assistant snapshot restore 2026-04-14T
```

For restore paths that replace state directories as whole units, NemoClaw stages every replacement before it changes live state.
If publication fails before the transaction commits, NemoClaw restores the original directories.
If rollback or cleanup cannot finish, NemoClaw preserves `.nemoclaw-restore-rollback` under the agent state directory and refuses another restore so it does not overwrite the recovery copy.
Preserve the reported recovery path and verify its contents before you retry or remove it.

<AgentOnly variant="hermes">
A running Hermes gateway keeps serving its pre-restore state databases until it reopens them.
After a restore that includes Hermes state databases, the CLI prints a reminder to restart the gateway.
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,8 @@ For OpenClaw, the backed-up paths include agents, extensions, workspace, skills,
</AgentOnly>
<AgentOnly variant="hermes">

For Hermes, the backed-up paths come from `agents/hermes/manifest.yaml`, including `/sandbox/.hermes` state such as memories, sessions, skills, plugins, cron, logs, plans, workspace, messaging platform state, `runtime/state.db`, and the default kanban board in `kanban.db`.
For Hermes, the backed-up paths come from `agents/hermes/manifest.yaml`, including `/sandbox/.hermes` state such as memories, sessions, skills, plugins, scripts, cron, logs, plans, workspace, messaging platform state, `runtime/state.db`, and the default kanban board in `kanban.db`.
During restore, NemoClaw drains the Hermes gateway before replacing scripts or cron definitions, validates enabled-job script references, and then resumes only a drain that it created.
Kanban backup does not include named boards, attachments, worker logs, scratch workspaces under `kanban/`, or external directory or worktree targets.

</AgentOnly>
Expand Down
3 changes: 2 additions & 1 deletion docs/security/best-practices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ Writable agent state such as plugins, skills, hooks, and workspace metadata live

By default, this directory starts writable so the agent can manage its own config, install skills, and write to standard home-directory paths natively.
For sensitive workloads, use a reviewed host-side immutability workflow after initial setup so the sandbox user cannot change config or high-risk state entry points.
The immutability workflow locks high-risk state directories (`skills`, `agent`, `hooks`, `cron`, `agents`, `extensions`, `plugins`, `workspace`, `memory`, `devices`, `canvas`, `telegram`, `wechat`, `whatsapp`, `platforms`, `weixin`, `profiles`, `skins`) to `root:sandbox` and removes group and world write access.
The immutability workflow locks high-risk state directories (`skills`, `agent`, `hooks`, `cron`, `agents`, `extensions`, `plugins`, `scripts`, `workspace`, `memory`, `devices`, `canvas`, `telegram`, `wechat`, `whatsapp`, `platforms`, `weixin`, `profiles`, `skins`) to `root:sandbox` and removes group and world write access.
The root-only helper traverses from opened directory descriptors with no-follow semantics instead of using recursive pathname `chown` or `chmod`.
Read-only preflight and unlock operations reject unsafe external symlinks, hardlinks, special files, cross-device entries, and entries that race the traversal without modifying them.
After the top-level config binding is frozen, lockdown makes containment monotonic.
Expand Down Expand Up @@ -344,6 +344,7 @@ Direct edits to these files can be overwritten when NemoClaw regenerates the ima

Hermes also stores runtime state such as `state.db`, logs, and platform sessions under the `.hermes` tree.
Messaging sessions such as WhatsApp pairing can remain mutable by design so they survive rebuilds.
Hermes rebuild restore blocks new gateway dispatch and waits for active messaging, API, and cron work to drain before replacing cron scripts or job definitions. It keeps that drain in place until enabled script references validate or the prior state is rolled back.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

| Aspect | Detail |
|---|---|
Expand Down
1 change: 1 addition & 0 deletions scripts/state-dir-guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"agent",
"hooks",
"cron",
"scripts",
"agents",
"extensions",
"plugins",
Expand Down
1 change: 1 addition & 0 deletions src/lib/onboard/managed-startup/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@ export const MANAGED_STARTUP_PROFILE_EXCLUDED_DOCKER_INPUTS = {
{ input: "NEMOCLAW_HERMES_BACKUP_SOURCE_SHA256", reason: "integrity-pin" },
{ input: "NEMOCLAW_HERMES_DISCORD_RECOVERY_PATCHER_SHA256", reason: "integrity-pin" },
{ input: "NEMOCLAW_HERMES_LANGFUSE_PATCHER_SHA256", reason: "integrity-pin" },
{ input: "NEMOCLAW_HERMES_RESTORE_CRON_GUARD_SHA256", reason: "integrity-pin" },
{ input: "NEMOCLAW_HERMES_WRAPPER_SHA256", reason: "integrity-pin" },
{ input: "NEMOCLAW_HERMES_CLI_ADAPTER_SHA256", reason: "integrity-pin" },
{ input: "NEMOCLAW_HERMES_CLI_ADAPTER_VALIDATOR_SHA256", reason: "integrity-pin" },
Expand Down
1 change: 1 addition & 0 deletions src/lib/shields/state-dir-lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const HIGH_RISK_STATE_DIRS = [
"agent",
"hooks",
"cron",
"scripts",
"agents",
"extensions",
"plugins",
Expand Down
Loading
Loading