Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
6 changes: 3 additions & 3 deletions .agents/skills/nemoclaw-user-reference/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ $ nemoclaw my-assistant logs [--follow]
Stop the NIM container and delete the sandbox.
This removes the sandbox from the registry.

> **Warning:** Destroying a sandbox permanently deletes all files inside it, including
> workspace files (see the `nemoclaw-user-workspace` skill) (SOUL.md, USER.md, IDENTITY.md, AGENTS.md, MEMORY.md, and daily memory notes).
> Back up your workspace first by following the instructions at Back Up and Restore (see the `nemoclaw-user-workspace` skill).
> **Warning:** This command permanently deletes the sandbox **and its persistent volume**.
> All workspace files (see the `nemoclaw-user-workspace` skill) (SOUL.md, USER.md, IDENTITY.md, AGENTS.md, MEMORY.md, and daily memory notes) are lost.
> Back up your workspace first — see Backup and Restore (see the `nemoclaw-user-workspace` skill).

```console
$ nemoclaw my-assistant destroy
Expand Down
72 changes: 28 additions & 44 deletions .agents/skills/nemoclaw-user-workspace/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
---
name: "nemoclaw-user-workspace"
description: "Backs up and restores OpenClaw workspace files before destructive operations. Use when backing up a sandbox, restoring workspace state, or preparing for a destructive operation. Explains what workspace files are, where they live, and how they persist across sandbox restarts. Use when asking about soul.md, identity.md, memory.md, agents.md, or sandbox file persistence."
description: "Hows to back up and restore OpenClaw workspace files before destructive operations. Whats workspace personality and configuration files are, where they live, and how they persist across sandbox restarts."

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.

⚠️ Potential issue | 🟡 Minor

Fix grammar in frontmatter description.

Line 3 has typos (“Hows”, “Whats”) and reads ungrammatically. This is user-facing metadata and should be corrected in the docs source, then regenerated into this skill file.

✍️ Suggested text
-description: "Hows to back up and restore OpenClaw workspace files before destructive operations. Whats workspace personality and configuration files are, where they live, and how they persist across sandbox restarts."
+description: "How to back up and restore OpenClaw workspace files before destructive operations, and what workspace personality/configuration files are, where they live, and how they persist across sandbox restarts."
Based on learnings: "In NVIDIA/NemoClaw, treat `.agents/skills/nemoclaw-user-*/*.md` (SKILL.md) files as auto-generated... do not flag these as manual edit violations. Only flag ... when the PR does NOT include corresponding changes ... under `docs/`."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: "Hows to back up and restore OpenClaw workspace files before destructive operations. Whats workspace personality and configuration files are, where they live, and how they persist across sandbox restarts."
description: "How to back up and restore OpenClaw workspace files before destructive operations, and what workspace personality/configuration files are, where they live, and how they persist across sandbox restarts."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/nemoclaw-user-workspace/SKILL.md at line 3, Fix the
frontmatter "description" field in SKILL.md by correcting typos and improving
grammar: replace "Hows" with "How to" and "Whats" with "What", and rephrase the
sentence to read like a single clear clause (for example: "How to back up and
restore OpenClaw workspace files before destructive operations, what workspace
personality and configuration files are, where they live, and how they persist
across sandbox restarts."); update the description value in the SKILL.md
frontmatter accordingly and regenerate the auto-generated skill file so the docs
output matches this corrected text.

---

<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# NemoClaw User Workspace

Backs up and restores OpenClaw workspace files before destructive operations. Use when backing up a sandbox, restoring workspace state, or preparing for a destructive operation.
How to back up and restore OpenClaw workspace files before destructive operations.

## Context

OpenClaw stores agent identity, behavior, and memory in a set of Markdown files inside the sandbox.
These files live at `/sandbox/.openclaw/workspace/` and are read by the agent at the start of every session.
OpenClaw stores its personality, user context, and behavioral configuration in a set of Markdown files inside the sandbox.
These files live at `/sandbox/.openclaw/workspace/` and are collectively called **workspace files**.

## File Reference

Each file controls a distinct aspect of the agent's behavior and memory.

| File | Purpose | Upstream Docs |
|---|---|---|
| `SOUL.md` | Core personality, tone, and behavioral rules. | [SOUL template](https://docs.openclaw.ai/reference/templates/SOUL) |
| `USER.md` | Preferences, context, and facts the agent learns about you. | [USER template](https://docs.openclaw.ai/reference/templates/USER) |
| `IDENTITY.md` | Agent name, creature type, emoji, and self-presentation. | [IDENTITY template](https://docs.openclaw.ai/reference/templates/IDENTITY) |
| `AGENTS.md` | Multi-agent coordination, memory conventions, and safety guidelines. | [AGENTS template](https://docs.openclaw.ai/reference/templates/AGENTS) |
| `MEMORY.md` | Curated long-term memory distilled from daily notes. | — |
| `memory/` | Directory of daily note files (`YYYY-MM-DD.md`) for session continuity. | — |
| File | Purpose |
|---|---|
| `SOUL.md` | Defines the agent's persona, tone, and communication style. |
| `USER.md` | Stores information about the human the agent assists. |
| `IDENTITY.md` | Short identity card — name, language, emoji, creature type. |
| `AGENTS.md` | Behavioral rules, memory conventions, safety guidelines, and session workflow. |
| `MEMORY.md` | Curated long-term memory distilled from daily notes. |
| `memory/` | Directory of daily note files (`YYYY-MM-DD.md`) for session continuity. |

## Where They Live

Expand All @@ -44,34 +42,30 @@ All workspace files reside inside the sandbox filesystem:
└── 2026-03-19.md
```

> **Note:** The workspace directory is hidden (`.openclaw`).
> The files are not at `/sandbox/SOUL.md`. Use the full path when downloading or uploading.

## Persistence Behavior

Understanding when these files persist and when they are lost is critical.

| Event | Workspace files |
|---|---|
| Sandbox restart | **Preserved:** the sandbox PVC retains its data. |
| `nemoclaw <name> destroy` | **Lost:** the sandbox and its PVC are deleted. |
### Survives: Sandbox Restart

Sandbox restarts (`openshell sandbox restart`) preserve workspace files.
The sandbox uses a **Persistent Volume Claim (PVC)** that outlives individual container restarts.

### Lost: Sandbox Destroy

Running `nemoclaw <name> destroy` **deletes the sandbox and its PVC**.
All workspace files are permanently lost unless you back them up first.
Comment on lines +56 to +57

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.

⚠️ Potential issue | 🟡 Minor

Make destroy/PVC wording slightly less absolute.

Line 56 currently asserts direct PVC deletion. The implementation path uses sandbox delete, with storage cleanup as behavior of that operation; consider wording this as associated workspace storage removal for long-term accuracy.

✍️ Suggested wording
-Running `nemoclaw <name> destroy` **deletes the sandbox and its PVC**.
+Running `nemoclaw <name> destroy` **deletes the sandbox and its associated workspace storage**.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Running `nemoclaw <name> destroy` **deletes the sandbox and its PVC**.
All workspace files are permanently lost unless you back them up first.
Running `nemoclaw <name> destroy` **deletes the sandbox and its associated workspace storage**.
All workspace files are permanently lost unless you back them up first.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/nemoclaw-user-workspace/SKILL.md around lines 56 - 57, Update
the warning text for the "nemoclaw <name> destroy" command to be less absolute
about PVC deletion: replace the phrase that says it "deletes the sandbox and its
PVC" with wording that reflects the implementation path ("deletes the sandbox")
and that sandbox deletion may remove associated workspace storage (e.g., PVCs)
and that files can be permanently lost unless backed up; reference the command
name "nemoclaw <name> destroy" and the concept "sandbox delete" when making the
change.


> **Warning:** Always back up your workspace files before running `nemoclaw <name> destroy`.
> See Back Up and Restore (see the `nemoclaw-user-workspace` skill) for instructions.
> See Backup and Restore (see the `nemoclaw-user-workspace` skill) for instructions.

## Editing Workspace Files

The agent reads these files at the start of every session.
You can edit them in two ways:

1. **Let the agent do it:** Ask your agent to update its persona, memory, or user context during a session.
2. **Edit manually:** Use `openshell sandbox connect` to open a terminal inside the sandbox and edit files directly, or use `openshell sandbox upload` to push edited files from your host.

## Prerequisites

- A running NemoClaw sandbox (for backup) or a freshly created sandbox (for restore).
- The OpenShell CLI on your `PATH`.
- The sandbox name (shown by `nemoclaw list`).
1. **Let the agent do it** — Ask your agent to update its persona, memory, or user context.
2. **Edit manually** — Use `openshell sandbox shell` to open a terminal inside the sandbox and edit files directly, or use `openshell sandbox upload` to push edited files from your host.

Workspace files define your agent's personality, memory, and user context.
They persist across sandbox restarts but are **permanently deleted** when you run `nemoclaw <name> destroy`.
Expand All @@ -80,9 +74,9 @@ This guide covers manual backup with CLI commands and an automated script.

## Step 1: When to Back Up

- Before running `nemoclaw <name> destroy`.
- Before major NemoClaw version upgrades.
- Periodically, if you have invested time customizing your agent.
- **Before running `nemoclaw <name> destroy`**
- Before major NemoClaw version upgrades
- Periodically, if you've invested time customizing your agent

## Step 2: Manual Backup

Expand Down Expand Up @@ -148,7 +142,7 @@ $ ./scripts/backup-workspace.sh restore my-assistant 20260320-120000
List backed-up files to confirm completeness:

```console
$ ls ~/.nemoclaw/backups/20260320-120000/
$ ls -la ~/.nemoclaw/backups/20260320-120000/
AGENTS.md
IDENTITY.md
MEMORY.md
Expand All @@ -157,16 +151,6 @@ USER.md
memory/
```

## Step 6: Inspecting Files Inside the Sandbox

Connect to the sandbox to list or view workspace files directly:

```console
$ openshell sandbox connect my-assistant
$ ls -la /sandbox/.openclaw/workspace/
```

## Related Skills

- `nemoclaw-user-reference` — Commands reference
- `nemoclaw-user-monitor-sandbox` — Monitor Sandbox Activity
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Workspace Files

OpenClaw stores agent identity, behavior, and memory in a set of Markdown files inside the sandbox.
These files live at `/sandbox/.openclaw/workspace/` and are read by the agent at the start of every session.
OpenClaw stores its personality, user context, and behavioral configuration in a set of Markdown files inside the sandbox.
These files live at `/sandbox/.openclaw/workspace/` and are collectively called **workspace files**.

## File Reference

Each file controls a distinct aspect of the agent's behavior and memory.

| File | Purpose | Upstream Docs |
|---|---|---|
| `SOUL.md` | Core personality, tone, and behavioral rules. | [SOUL template](https://docs.openclaw.ai/reference/templates/SOUL) |
| `USER.md` | Preferences, context, and facts the agent learns about you. | [USER template](https://docs.openclaw.ai/reference/templates/USER) |
| `IDENTITY.md` | Agent name, creature type, emoji, and self-presentation. | [IDENTITY template](https://docs.openclaw.ai/reference/templates/IDENTITY) |
| `AGENTS.md` | Multi-agent coordination, memory conventions, and safety guidelines. | [AGENTS template](https://docs.openclaw.ai/reference/templates/AGENTS) |
| `MEMORY.md` | Curated long-term memory distilled from daily notes. | — |
| `memory/` | Directory of daily note files (`YYYY-MM-DD.md`) for session continuity. | — |
| File | Purpose |
|---|---|
| `SOUL.md` | Defines the agent's persona, tone, and communication style. |
| `USER.md` | Stores information about the human the agent assists. |
| `IDENTITY.md` | Short identity card — name, language, emoji, creature type. |
| `AGENTS.md` | Behavioral rules, memory conventions, safety guidelines, and session workflow. |
| `MEMORY.md` | Curated long-term memory distilled from daily notes. |
| `memory/` | Directory of daily note files (`YYYY-MM-DD.md`) for session continuity. |

## Where They Live

Expand All @@ -34,30 +32,32 @@ All workspace files reside inside the sandbox filesystem:
└── 2026-03-19.md
```

> **Note:** The workspace directory is hidden (`.openclaw`).
> The files are not at `/sandbox/SOUL.md`. Use the full path when downloading or uploading.

## Persistence Behavior

Understanding when these files persist and when they are lost is critical.

| Event | Workspace files |
|---|---|
| Sandbox restart | **Preserved:** the sandbox PVC retains its data. |
| `nemoclaw <name> destroy` | **Lost:** the sandbox and its PVC are deleted. |
### Survives: Sandbox Restart

Sandbox restarts (`openshell sandbox restart`) preserve workspace files.
The sandbox uses a **Persistent Volume Claim (PVC)** that outlives individual container restarts.

### Lost: Sandbox Destroy

Running `nemoclaw <name> destroy` **deletes the sandbox and its PVC**.
All workspace files are permanently lost unless you back them up first.

> **Warning:** Always back up your workspace files before running `nemoclaw <name> destroy`.
> See Back Up and Restore (see the `nemoclaw-user-workspace` skill) for instructions.
> See Backup and Restore (see the `nemoclaw-user-workspace` skill) for instructions.

## Editing Workspace Files

The agent reads these files at the start of every session.
You can edit them in two ways:

1. **Let the agent do it:** Ask your agent to update its persona, memory, or user context during a session.
2. **Edit manually:** Use `openshell sandbox connect` to open a terminal inside the sandbox and edit files directly, or use `openshell sandbox upload` to push edited files from your host.
1. **Let the agent do it** — Ask your agent to update its persona, memory, or user context.
2. **Edit manually** — Use `openshell sandbox shell` to open a terminal inside the sandbox and edit files directly, or use `openshell sandbox upload` to push edited files from your host.

## Next Steps

- Back Up and Restore workspace files (see the `nemoclaw-user-workspace` skill)
- Backup and Restore workspace files (see the `nemoclaw-user-workspace` skill)
- Commands reference (see the `nemoclaw-user-reference` skill)
42 changes: 42 additions & 0 deletions nemoclaw/src/blueprint/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,48 @@ describe("runner", () => {
expect(providerCall[1]).not.toContain("--credential");
});

it("does not leak parent secrets into subprocess env", async () => {
process.env.MY_API_KEY = "secret-key-123";
process.env.GITHUB_TOKEN = "ghp_leaked";
process.env.AWS_ACCESS_KEY_ID = "AKIA_leaked";
process.env.NVIDIA_API_KEY = "nvapi-leaked";
process.env.HTTPS_PROXY = "http://proxy.corp:8080";
process.env.OPENSHELL_DEBUG = "1";
try {
await actionApply("default", minimalBlueprint());

const providerCall = mockExeca.mock.calls.find(
(c) => Array.isArray(c[1]) && c[1].includes("provider"),
);
if (!providerCall) throw new Error("provider create call not found");
const subEnv = providerCall[2].env;

// The explicitly injected credential must be present
expect(subEnv.OPENAI_API_KEY).toBe("secret-key-123");

// Secrets from the parent process must NOT be present
expect(subEnv).not.toHaveProperty("GITHUB_TOKEN");
expect(subEnv).not.toHaveProperty("AWS_ACCESS_KEY_ID");
expect(subEnv).not.toHaveProperty("NVIDIA_API_KEY");
expect(subEnv).not.toHaveProperty("MY_API_KEY");

// Allowed system vars should still be present
expect(subEnv).toHaveProperty("PATH");
expect(subEnv).toHaveProperty("HOME");

// Proxy, TLS, and openshell vars must pass through
expect(subEnv.HTTPS_PROXY).toBe("http://proxy.corp:8080");
expect(subEnv.OPENSHELL_DEBUG).toBe("1");
} finally {
delete process.env.MY_API_KEY;
delete process.env.GITHUB_TOKEN;
delete process.env.AWS_ACCESS_KEY_ID;
delete process.env.NVIDIA_API_KEY;
delete process.env.HTTPS_PROXY;
delete process.env.OPENSHELL_DEBUG;
}
});

it("falls back to credential_default when env var is unset", async () => {
const bp = {
components: {
Expand Down
3 changes: 2 additions & 1 deletion nemoclaw/src/blueprint/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { execa } from "execa";
import YAML from "yaml";

import { validateEndpointUrl } from "./ssrf.js";
import { buildSubprocessEnv } from "../lib/subprocess-env.js";
import { DASHBOARD_PORT } from "../lib/ports.js";

type Action = "plan" | "apply" | "status" | "rollback";
Expand Down Expand Up @@ -294,7 +295,7 @@ export async function actionApply(
reject: false,
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, ...credEnv },
env: buildSubprocessEnv(credEnv),
});

progress(70, "Setting inference route");
Expand Down
55 changes: 55 additions & 0 deletions nemoclaw/src/lib/subprocess-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

/**
* Subprocess environment allowlist.
*
* Subprocesses spawned by the CLI or plugin must NOT inherit the full
* parent process.env — that leaks secrets (NVIDIA_API_KEY, GITHUB_TOKEN,
* AWS_ACCESS_KEY_ID, etc.) to child processes where they can be read and
* exfiltrated. Instead, only forward the categories below.
*
* Credentials needed by a subprocess are injected explicitly via the
* `extra` parameter.
*
* See: #1874
*
* NOTE: src/lib/subprocess-env.ts is a mirror of this file for the CLI
* project. Keep them in sync.
*/

// ── Allowed individual names ───────────────────────────────────

const SYSTEM = ["HOME", "USER", "LOGNAME", "SHELL", "PATH", "TERM", "HOSTNAME", "NODE_ENV"];

const TEMP = ["TMPDIR", "TMP", "TEMP"];

const LOCALE = ["LANG"]; // LC_* handled via prefix

const PROXY = ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"];

const TLS = ["SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"];

const TOOLCHAIN = ["DOCKER_HOST", "KUBECONFIG", "SSH_AUTH_SOCK", "RUST_LOG", "RUST_BACKTRACE"];

const ALLOWED_ENV_NAMES = new Set([...SYSTEM, ...TEMP, ...LOCALE, ...PROXY, ...TLS, ...TOOLCHAIN]);

// ── Allowed prefixes ───────────────────────────────────────────

const ALLOWED_ENV_PREFIXES = ["LC_", "XDG_", "OPENSHELL_", "GRPC_"];

// ── Public API ─────────────────────────────────────────────────

export function buildSubprocessEnv(extra?: Record<string, string>): Record<string, string> {
const env: Record<string, string> = {};
for (const [key, value] of Object.entries(process.env)) {
if (value === undefined) continue;
if (ALLOWED_ENV_NAMES.has(key) || ALLOWED_ENV_PREFIXES.some((p) => key.startsWith(p))) {
env[key] = value;
}
}
if (extra) {
Object.assign(env, extra);
}
return env;
}
5 changes: 5 additions & 0 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2679,6 +2679,11 @@ async function createSandbox(
// proxy rewrites Authorization headers with real secrets at egress.
// See: crates/openshell-sandbox/src/secrets.rs (placeholder rewriting),
// crates/openshell-router/src/backend.rs (inference auth injection).
//
// TODO: migrate this blocklist to the shared allowlist in
// src/lib/subprocess-env.ts once the sandbox create path has been validated
// end-to-end with the stricter filtering. The allowlist rejects unknown
// env vars by default, which is safer but needs careful rollout.
const envArgs = [formatEnvAssignment("CHAT_UI_URL", chatUiUrl)];
const blockedSandboxEnvNames = new Set([
// Derived from REMOTE_PROVIDER_CONFIG to prevent drift
Expand Down
3 changes: 2 additions & 1 deletion src/lib/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { join } from "node:path";

import { DASHBOARD_PORT } from "./ports";
import { buildSubprocessEnv } from "./subprocess-env";

// ---------------------------------------------------------------------------
// Types
Expand Down Expand Up @@ -126,7 +127,7 @@ function startService(
const subprocess = spawn(command, args, {
detached: true,
stdio: ["ignore", logFd, logFd],
env: { ...process.env, ...env },
env: buildSubprocessEnv(env),
});
closeSync(logFd);

Expand Down
Loading
Loading