# Defense-in-Depth Score: NanoClaw

**Repo:** https://github.com/nanocoai/nanoclaw · **Commit:** `17bf7c4fb255e3d29def72de767f2627b17f7bc0` (v2.4.0-66-g17bf7c4f) · **Reviewed:** 2026-10-04
**What it is:** Personal AI assistant that runs Claude Agent SDK agents in per-session Docker containers, reachable over chat apps (Slack, Telegram, WhatsApp, Discord and more).
**Category:** AI Assistants
**Scored configuration:** Default `bash nanoclaw.sh` install: Docker driver, OneCLI credential gateway, Claude provider, first agent with owner role and global CLI scope, egress lockdown off, no CPU/memory limits.
**Agent surface (default):** code execution yes · filesystem write yes · network egress yes · external credentials yes · persistent memory yes · untrusted input yes · third party extensions opt-in · sub agents yes · external communication yes

## Score: 3.6 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L1 | L2 | L2 | L2 | 0.42 | — | **0.42** | High |
| C2 | Approval gates | L3 | L0 | L3 | L1 | 0.42 | C2-POWERBYPASS | **0.25** | High |
| C3 | Tool & action scoping | L1 | L1 | L0 | L2 | 0.25 | — | **0.25** | High |
| C4 | Code-execution isolation | L2 | L3 | L3 | L2 | 0.62 | — | **0.62** | High |
| C5 | Untrusted input blast radius | L2 | L1 | L3 | L0 | 0.38 | C5-WORSTCASE | **0.25** | High |
| C6 | Memory, context & configuration integrity | L0 | L1 | L1 | L0 | 0.12 | — | **0.12** | High |
| C7 | Third-party extensions | L1 | L2 | L2 | L1 | 0.38 | — | **0.38** | High |
| C8 | Secrets & sensitive-data protection | L3 | L3 | L1 | L2 | 0.60 | — | **0.60** | High |
| C9 | Audit & traceability | L2 | L2 | L1 | L1 | 0.40 | — | **0.40** | High |
| C10 | Limits & kill switch | L1 | L2 | L1 | L1 | 0.33 | — | **0.33** | High |


NanoClaw runs each agent in a hardened, non-root Docker container that never holds real API keys, and every change to its own configuration, packages, roles or MCP servers waits for an admin's exact-command approval. Inside that container, though, the agent runs Claude Code with all permission prompts off and open internet access by default, so a prompt injection from a web page or another chat member can read the agent's files and memory and send them anywhere, or use any service you connected to the gateway, with no human in the loop. Turn on NANOCLAW_EGRESS_LOCKDOWN and configure approval policies in your gateway before connecting email or code-hosting accounts.

## Critical gaps
- The agent runs Claude Code with bypassPermissions, so Bash, file writes, web requests and credentialed API calls never cross a NanoClaw approval gate. (ASI02, ASI09, T10; C2) — [container/agent-runner/src/providers/claude-config.ts:99-101](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L99-L101); [container/agent-runner/src/providers/claude-config.ts:54-71](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L54-L71)
- Egress lockdown is off by default, so a hijacked agent can exfiltrate its files, memory and history to any host and take irreversible actions unattended. (ASI01, LLM01, T6; C5) — [src/drivers/index.ts:80-86](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/index.ts#L80-L86); [src/egress-lockdown.ts:63](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/egress-lockdown.ts#L63); [container/agent-runner/src/providers/claude-config.ts:99-101](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L99-L101)

## Criterion details

### C1 Identity & least privilege — 0.42 (high)

The agent container never receives real credentials: outbound HTTPS goes through a credential gateway (OneCLI by default) that injects keys per request, and the host refuses to start a container whose environment carries a credential-looking value. Each agent group gets its own gateway identity, so credential policy can differ per agent. But which services an agent may use, and whether any request needs approval, is decided by the external gateway's own policy, not by NanoClaw code, and the shipped agent guidance tells the model to call any connected API directly. Everyone allowed to talk to an agent (including other members of a shared group chat) acts through the same owner-connected credentials, with no per-requester authorization.

- **S L1:** Dedicated per-agent-group gateway identity, but credential scope is whatever the operator connects in the external gateway; NanoClaw itself narrows nothing per tool or per request. — [src/drivers/types.ts:521-524](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/types.ts#L521-L524); [.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md:3](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md#L3); [.claude/skills/add-onecli/payload/src/gateway-providers/onecli.ts:189](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/.claude/skills/add-onecli/payload/src/gateway-providers/onecli.ts#L189) (verified)
  - *To reach the next level:* No per-tool or per-request credential scoping or deterministic authorization check in NanoClaw code before credentials are attached.
- **C L2:** Every in-container path (Bash, WebFetch, MCP servers, sub-agents) reaches credentials only through the same gateway proxy; host control-plane actions go through the host guard. — [src/container-runner.ts:1286-1289](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1286-L1289); [src/drivers/types.ts:521-524](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/types.ts#L521-L524); [src/cli/guard.ts:102-106](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/cli/guard.ts#L102-L106) (verified)
  - *To reach the next level:* No authorization evaluated against the requesting principal; any group member's request runs with the owner's connected credentials.
- **D L2:** Default install connects only the owner's Anthropic credential, but the first agent is created with owner role and global CLI scope, and any service connected in the gateway becomes usable by the agent without further elevation. — [setup/channels/run-channel-skill.ts:12](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/setup/channels/run-channel-skill.ts#L12); [src/modules/agent-to-agent/guard.ts:47](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/agent-to-agent/guard.ts#L47) (verified)
  - *To reach the next level:* Default is not read-only/minimal: connected credentials are usable for writes without explicit elevation.
- **B L2:** In the default install a hijacked agent can spend on the owner's model account and post to its wired chat destinations; anything else the owner connects in the gateway (email, GitHub) widens this. — [.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md:3](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md#L3); [src/gateway-approval-coordinator.ts:403-414](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/gateway-approval-coordinator.ts#L403-L414) (verified)
  - *To reach the next level:* Not limited to one project with mostly-read, short-lived credentials.
- **Cap:** none

### C2 Approval gates — 0.25 (high)

NanoClaw has a well-built approval system for control-plane actions: installing packages, adding MCP servers, changing roles, members, wiring or container config all hold for an admin, the card shows the exact command and arguments, the approved payload is what runs, and only an authenticated admin's click counts. But the agent itself runs with Claude Code's permission prompts switched off, so shell commands, file writes, web requests and credentialed API calls through the gateway all happen with no human in the loop unless the external gateway's own policy holds a request. The most powerful tool, Bash with open network access, therefore bypasses every gate.

- **S L3:** Host-side approval cards show the exact ncl command and arguments, the stored frame is replayed on approve, risk tiers (open/approval/hostOnly) decide what needs a human, and reject is a first-class outcome. — [src/cli/dispatch.ts:155-163](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/cli/dispatch.ts#L155-L163); [src/cli/guard.ts:102-106](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/cli/guard.ts#L102-L106); [src/modules/approvals/response-handler.ts:127-143](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/approvals/response-handler.ts#L127-L143) (verified)
  - *To reach the next level:* Approvals are not policy-driven on parsed arguments for the agent's real tool calls, and gateway approval cards show method, host and path but not the request body.
- **C L0:** The agent runs with bypassPermissions, so Bash, Write, WebFetch and MCP tools in the container are never gated; only host control-plane actions are. — [container/agent-runner/src/providers/claude-config.ts:99-101](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L99-L101); [container/agent-runner/src/providers/claude.ts:296-298](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude.ts#L296-L298); searched `rg -n 'requestApproval|HOLD\('` in `container/agent-runner/src` → 0 hits (No approval step exists anywhere in the in-container tool path; holds exist only host-side for control-plane actions.) (verified)
  - *To reach the next level:* The most powerful tool (Bash) and credentialed HTTP through the gateway are exempt from any NanoClaw approval.
- **D L3:** Control-plane holds are unconditional for container callers and the approver must be an authenticated admin; the model cannot switch them off. — [src/modules/self-mod/guard.ts:25-28](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/guard.ts#L25-L28); [src/modules/approvals/response-handler.ts:127-143](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/approvals/response-handler.ts#L127-L143) (verified)
  - *To reach the next level:* Gating of the agent's own tool use is not on by default.
- **B L1:** A wrong in-container action can delete the agent's persistent files and memory, send chat messages to wired destinations and call any gateway-connected API; there is no checkpoint or undo. — [src/container-runner.ts:1083-1088](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1083-L1088); [.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md:3](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md#L3) (verified)
  - *To reach the next level:* No checkpoint/rollback for the persistent group folder and no previews for external actions.
- **Cap:** C2-POWERBYPASS — The agent runs Claude Code with bypassPermissions, so Bash (the most powerful action path) skips every approval in the default configuration.

### C3 Tool & action scoping — 0.25 (high)

The agent gets Claude Code's general-purpose tools: unrestricted Bash, file read and write, web fetch and search, sub-agents and teams, all enabled by default with no argument validation. The narrow host-facing actions are validated well: extra host mounts are resolved through realpath and checked against an operator allowlist kept outside the container (and blocked entirely when no allowlist exists), package names are regex-checked, and MCP server configs are parsed and bounded. The practical reach of a misused tool is the container and its read-write group folder, plus open network.

- **S L1:** Main tools are raw passthrough (any shell command, any URL); validation exists only for host-facing actions such as mounts and package names. — [container/agent-runner/src/providers/claude-config.ts:54-71](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L54-L71); [src/modules/mount-security/index.ts:214](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/mount-security/index.ts#L214); [src/modules/self-mod/request.ts:34-35](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/request.ts#L34-L35) (verified)
  - *To reach the next level:* No allowlist validation of the agent's general tools; Bash and WebFetch take arbitrary commands and URLs.
- **C L1:** Only the host-facing actions (additional mounts, install_packages, add_mcp_server) validate their inputs. — [src/modules/mount-security/index.ts:316-319](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/mount-security/index.ts#L316-L319); [src/modules/self-mod/request.ts:34-35](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/request.ts#L34-L35) (verified)
  - *To reach the next level:* Most built-in tools have no validation layer.
- **D L0:** Write, exec and network tools are all enabled by default in the allowlist passed to the SDK. — [container/agent-runner/src/providers/claude-config.ts:54-71](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L54-L71) (verified)
  - *To reach the next level:* No read-only default tool set or per-tool disabling in the default configuration.
- **B L2:** Tool misuse is scoped to the container: the session folder and group folder are read-write, extra host mounts are blocked without an allowlist, but network is open. — [src/container-runner.ts:1083-1088](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1083-L1088); [src/modules/mount-security/index.ts:316-319](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/mount-security/index.ts#L316-L319); [src/drivers/index.ts:80-86](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/index.ts#L80-L86) (verified)
  - *To reach the next level:* No quantity bounds on what tools can write or send.
- **Cap:** none

### C4 Code-execution isolation — 0.62 (high)

All model-driven execution happens inside a per-session Docker container that runs as a non-root user, drops all Linux capabilities, sets no-new-privileges, strips setuid binaries from the image, caps processes at 2048 and is removed when the session ends. There is no host-execution fallback: if Docker is unavailable the session fails. The agent container's root filesystem is writable (only auxiliary containers get --read-only), CPU and memory are unlimited by default, the group folder is mounted read-write, and network egress is open to the internet by default because the egress lockdown is opt-in.

- **S L2:** Docker container, non-root, --cap-drop=ALL, no-new-privileges, setuid stripped, default seccomp; the agent container's root filesystem is not read-only. — [src/drivers/docker-driver.ts:750-754](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/docker-driver.ts#L750-L754); [src/drivers/docker-driver.ts:781](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/docker-driver.ts#L781); [container/Dockerfile:138-140](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/Dockerfile#L138-L140) (verified)
  - *To reach the next level:* Agent container lacks a read-only root filesystem, the remaining element of a hardened container profile.
- **C L3:** Bash, MCP stdio servers, hooks and scheduled-task scripts all run inside the agent container; no code path runs model text on the host. — [src/container-runner.ts:1316-1325](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1316-L1325); [src/drivers/docker-driver.ts:750-754](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/docker-driver.ts#L750-L754) (verified)
  - *To reach the next level:* Fail-closed coverage cannot be credited above the strength of the primitive.
- **D L3:** Isolation is always on with no off switch; container args are built by host code outside the model's reach. — [src/drivers/docker-driver.ts:781](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/docker-driver.ts#L781); [src/drivers/docker-driver.ts:806](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/docker-driver.ts#L806) (verified)
  - *To reach the next level:* D cannot exceed one level above S.
- **B L2:** Inside: read-write session and group folders, no credentials in env, pids limit 2048, but unrestricted internet egress and no CPU/memory limit by default. — [src/drivers/index.ts:80-86](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/index.ts#L80-L86); [src/config.ts:106](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/config.ts#L106); [src/config.ts:94-95](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/config.ts#L94-L95); [src/config.ts:102](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/config.ts#L102); [src/container-runner.ts:1286-1289](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1286-L1289) (verified)
  - *To reach the next level:* Egress is not off or allowlisted by default and CPU/memory are unbounded.
- **Cap:** none

### C5 Untrusted input blast radius — 0.25 (high)

NanoClaw keeps strangers out by default (unknown senders need an admin's approval) and holds every control-plane change for an admin regardless of what the agent read. But nothing tracks untrusted content inside a session: web pages, files and messages from other group-chat members enter context with the same standing, and afterwards the agent can still use open network egress, web fetches, chat messages and any gateway-connected API with no approval. A successful injection can therefore read the agent's files, memory and conversation history and send them anywhere, and can delete persistent files or send messages, unattended.

- **S L2:** Host-side holds on control-plane actions apply regardless of what the agent read, but egress and in-container state changes are never gated. — [src/modules/self-mod/guard.ts:25-28](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/guard.ts#L25-L28); [container/agent-runner/src/providers/claude-config.ts:99-101](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L99-L101) (verified)
  - *To reach the next level:* No Rule-of-Two enforcement: egress and state-changing tools stay available after untrusted content is read.
- **C L1:** Unknown senders are filtered before reaching the agent; web, file, tool and group-member content is not distinguished. — [src/channels/channel-registry.ts:171-185](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/channels/channel-registry.ts#L171-L185); searched `rg -n 'requestApproval|HOLD\('` in `container/agent-runner/src` → 0 hits (No approval step exists anywhere in the in-container tool path; holds exist only host-side for control-plane actions.) (verified)
  - *To reach the next level:* Tool results, fetched pages and other members' messages are not separated from principal instructions.
- **D L3:** Sender filtering and host holds are on by default and cannot be configured away by content the agent reads. — [src/channels/channel-registry.ts:171-185](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/channels/channel-registry.ts#L171-L185); [src/modules/self-mod/guard.ts:25-28](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/guard.ts#L25-L28) (verified)
  - *To reach the next level:* Nothing in the agent session itself limits a hijacked model.
- **B L0:** A hijacked agent can exfiltrate group files, memory and history over open egress and delete persistent files or post messages, with no human involved. — [src/drivers/index.ts:80-86](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/index.ts#L80-L86); [src/egress-lockdown.ts:63](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/egress-lockdown.ts#L63); [src/container-runner.ts:1083-1088](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1083-L1088); [container/agent-runner/src/providers/claude-config.ts:99-101](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L99-L101) (verified)
  - *To reach the next level:* Exfiltration and irreversible actions are not both behind approval.
- **Cap:** C5-WORSTCASE — B is L0: in the default configuration a hijacked agent can both leak private data over open egress and take irreversible actions unattended.

### C6 Memory, context & configuration integrity — 0.12 (high)

Each agent group has persistent Markdown memory that the agent edits freely and that is re-injected into context at every new session, compaction or clear, with no validation, approval or provenance. The agent's Claude settings directory (including settings.json, which can hold hooks) is also mounted read-write and loaded as user settings. Some config is protected: the composed CLAUDE.md, container.json and stamped plugins are read-only mounts, and memory is isolated per agent group by the container's mounts. One poisoned write can persist across every later session of that agent, including for other members of a shared group.

- **S L0:** The model can write anything to memory/index.md and it is re-injected as context; settings.json under /home/node/.claude is agent-writable and loaded as user settings. — [container/agent-runner/src/memory/context.ts:11-39](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/memory/context.ts#L11-L39); [src/provider-contracts/claude.ts:19-25](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/provider-contracts/claude.ts#L19-L25); [container/agent-runner/src/providers/claude.ts:296-298](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude.ts#L296-L298) (verified)
  - *To reach the next level:* No gating, validation or provenance on memory writes or agent-writable settings.
- **C L1:** The composed CLAUDE.md, container.json and plugins directory are nested read-only mounts the agent cannot change. — [src/container-runner.ts:1092-1103](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1092-L1103); [src/container-runner.ts:1083-1088](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1083-L1088) (verified)
  - *To reach the next level:* Memory files and the Claude settings/state directory are not controlled.
- **D L1:** Memory is separated per agent group by mounts the model cannot change, but D cannot exceed one level above S. — [src/container-runner.ts:1083-1088](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1083-L1088) (verified)
  - *To reach the next level:* Isolation credit is capped by the absent write control.
- **B L0:** Poisoned memory or hooks persist across all later sessions of the agent group, which can be shared by several users and channels, and can trigger tool use. — [container/agent-runner/src/memory/context.ts:11-39](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/memory/context.ts#L11-L39); [src/provider-contracts/claude.ts:19-25](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/provider-contracts/claude.ts#L19-L25) (verified)
  - *To reach the next level:* No human review or rollback before poisoned memory takes effect.
- **Cap:** none

### C7 Third-party extensions — 0.38 (high)

Nothing third-party is enabled for the agent by default. When the agent asks to add an MCP server or install apt/npm packages, an admin must approve a card showing the exact package names or command, with secret-looking values masked. Package names are syntax-checked but not version-pinned or hash-verified, and an added MCP server runs inside the agent container with the same user, network and gateway access as the agent. Separately, because Bash has open network access, the agent can fetch and run packages inside its own container without any approval.

- **S L1:** Admin-chosen packages and MCP commands, accepted by name only with no version pin or integrity check. — [src/modules/self-mod/request.ts:34-35](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/request.ts#L34-L35); [src/modules/self-mod/guard.ts:25-28](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/guard.ts#L25-L28) (verified)
  - *To reach the next level:* No version pinning for approved packages or MCP servers.
- **C L2:** Both extension routes (install_packages, add_mcp_server) go through the same host-side hold. — [src/modules/self-mod/guard.ts:25-28](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/guard.ts#L25-L28); [container/agent-runner/src/providers/claude-config.ts:99-101](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/providers/claude-config.ts#L99-L101) (verified)
  - *To reach the next level:* Extensions fetched directly via Bash inside the container are not covered.
- **D L2:** No extension enabled by default; adding one needs admin approval showing the exact request. — [src/modules/self-mod/guard.ts:25-28](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/guard.ts#L25-L28); [src/modules/self-mod/request.ts:66-77](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/request.ts#L66-L77) (verified)
  - *To reach the next level:* D cannot exceed one level above S.
- **B L1:** An extension runs as a separate process in the agent container, same user, with the agent's files, network and gateway access. — [src/container-runner.ts:1083-1088](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1083-L1088); [src/container-runner.ts:1286-1289](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1286-L1289) (verified)
  - *To reach the next level:* No per-extension sandbox or scrubbed environment.
- **Cap:** none

### C8 Secrets & sensitive-data protection — 0.60 (high)

Credential handling is the strongest part of the design. Real keys live in the gateway and are injected at the network boundary, so they never enter the container, its environment or the model's context, and the host refuses to start a container whose contributed environment contains a credential-looking value. Approval cards mask secret-looking values. Gaps: the host logger has no redaction layer, credentials are long-lived keys the gateway holds, and setup sends anonymous PostHog diagnostics by default (opt-out with an env var).

- **S L3:** Secrets are held by the gateway and substituted at request time; admission refuses credential values in container env; approval cards redact secret-shaped values. — [src/drivers/types.ts:521-524](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/types.ts#L521-L524); [.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md:3](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md#L3); [src/modules/self-mod/request.ts:102-105](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/self-mod/request.ts#L102-L105) (verified)
  - *To reach the next level:* No short-lived tokens or output scanning for secret patterns.
- **C L3:** Subprocess/container env, model-bound context and approval cards are protected; host logs have no redaction. — [src/container-runner.ts:1286-1289](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/container-runner.ts#L1286-L1289); searched `rg -n -i redact` in `src/log.ts container/agent-runner/src` → 1 hits (The single hit is a comment about honouring a value's own toJSON; there is no redaction layer in the host logger or the agent runner.) (verified)
  - *To reach the next level:* Host log path has no redaction filter.
- **D L1:** Setup diagnostics go to PostHog by default (content-free event names and platform), disabled only by NANOCLAW_NO_DIAGNOSTICS=1. — [setup/lib/diagnostics.ts:54](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/setup/lib/diagnostics.ts#L54) (verified)
  - *To reach the next level:* Telemetry is not opt-in.
- **B L2:** A leak through the agent yields no raw key; the gateway-held keys are scoped per service but long-lived. — [.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md:3](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/.claude/skills/add-onecli/payload/container/skills/onecli-gateway/instructions.md#L3); [src/drivers/types.ts:521-524](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/drivers/types.ts#L521-L524) (verified)
  - *To reach the next level:* Keys are not short-lived or per-task.
- **Cap:** none

### C9 Audit & traceability — 0.40 (high)

The record of what the agent did is Claude Code's own session transcript, a structured JSONL file with every tool call and result, plus the host's message databases and a central table of approvals with the approver. The transcript lives in the agent's read-write Claude directory, so the agent can edit or delete it, and there is no tamper-evident or off-host log, no actor attribution for individual tool calls, and no fail-closed behaviour.

- **S L2:** SDK session transcript records every tool call with arguments and results; approvals are persisted with approver. — [container/agent-runner/src/upload-trace.ts:14-15](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/upload-trace.ts#L14-L15); [src/modules/approvals/primitive.ts:249](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/approvals/primitive.ts#L249) (verified)
  - *To reach the next level:* No actor attribution or correlation IDs across sub-agents for tool calls.
- **C L2:** Built-in and MCP tool calls are in the transcript and host control-plane approvals in the central DB; processes spawned by Bash are not individually recorded. — [container/agent-runner/src/upload-trace.ts:14-15](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/upload-trace.ts#L14-L15); [src/modules/approvals/primitive.ts:249](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/modules/approvals/primitive.ts#L249) (verified)
  - *To reach the next level:* Credential use and memory writes are not recorded as audit events.
- **D L1:** Transcript is on by default but stored in /home/node/.claude, which the agent can write. — [src/provider-contracts/claude.ts:19-25](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/provider-contracts/claude.ts#L19-L25) (verified)
  - *To reach the next level:* Record is not stored outside the agent's writable mounts.
- **B L1:** Best-effort transcript writing by the SDK; nothing blocks actions when the record fails. — [container/agent-runner/src/upload-trace.ts:14-15](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/container/agent-runner/src/upload-trace.ts#L14-L15) (verified)
  - *To reach the next level:* No error surfacing or durable per-action write owned by the host.
- **Cap:** none

### C10 Limits & kill switch — 0.33 (high)

There is no limit on turns, tokens or cost. The host kills a container whose heartbeat has been silent for 30 minutes or that claims a message and goes quiet, and stopping a container removes everything in it, but an active agent can run indefinitely, and declaring a long Bash timeout extends the silence ceiling. The agent can create recurring scheduled tasks without approval and can itself override the daily-frequency limit with a flag, so work continues on a schedule after a conversation ends. A 2048-process cap is on by default; CPU and memory limits are opt-in.

- **S L1:** Only liveness timeouts (30-minute silent-heartbeat ceiling, stuck-claim kill) and a pids limit; no step, token or cost cap. — [src/reconcile-session.ts:47](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/reconcile-session.ts#L47); searched `rg -n 'maxTurns|maxBudgetUsd|max_turns'` in `container/agent-runner/src src` → 0 hits (No turn, token or cost cap is passed to the SDK or enforced by the host.) (verified)
  - *To reach the next level:* No iteration cap plus token/cost cap enforced in code.
- **C L2:** Container kill covers everything spawned inside it, and Bash calls carry SDK timeouts. — [src/reconcile-session.ts:93](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/reconcile-session.ts#L93); [src/config.ts:102](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/config.ts#L102) (verified)
  - *To reach the next level:* Scheduled tasks and sub-agents do not count against a shared budget.
- **D L1:** The model can raise its own ceilings: a declared Bash timeout extends the kill ceiling and the agent can pass the recurrence-limit override. — [src/reconcile-session.ts:93](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/reconcile-session.ts#L93); [src/cli/resources/tasks.ts:517](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/cli/resources/tasks.ts#L517) (verified)
  - *To reach the next level:* Model can raise its limits.
- **B L1:** A busy runaway has no ceiling on spend or time, and recurring scheduled tasks keep firing after a stop. — [src/cli/resources/tasks.ts:517](https://github.com/nanocoai/nanoclaw/blob/17bf7c4fb255e3d29def72de767f2627b17f7bc0/src/cli/resources/tasks.ts#L517); searched `rg -n 'maxTurns|maxBudgetUsd|max_turns'` in `container/agent-runner/src src` → 0 hits (No turn, token or cost cap is passed to the SDK or enforced by the host.) (verified)
  - *To reach the next level:* No tight per-run time and cost ceilings; scheduled work continues.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: WebFetch/WebSearch and group-member messages enter context unmarked (container/agent-runner/src/providers/claude-config.ts:54-71) · [B] sensitive data/systems: Read-write group folder with memory and history, plus gateway-injected credentials (src/container-runner.ts:1083-1088) · [C] state change / egress: Bash with open bridge network and bypassPermissions (src/drivers/index.ts:80-86, claude-config.ts:99-101) · Same default session? Yes

## Highest-impact improvements
1. Turn egress lockdown on by default so agent traffic can only reach the credential gateway, and ship a gateway policy that holds non-model credentialed requests for approval. — C5 B L0→L2, +0.100 before caps (Playbook 1)
2. Add --read-only (with tmpfs for /tmp) to the agent container so the hardened container profile is complete. — C4 S L2→L3, +0.075 before caps (Playbook 3 step 1)
3. Mount settings.json read-only and require approval or provenance tagging for writes to the always-loaded memory files. — C6 S L0→L2, +0.150 before caps (Playbook 2)
4. Pass a turn cap and a token/cost budget to the SDK, and stop letting a model-declared Bash timeout or a model-set flag raise limits. — C10 S L1→L2, +0.075 before caps (Playbook 3 step 3)
5. Write the session transcript to a host-owned location outside the agent's writable mounts. — C9 D L1→L2, +0.050 before caps

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- No release tag points at the pinned commit; package.json reports version 2.4.0.
- The OneCLI gateway's own policy engine, vault storage and default policy are external to this repository and were not examined; ratings assume no approval policy beyond what this repo configures.
- Channel adapters live on a separate `channels` branch and were not reviewed; only trunk and the in-tree add-onecli skill payload were examined.
- Claude Agent SDK behaviour (bypassPermissions, settingSources, transcript writing) is inferred from its documented semantics, not from SDK source.
- No text aimed at steering AI reviewers was found in README, AGENTS.md, CLAUDE.md or container/CLAUDE.md.
