Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
90da1e0
refactor(agent): derive state handling from definitions
jyaunches Aug 3, 2026
3d7a4a2
merge: resolve conflicts with main
github-actions[bot] Aug 3, 2026
622dc27
refactor(agent): address state definition review
jyaunches Aug 3, 2026
f6f0085
test(state): validate Hermes config digest fixture
jyaunches Aug 3, 2026
d5313dc
merge: refresh from main
jyaunches Aug 3, 2026
6bec1a8
docs(security): clarify state guard boundary
jyaunches Aug 3, 2026
445b9c6
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
f622c45
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
493f035
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
e4e2c14
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
1278b61
merge(main): refresh PR and address review
jyaunches Aug 4, 2026
410e4b2
fix(shields): authorize startup state repair
jyaunches Aug 4, 2026
0a633eb
merge(main): integrate deadline recovery serialization
jyaunches Aug 4, 2026
fa97f8b
chore(ci): retry transient PR gate
jyaunches Aug 4, 2026
478243b
refactor(state): avoid plan variable shadowing
jyaunches Aug 4, 2026
fa5ae3e
test(e2e): accept managed startup paths
jyaunches Aug 4, 2026
d7bb3cd
merge(main): refresh PR after E2E
jyaunches Aug 4, 2026
a6257fb
merge: resolve conflicts with main
github-actions[bot] Aug 4, 2026
4151bd7
test(e2e): retry transient DCode status
jyaunches Aug 4, 2026
16e0897
merge(main): refresh PR after E2E hardening
jyaunches Aug 4, 2026
452ad68
merge(main): refresh PR after transport diagnostics
jyaunches Aug 4, 2026
62006d5
test(e2e): keep retry assertions linear
jyaunches Aug 4, 2026
e4f37f4
merge(main): refresh PR after image qualification
jyaunches Aug 4, 2026
cc396e6
test(e2e): avoid dynamic shell sourcing
jyaunches Aug 4, 2026
5a26175
test(e2e): bound generated driver processes
jyaunches Aug 4, 2026
e9fe555
merge(main): refresh PR after image provenance fix
jyaunches Aug 4, 2026
c8bbb5f
test(tunnel): allow coverage-load startup time
jyaunches Aug 4, 2026
a687345
merge(main): refresh PR 8143 base
jyaunches Aug 4, 2026
13b355a
merge(main): refresh PR after E2E
jyaunches Aug 4, 2026
e225e16
merge(main): refresh PR after E2E verification
jyaunches Aug 4, 2026
42d2631
merge(main): refresh PR 8143 base
jyaunches Aug 5, 2026
ba8141c
docs(security): correct OpenClaw confidential state
jyaunches Aug 5, 2026
7d7ce4d
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
5c44a4d
test(shields): isolate recovery policy command
jyaunches Aug 5, 2026
72936fa
merge: refresh main
jyaunches Aug 5, 2026
c6b6201
test(shields): pin recovery command fixture
jyaunches Aug 5, 2026
7c86cbf
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
3099cdc
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
9809a36
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
0641b39
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
fd98869
test(state): align refreshed fixtures with current contracts
jyaunches Aug 5, 2026
eaadbfc
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
bbe50fb
docs(security): clarify Hermes state protection
jyaunches Aug 5, 2026
e4799bd
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
e8910b0
test(e2e): wait for sandbox readiness after restart
jyaunches Aug 5, 2026
7062e02
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
9f6554e
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
1fa40e3
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
f155f30
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
820292c
test(state): align merged target fixtures
jyaunches Aug 5, 2026
f59602b
merge: refresh main for PR #8143
jyaunches Aug 5, 2026
29564c9
docs(security): align state plan inventory
jyaunches Aug 5, 2026
2154233
merge: reconcile published PR history
jyaunches Aug 5, 2026
15ede40
test(e2e): support flat Hermes cron state
jyaunches Aug 5, 2026
b95ca42
test(e2e): validate redacted Hermes cron receipt
jyaunches Aug 5, 2026
4d26e8e
merge: sync main into PR 8143
jyaunches Aug 5, 2026
c8c71c7
test(e2e): exercise Hermes cron scheduler drain
jyaunches Aug 5, 2026
582ba9a
merge: sync main into PR 8143
jyaunches Aug 5, 2026
dc18539
merge: sync latest main into PR 8143
jyaunches Aug 5, 2026
d89caf6
test(e2e): tolerate Hermes restart status transient
jyaunches Aug 5, 2026
14c334a
merge: sync security review rubric from main
jyaunches Aug 5, 2026
e25a51f
merge: resolve conflicts with main
github-actions[bot] Aug 5, 2026
af5d956
merge: sync latest main into PR 8143
jyaunches Aug 6, 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
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
COPY scripts/lib/clean_runtime_shell_env_shim.py /usr/local/lib/nemoclaw/clean_runtime_shell_env_shim.py
COPY scripts/lib/normalize_mutable_config_perms.py /usr/local/lib/nemoclaw/normalize_mutable_config_perms.py
COPY scripts/state-dir-guard.py /usr/local/lib/nemoclaw/state-dir-guard.py
COPY agents/openclaw/state-lock-plan.json /usr/local/share/nemoclaw/state-lock-plan.json
COPY scripts/openclaw-config-guard.py /usr/local/lib/nemoclaw/openclaw-config-guard.py
COPY scripts/managed-gateway-control.py /usr/local/lib/nemoclaw/managed-gateway-control.py
COPY scripts/nemoclaw-start.sh /usr/local/bin/nemoclaw-start
Expand Down Expand Up @@ -1103,12 +1104,14 @@
&& 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/share/nemoclaw/state-lock-plan.json \
/usr/local/lib/nemoclaw/openclaw-config-guard.py \
/usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 700 /usr/local/bin/nemoclaw-gateway-control \
&& chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py \
/usr/local/lib/nemoclaw/openclaw-config-guard.py \
/usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 444 /usr/local/share/nemoclaw/state-lock-plan.json \
&& chmod 444 /usr/local/lib/nemoclaw/gateway-supervisor.sh \
/usr/local/lib/nemoclaw/entrypoint-env-wrapper.sh \
/usr/local/lib/nemoclaw/sandbox-rlimits.sh \
Expand Down Expand Up @@ -1189,10 +1192,10 @@
# CHAT_UI_URL is a non-loopback address (Brev Launchable, remote deployments)
# since terminal-based pairing is impossible in those contexts.
# Default: "0" (device auth enabled for local deployments — secure by default).
ARG NEMOCLAW_DISABLE_DEVICE_AUTH=0

Check warning on line 1195 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
# Internal audit provenance for the opt-out above. Standard onboarding rewrites
# this to managed-onboard; direct image builders retain operator provenance.
ARG NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE=operator

Check warning on line 1198 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
# Compatibility build arg for older custom Dockerfiles and rebuild tooling.
# NemoClaw-managed images intentionally do not consume it; gateway auth tokens
# are generated at container startup and are never baked into image layers.
Expand Down Expand Up @@ -1226,7 +1229,7 @@
# NEMOCLAW_MESSAGING_PLAN_B64 intentionally remains ARG-only: Docker exposes it
# to build RUN processes without retaining the full plan in the final image env.
# Direct ARG interpolation into inline source is a code injection vector (C-2).
ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \

Check warning on line 1232 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 1232 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
NEMOCLAW_INFERENCE_PROVIDER_ID=${NEMOCLAW_INFERENCE_PROVIDER_ID} \
NEMOCLAW_UPSTREAM_PROVIDER=${NEMOCLAW_UPSTREAM_PROVIDER} \
NEMOCLAW_PRIMARY_MODEL_REF=${NEMOCLAW_PRIMARY_MODEL_REF} \
Expand Down Expand Up @@ -1830,6 +1833,7 @@
&& check_metadata /usr/local/lib/nemoclaw/managed-bootstrap-trampoline.sh 'root:root:444' \
&& check_metadata /usr/local/bin/nemoclaw-gateway-control 'root:root:700' \
&& check_metadata /usr/local/lib/nemoclaw/state-dir-guard.py 'root:root:500' \
&& check_metadata /usr/local/share/nemoclaw/state-lock-plan.json 'root:root:444' \
&& check_metadata /usr/local/lib/nemoclaw/preloads/sandbox-safety-net.js 'root:root:644' \
&& check_metadata /scripts/checks/node-tar-image-scan.mts 'root:root:755' \
&& install -d -m 0755 /usr/local/share/nemoclaw \
Expand Down
6 changes: 4 additions & 2 deletions agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ COPY agents/hermes/mcp-config-transaction.py /usr/local/lib/nemoclaw/hermes-mcp-
COPY agents/hermes/cron-restore-control.py /usr/local/lib/nemoclaw/hermes-cron-restore-control.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 agents/hermes/state-lock-plan.json /usr/local/share/nemoclaw/state-lock-plan.json
COPY nemoclaw-blueprint/scripts/*.js /usr/local/lib/nemoclaw/preloads/

FROM scratch AS hermes-wrapper-payload
Expand Down Expand Up @@ -359,10 +360,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/bin/nemoclaw-managed-startup-hold /usr/local/bin/nemoclaw-managed-bootstrap /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/hermes-cron-restore-control.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/share/nemoclaw/state-lock-plan.json /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/hermes-cron-restore-control.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \
&& chmod 700 /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/hermes-cron-restore-control.py \
&& chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 444 /usr/local/lib/nemoclaw/entrypoint-env-wrapper.sh /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py \
&& chmod 444 /usr/local/lib/nemoclaw/entrypoint-env-wrapper.sh /usr/local/share/nemoclaw/state-lock-plan.json /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 \
&& if [ -d /usr/local/lib/nemoclaw/preloads ]; then \
Expand Down Expand Up @@ -1146,6 +1147,7 @@ RUN check_metadata() { \
&& test ! -L /usr/local/lib/nemoclaw/managed-bootstrap-trampoline.sh \
&& check_metadata /usr/local/lib/nemoclaw/managed-bootstrap-trampoline.sh 'root:root 444' \
&& check_metadata /usr/local/bin/nemoclaw-gateway-control 'root:root 700' \
&& check_metadata /usr/local/share/nemoclaw/state-lock-plan.json 'root:root 444' \
&& check_metadata /usr/local/lib/nemoclaw/hermes-cron-restore-control.py '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
41 changes: 32 additions & 9 deletions agents/hermes/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,40 +57,63 @@ config:
dir: /sandbox/.hermes
config_file: config.yaml
env_file: .env # relative to dir — API keys
# Additional generated config files that Shields seals with config_file and
# .config-hash. Each entry must exist before a Shields transition. The
# runtime-config-guard parity test protects the separate in-image boundary.
shields_files:
- .env
auth_file: auth.json # OAuth tokens (Nous Portal, Codex, etc.)
format: yaml

# ── State directories ──────────────────────────────────────────
# All state dirs live under the single config dir now that
# immutable/writable split has been removed.
state_lock_plan_in_image: true
state_dirs:
- memories
- sessions
- skills
- plugins
- cron
- path: skills
shields: read-only
- path: plugins
shields: read-only
- path: cron
shields: read-only
# User-authored cron scripts are part of the job definition contract. Keep
# them in the same rebuild backup so active jobs never outlive their inputs.
- scripts
# Hermes creates this mutable integration directory today, and the existing
# Shields helper protects it. Keep that lock behavior without newly treating
# its contents as portable snapshot state.
- path: hooks
backup: false
shields: read-only
- logs
- skins
- path: skins
shields: read-only
- plans
- workspace
- profiles
- path: workspace
shields: read-only
- path: profiles
shields: read-only
writable_subpaths:
- dashboard-home
- cache
- pairing
- path: pairing
shields: confidential
# Legacy pre-#7200 dashboard profile location. Keep it in snapshots while
# startup migrates existing state into profiles/dashboard-home.
- dashboard-home
# Hermes' WhatsApp bridge stores QR-paired session credentials under
# ~/.hermes/platforms/whatsapp/session. Preserve the parent so rebuilds
# keep the in-sandbox pairing state without re-scanning.
- platforms
- path: platforms
shields: read-only
# Hermes' iLink WeChat adapter persists per-account context tokens under
# ~/.hermes/weixin/accounts/<id>.context-tokens.json so the long-poll
# cursor survives a rebuild. The bot token itself comes from .env via
# the L7 proxy and is not stored on disk inside the sandbox.
- weixin
- path: weixin
shields: read-only

# ── Top-level durable state files ───────────────────────────────
# NemoClaw stores Hermes gateway-created top-level state under runtime/ and
Expand Down
19 changes: 18 additions & 1 deletion agents/hermes/runtime-config-guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
)
NEMOCLAW_START_ARGV = (b"nemoclaw-start", b"/usr/local/bin/nemoclaw-start")
OPENSHELL_SUPERVISOR_ARGV0 = b"/opt/openshell/bin/openshell-sandbox"
# Keep this in exact parity with manifest config_file + config.shields_files +
# .config-hash. The host manifest remains authoritative for host transitions;
# the integration test protects this separate in-image recovery boundary.
SEALED_FILE_NAMES = ("config.yaml", ".env", ".config-hash")
RESTART_ORPHAN_MARKER_NAME = ".nemoclaw-hermes-restart-seal"
SHIELDS_TRANSITION_LEASE_SECONDS = 300
Expand Down Expand Up @@ -4411,7 +4414,11 @@ def abort_shields_transition(hermes_dir: str, state_file: str, lock_token: str)


def run_state_dir_transition(
hermes_dir: str, state_file: str, lock_token: str, action: str
hermes_dir: str,
state_file: str,
lock_token: str,
action: str,
state_lock_plan_json: str,
) -> None:
if action not in ("lock", "unlock"):
raise UnsafePathError("refusing unsupported Hermes state-dir action")
Expand All @@ -4433,6 +4440,13 @@ def run_state_dir_transition(
helper = installed if os.path.isfile(installed) else checkout
if not os.path.isfile(helper):
raise UnsafePathError("Hermes state-dir guard is unavailable")
if state_lock_plan_json:
plan_args = ["--plan-json", state_lock_plan_json]
else:
plan_file = "/usr/local/share/nemoclaw/state-lock-plan.json"
if not os.path.isfile(plan_file):
raise UnsafePathError("Hermes state lock plan is unavailable")
plan_args = ["--plan-file", plan_file]
# Preserve this exact PID/start identity as GNU timeout while it owns and
# waits for the recursive worker. Cancel the Python alarm before exec so
# timeout alone owns TERM/KILL tree cleanup and no orphan child survives.
Expand All @@ -4449,6 +4463,7 @@ def run_state_dir_transition(
action,
"--config-dir",
hermes_dir,
*plan_args,
],
)

Expand Down Expand Up @@ -5004,6 +5019,7 @@ def main() -> int:
parser.add_argument("--expected-config-sha256", default="")
parser.add_argument("--lock-token", default="")
parser.add_argument("--state-action", choices=("lock", "unlock"), default="")
parser.add_argument("--state-lock-plan-json", default="")
parser.add_argument("--shields-mode", choices=("locked", "mutable"), default="")
parser.add_argument(
"--rollback-shields-mode", choices=("locked", "mutable"), default=""
Expand Down Expand Up @@ -5186,6 +5202,7 @@ def main() -> int:
args.state_file,
args.lock_token,
args.state_action,
args.state_lock_plan_json,
)
except UnsafePathError as exc:
_die(str(exc))
Expand Down
23 changes: 23 additions & 0 deletions agents/hermes/state-lock-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$comment": "SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\nSPDX-License-Identifier: Apache-2.0",
"version": 1,
"readOnlyRoots": [
"cron",
"hooks",
"platforms",
"plugins",
"profiles",
"skills",
"skins",
"weixin",
"workspace"
],
"confidentialRoots": [
"pairing"
],
"readOnlyPrefixes": [],
"confidentialPrefixes": [],
"writableSubpaths": [
"profiles/dashboard-home"
]
}
7 changes: 5 additions & 2 deletions agents/langchain-deepagents-code/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,13 @@ config:
# /sandbox/.deepagents. The built-in skill-creator writes user skills to
# agent/skills (per its init_skill.py), so it must be backed up alongside
# .state and skills — otherwise skills are silently lost on rebuild.
state_lock_plan_in_image: false
state_dirs:
- .state
- skills
- agent/skills
- path: skills
shields: read-only
- path: agent/skills
shields: read-only

# ── Top-level durable state files ───────────────────────────────
# config.toml mixes DCode preferences with NemoClaw-managed model routing.
Expand Down
76 changes: 49 additions & 27 deletions agents/openclaw/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,34 +45,56 @@ config:
format: json

# ── State directories ──────────────────────────────────────────
state_lock_plan_in_image: true
state_dirs:
- agents
- extensions
- workspace
- skills
- hooks
- identity
- devices
- canvas
- cron
- memory
- telegram
- wechat
- whatsapp
- credentials

# Machine-local gateway auth state: the Ed25519 device identity
# (identity/device.json) and paired-device token store (devices/). Backup
# sanitization scrubs their key/token fields, so a restored copy can never
# authenticate — restoring it replaces working pairing state with corrupt
# files and the CLI fails with GatewayCredentialsRequiredError (issue #6852).
# These dirs stay in state_dirs so destroy still wipes them from the durable
# volume, but they are never captured into or restored from snapshots;
# OpenClaw regenerates the identity on demand and NemoClaw auto-pair
# re-pairs on connect.
runtime_auth_state_dirs:
- identity
- devices
- path: agents
shields: read-only
writable_subpaths:
- "*/sessions"
- path: extensions
shields: read-only
# Legacy layouts can still contain this pre-extensions directory. Preserve
# its existing Shields posture without newly treating it as portable state.
- path: plugins
backup: false
shields: read-only
# OpenClaw may create profile-scoped runtime state. Preserve its existing
# Shields boundary without newly treating profile data as portable state.
- path: profiles
backup: false
shields: read-only
- path: workspace
shields: read-only
# Multi-agent OpenClaw deployments create workspace-<agent> siblings.
- prefix: workspace-
shields: read-only
- path: skills
shields: read-only
- path: hooks
shields: read-only
# Machine-local gateway auth state is wiped on destroy but never captured.
# Sanitization removes the identity key and paired-device tokens, so a
# restored copy cannot authenticate (#6852).
- path: identity
backup: false
shields: confidential
- path: devices
backup: false
shields: read-only
- path: canvas
shields: read-only
- path: cron
shields: read-only
- path: memory
shields: read-only
- path: telegram
shields: read-only
- path: wechat
shields: read-only
- path: whatsapp
shields: read-only
- path: credentials
shields: confidential

# ── Top-level durable state files ───────────────────────────────
# openclaw.json holds the core OpenClaw settings the state dirs above do not
Expand Down
31 changes: 31 additions & 0 deletions agents/openclaw/state-lock-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$comment": "SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\nSPDX-License-Identifier: Apache-2.0",
"version": 1,
"readOnlyRoots": [
"agents",
"canvas",
"cron",
"devices",
"extensions",
"hooks",
"memory",
"plugins",
"profiles",
"skills",
"telegram",
"wechat",
"whatsapp",
"workspace"
],
"confidentialRoots": [
"credentials",
"identity"
],
"readOnlyPrefixes": [
"workspace-"
],
"confidentialPrefixes": [],
"writableSubpaths": [
"agents/*/sessions"
]
}
5 changes: 5 additions & 0 deletions ci/source-shape-test-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
"test": "accepts only the tracked published Hermes base digest",
"category": "security"
},
{
"file": "src/lib/agent/state-directory-contract.test.ts",
"test": "keeps generated image plans equal to their AgentDefinition projections (#8006)",
"category": "security"
},
{
"file": "src/lib/onboard/managed-startup-profile.test.ts",
"test": "classifies every stock Docker ARG as startup-affordance or deliberate exclusion",
Expand Down
2 changes: 1 addition & 1 deletion ci/test-file-size-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"src/lib/onboard/preflight.test.ts": 1904,
"test/generate-openclaw-config.test.ts": 1941,
"test/install-preflight.test.ts": 3921,
"test/nemoclaw-start.test.ts": 4817,
"test/nemoclaw-start.test.ts": 4791,
"test/onboard-messaging.test.ts": 2043,
"test/onboard-selection.test.ts": 4769
}
Expand Down
3 changes: 3 additions & 0 deletions docs/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,9 @@ navigation:
- page: "Credential Storage"
path: _build/agent-variants/security/credential-storage.deepagents.generated.mdx
slug: credential-storage
- page: "Trusted Computing Base"
path: _build/agent-variants/security/tcb-boundary.deepagents.generated.mdx
slug: trusted-computing-base
- page: "OpenShell 0.0.72 Compatibility Review"
path: _build/agent-variants/security/openshell-0.0.72-compatibility-review.deepagents.generated.mdx
slug: openshell-0.0.72-compatibility-review
Expand Down
Loading
Loading