The threat model behind NanoClaw’s design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.
NanoClaw connects an AI agent with real tools — a shell, a filesystem, network access — to chat platforms where anyone can type. Every inbound message is untrusted input fed straight into the model’s context, and no model reliably distinguishes “instructions from my operator” from “instructions an attacker pasted into the group chat.” So the design assumption is blunt: prompt injection eventually succeeds, and the agent acts as if the attacker wrote its instructions.Everything below answers the question that assumption forces: what can a fully compromised agent actually do? Each layer shrinks that answer. This page is the why; the configuration lives in Hardening and Credentials.
Container isolation — the blast radius is one agent group
A compromised agent is a compromised container, and every active session gets its own. The container runs as the unprivileged node user (or your host uid/gid), and --rm removes the container after exit; mounted group and session state persists. Every spawn also carries the same hardening flags, unconditionally and with no per-group override: --cap-drop=ALL and --security-opt no-new-privileges (depth against a root-in-container path — under the normal user mapping the capability sets are already empty), --init so signals actually reach the runner instead of being discarded at PID 1, plus a configurable --pids-limit fork-bomb backstop that defaults to 2048. The image itself strips the setuid-root bit from every binary Debian ships (mount, su, passwd, and friends), none of which an unprivileged agent needs. Its filesystem view is exactly the mount table: the session folder, the group workspace, and read-only shared code. No host home directory, no .ssh, no Docker socket. Its own container.json and composed project document are re-mounted read-only on top of the writable workspace, so the agent can read its configuration but not rewrite it to grant itself more. Stamped plugin code under plugins/ is also mounted read-only; runtime writes belong in the separate plugin-data/ tree, which remains writable through the group mount.One boundary the container does not draw: every session of an agent group mounts the same group workspace read-write, so what a compromised session reaches is the group’s files and memory/ tree, not just its own conversation’s. Session databases and provider continuations stay separate, but an agent can retrieve other sessions’ transcripts within its group through ncl sessions history. That command rejects cross-group reads. The honest unit of blast radius is therefore the agent group, which is why different trust domains belong in separate agent groups.The container is also disposable by design — the host kills it on a stale heartbeat and rebuilds the entire environment at next spawn, so a compromised process doesn’t outlive its session. See Container lifecycle.Files flow into the container too — channel attachments and files forwarded from other agents are written by the host into the session’s writable inbox/. That’s a classic symlink trap: a compromised agent could replace its inbox with a symlink and redirect the host’s write anywhere the host can reach. The shared inbox guard (src/inbox-safety.ts) closes it — before any write, the host verifies the inbox root and the per-message directory are real directories (not symlinks), resolves the final path, and refuses anything that lands outside the session folder; the writes themselves use exclusive-create flags so a pre-placed symlinked file can’t be followed either.
The single most valuable thing to exfiltrate from an agent is an API key, so the invariant is that there are none: secrets live in the OneCLI Agent Vault on the host, and the vault’s gateway injects them into outbound HTTPS requests in transit. Where a tool insists on finding a credential file locally, it gets a stub whose secret value is the placeholder onecli-managed. A compromised agent can dump its environment, its filesystem, and /proc and find nothing worth stealing — it can use the credentials its group was granted (the gateway injects them per request), but it can never extract them to use elsewhere. If the vault is unreachable, the spawn aborts rather than falling back to raw keys.Setup, per-group secret grants, and the credential admission rules are in Credentials.
Vault injection works through HTTPS_PROXY, and a proxy env var only governs clients that honor it. A compromised agent could open a raw socket — or run any non-proxy-aware tool — and ship data anywhere on the internet, bypassing credential injection, approvals, and audit. Egress lockdown closes that hole at the network layer: agent containers join a Docker --internal network with no route to the internet, where the vault gateway is the only reachable hop. The agent runs non-root without NET_ADMIN, so it can’t reconfigure its way out, and NanoClaw refuses to spawn at all if lockdown is on but can’t be established.It’s off by default because it breaks by design: any workflow that needs a non-proxy-aware tool to reach the internet directly will fail. Turn it on when agents face untrusted audiences or hold credentials worth stealing — setup in Hardening.
Mounts define the agent’s entire filesystem reach, so extending them is the most direct way to widen the blast radius. Per-group additional_mounts requests are therefore validated against an allowlist at ~/.config/nanoclaw/mount-allowlist.json that is deny-by-default — no allowlist file means no additional mounts — and lives outside the project root precisely so no container can ever reach it. Built-in blocked patterns (.ssh, .aws, .env, id_rsa, and friends) can’t be removed, host paths are resolved through symlinks before checking so the agent can’t alias its way past the rules, and everything is read-only unless both the request and the matching root opt into read-write.Rules and tooling in Hardening.
Every layer above limits what a compromised agent can do; this one limits who gets to attempt the compromise. Prompt injection needs a delivery channel, and the cheapest one is just messaging the bot. Three gates stand in the way:
Channel registration — a mention or DM in a chat NanoClaw has never seen doesn’t reach any agent. It escalates to a human (group admins, then global admins, then owners) with an approve/deny card; denied chats drop silently forever.
Sender policies — each known chat carries an unknown_sender_policy: strict silently drops strangers, request_approval asks an admin before admitting them, decline_notify politely declines unknown DM senders and sends an owner or admin a one-line FYI, and public lets anyone in. On group chats, decline_notify falls back to a silent drop. Auto-created chats use the receiving adapter’s declared DM or group policy; undeclared adapters fall back to request_approval.
Per-wiring scope — sender_scope: known requires owner, admin, or group membership even in a public chat. Crucially, messages refused here are never even accumulated as context, so a rejected sender can’t smuggle instructions into the agent’s next batch.
Configuration in Hardening; how senders, roles, and wirings fit together in the entity model.
Some actions are too consequential to leave to a model that might be acting on injected instructions, so they pause for a human. The approvals module delivers a card to an admin DM — group admins first, then global admins, then owners, and no reachable approver means auto-deny — and nothing happens until someone taps Approve:
Credential use — vault secrets can require per-request approval; the gateway holds the HTTPS request open while the card shows an admin the method, host, path, and body preview.
Self-modification — an agent asking to install packages or add an MCP server into its own container is asking to expand its own capabilities, which is exactly what an attacker would ask for. Both actions queue an approval card.
Channel registration — new chats reaching agents, as above.
Agent-to-agent messages — an operator can gate a connection between two agents so every message from one to the other is held until a human approves it, without un-wiring the connection. Set it with ncl policies set; no policy means messages flow freely.
Two refinements control who decides and what the agent learns:
Named approver — most cards fall to the first reachable admin, but an agent-to-agent message policy pins a specific approver. Only that exact user (or an owner) can resolve the card, recorded as approver_user_id on the request — so a held message can’t be cleared by just anyone with admin rights.
Reject with reason — alongside Approve and Reject, an admin can decline with a one-line note. NanoClaw parks the request, prompts the admin in their DM, and relays their reply back to the requesting agent (Your <action> request was rejected by admin: "<reason>"). If the admin doesn’t reply within ~5 minutes — or the host restarts mid-prompt — the host sweep finalizes a plain reject, so the agent is never left waiting on a decision that never comes.
The credential flow in detail, including card expiry and ncl approvals, is in Credentials.
Platform slash-commands are classified on the host before any container sees them. Commands that manipulate host-side CLI state (/login, /logout, /config, and friends) are dropped outright, and operational commands like /clear or /upload-trace require an owner or admin role — so a stranger in a public chat can’t wipe an agent’s context or exfiltrate a trace just by typing. The full classification is in Hardening.
The host process itself is in the trusted zone, so its dependencies matter: pnpm is configured with a three-day minimumReleaseAge (newly published package versions wait before they can be installed) and an onlyBuiltDependencies allowlist limiting install scripts to esbuild, protobufjs, and sharp.
Be honest about the gap: anything the agent can legitimately do, it can do while manipulated. No layer above distinguishes a sincere action from an injected one — they only bound the action space. A compromised agent can still:
Message anyone in its destinations — including posting attacker-chosen content to your team channel, or leaking conversation context to another chat it legitimately serves.
Read and destroy its whole workspace — every session of an agent group shares the group folder read-write, including its memory/ tree and instructions.prepend.md — and it can read whatever its allowlisted mounts expose (read-write only where explicitly granted).
Spend through granted credentials — it can’t extract keys, but it can make authenticated API calls with everything its group was granted (per-request approval, where enabled, is the brake).
Poison its own memory — instructions written to its memory/ tree or instructions.prepend.md today shape every future session of that group.
The defense against all four is the same and it’s yours, not the software’s: scoping. An agent that talks to strangers should not share a group with your code repo and deploy credentials; chats with different audiences should not share files or memory. The decision guide is Isolation levels.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"NanoClaw","colors":{"primary":"#2DD4BF","light":"#5EEAD4","dark":"#14B8A6"},"logo":{"light":"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/nanoclaw-logotype-dark.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=7fabf1abf0eb70d18a0235dd67255fcb","dark":"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/nanoclaw-logotype-dark.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=7fabf1abf0eb70d18a0235dd67255fcb","href":"https://nanoclaw.dev"},"favicon":"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/favicon.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=59a44855795dae3f826d473592ade7a0","appearance":{"default":"dark","strict":true},"background":{"decoration":"gradient"},"navbar":{"primary":{"type":"github","href":"https://github.com/nanocoai/nanoclaw"}},"footer":{"socials":{"github":"https://github.com/nanocoai/nanoclaw","discord":"https://discord.gg/VDdww8qS42","x":"https://x.com/Gavriel_Cohen","youtube":"https://www.youtube.com/@NanoClaw","website":"https://nanoclaw.dev"},"links":[{"header":"Legal","items":[{"label":"MIT License","href":"https://github.com/nanocoai/nanoclaw/blob/main/LICENSE"}]}]},"search":{"prompt":"Search NanoClaw docs..."},"seo":{"indexing":"navigable"},"fonts":{"heading":{"family":"Inter","weight":600},"body":{"family":"Inter","weight":400}},"styling":{"eyebrows":"breadcrumbs","latex":false},"banner":{"content":"[Join the Discord community](https://discord.gg/VDdww8qS42) — get help, share what you're building, or just say hi.","dismissible":true},"contextual":{"options":["copy","view","chatgpt","claude","cursor"],"display":"header"},"metadata":{"timestamp":true},"navigation":{"global":{"anchors":[{"anchor":"Changelog","icon":"clock-rotate-left","href":"/changelog"}]},"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"qwibitai-nanoclaw-8","hasAudioNarration":false,"actualSubdomain":"qwibitai-nanoclaw-8","customDomains":["docs.nanoclaw.dev"],"gitSource":{"type":"github","owner":"glifocat","repo":"nanoclaw-docs","deployBranch":"main","contentDirectory":"","isPrivate":false,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ad9d4e10febba82de2fda10","collectionId":"e8f44256-8726-4d08-8a35-a07089f9f5e4","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ad9d4e10febba82de2fda10"},"feedback":{"thumbs":true,"edits":true,"issues":true,"agentFeedback":true},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"CONTEXTUAL_FEEDBACK":{"status":"DISABLED"},"CODE_SNIPPET_FEEDBACK":{"status":"DISABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6ad9d4e10febba82de2fda10","clientVersion":"0.0.4076","searchFilterCounts":[{"language":"default","tag":"Documentation","count":49},{"language":"default","tag":"Channels","count":12},{"language":"default","tag":"Operate","count":9},{"language":"default","tag":"Reference","count":8},{"language":"default","tag":"Understand","count":7},{"language":"default","tag":"Templates","count":6},{"language":"default","tag":"Extend","count":5},{"language":"default","tag":"Operating","count":5},{"language":"default","tag":"Build with agents","count":4},{"language":"default","tag":"Get started","count":4}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"custom.css","content":".quickstart-walkthrough [data-component-part=\"step-title\"]{font-size:1.25rem;line-height:1.5;font-weight:650;color:#f3f4f6}.quickstart-walkthrough .step-container{padding-bottom:2rem}.quickstart-walkthrough .setup-terminal{margin:1.25rem 0;overflow:hidden;border:1px solid #33413f;border-radius:12px;background:#101715;color:#e5e7eb}.setup-terminal-label{padding:0.65rem 1rem;border-bottom:1px solid #33413f;color:#bac8c3;font-size:0.75rem;letter-spacing:0.02em}.setup-terminal-screens{padding:1rem}.setup-terminal-screen+.setup-terminal-screen{border-top:1px solid #33413f;margin-top:1rem;padding-top:1rem}.setup-terminal-order{color:#bac8c3;font-size:0.75rem;margin-bottom:0.6rem}.setup-terminal-question,.setup-terminal-options{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size:0.875rem;line-height:1.65;overflow-wrap:anywhere}.setup-terminal-question{display:flex;gap:0.7rem;color:#f3f4f6}.setup-terminal-question>span:first-child{color:#5eead4;flex-shrink:0}.setup-terminal-options{list-style:none;padding:0;margin:0.8rem 0 0}.setup-terminal-options li{display:flex;gap:0.7rem;padding:0.5rem 0.65rem;border-radius:5px}.setup-terminal-options li>span:first-child{flex-shrink:0}.setup-terminal-options .setup-terminal-selected{background:#173b32;color:#d1fae5}.setup-terminal figcaption{padding:0.75rem 1rem;border-top:1px solid #33413f;font-size:0.75rem;line-height:1.5;color:#bac8c3}.quickstart-walkthrough .setup-check{display:flex;gap:0.8rem;margin:1.5rem 0;padding:0.8rem 1rem;border-left:3px solid #5eead4;background:#10251f;color:#e5e7eb;font-size:0.9375rem;line-height:1.65}.setup-check-icon{color:#5eead4}.setup-check-label{font-size:0.75rem;font-weight:600;color:#99f6e4;margin-bottom:0.2rem}"}],"page":{"pagePath":"concepts/security","slug":"/concepts/security","pageMetadata":{"title":"Security model","description":"The threat model behind NanoClaw's design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.","keywords":["security","threat model","prompt injection","container isolation","egress lockdown","mount allowlist","approvals","credentials","defense in depth"],"href":"/concepts/security"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"The threat model behind NanoClaw’s design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,p:`p`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{Heading,OptimizedImage}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!OptimizedImage)_missingMdxReference(`OptimizedImage`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`NanoClaw connects an AI agent with real tools — a shell, a filesystem, network access — to chat platforms where anyone can type. Every inbound message is untrusted input fed straight into the model’s context, and no model reliably distinguishes “instructions from my operator” from “instructions an attacker pasted into the group chat.” So the design assumption is blunt: `,_jsx(_components.strong,{children:`prompt injection eventually succeeds, and the agent acts as if the attacker wrote its instructions.`})]}),`\n`,_jsxs(_components.p,{children:[`Everything below answers the question that assumption forces: `,_jsx(_components.em,{children:`what can a fully compromised agent actually do?`}),` Each layer shrinks that answer. This page is the why; the configuration lives in `,_jsx(_components.a,{href:`/operate/hardening`,children:`Hardening`}),` and `,_jsx(_components.a,{href:`/operate/credentials`,children:`Credentials`}),`.`]}),`\n`,_jsx(OptimizedImage,{className:`mint-block dark:mint-hidden`,src:`https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-light.svg?fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=f3565dc7e6eb4d697d0531ce74bc955c`,alt:`Containment view of the security model: an assumed-injected agent inside the container boundary, sender policies and the command gate on the inbound side, and bounded filesystem, network, and privileged-action paths crossing out`,\"data-og-width\":`1320`,width:`1320`,\"data-og-height\":`720`,height:`720`,\"data-path\":`images/diagrams/security-light.svg`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-light.svg?w=280\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=f8dd3d9c0c2b95a51ef97d5c82c580b1 280w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-light.svg?w=560\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=09ff020f2a9a3ce5f68bdbf7a56d25b0 560w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-light.svg?w=840\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=0d6612c891d7589cf67801d38cbf93ff 840w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-light.svg?w=1100\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=c6907f8061d0fbe983b334728157451d 1100w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-light.svg?w=1650\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=37684873d785d6a605ae2d4bba9c1a4f 1650w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-light.svg?w=2500\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=2553c6730ebfc9f85f97c6014a87c044 2500w`}),`\n`,_jsx(OptimizedImage,{className:`mint-hidden dark:mint-block`,src:`https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-dark.svg?fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=bbe749a6d36780e74d13279cd4030d70`,alt:`Containment view of the security model: an assumed-injected agent inside the container boundary, sender policies and the command gate on the inbound side, and bounded filesystem, network, and privileged-action paths crossing out`,\"data-og-width\":`1320`,width:`1320`,\"data-og-height\":`720`,height:`720`,\"data-path\":`images/diagrams/security-dark.svg`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-dark.svg?w=280\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=bd8b316ee049b61dbc3759451a92b367 280w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-dark.svg?w=560\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=96935a03ebf7c5f11cb057e9078f670f 560w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-dark.svg?w=840\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=66dd08d69aa0bf3e7c72dc620b20210b 840w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-dark.svg?w=1100\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=e60bed0eaeff011d545d7be9d069708d 1100w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-dark.svg?w=1650\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=37fe15e14273ce95f1a1377f09c4ff69 1650w, https://mintcdn.com/qwibitai-nanoclaw-8/eaEEYcmCdIZ6Tipx/images/diagrams/security-dark.svg?w=2500\u0026fit=max\u0026auto=format\u0026n=eaEEYcmCdIZ6Tipx\u0026q=85\u0026s=35803ac09449cee702ecbc4cfb8cf8df 2500w`}),`\n`,_jsx(Heading,{level:`2`,id:`container-isolation-—-the-blast-radius-is-one-agent-group`,children:`Container isolation — the blast radius is one agent group`}),`\n`,_jsxs(_components.p,{children:[`A compromised agent is a compromised `,_jsx(_components.em,{children:`container`}),`, and every active session gets its own. The container runs as the unprivileged `,_jsx(_components.code,{children:`node`}),` user (or your host uid/gid), and `,_jsx(_components.code,{children:`--rm`}),` removes the container after exit; mounted group and session state persists. Every spawn also carries the same hardening flags, unconditionally and with no per-group override: `,_jsx(_components.code,{children:`--cap-drop=ALL`}),` and `,_jsx(_components.code,{children:`--security-opt no-new-privileges`}),` (depth against a root-in-container path — under the normal user mapping the capability sets are already empty), `,_jsx(_components.code,{children:`--init`}),` so signals actually reach the runner instead of being discarded at PID 1, plus a configurable `,_jsx(_components.code,{children:`--pids-limit`}),` fork-bomb backstop that defaults to 2048. The image itself strips the setuid-root bit from every binary Debian ships (`,_jsx(_components.code,{children:`mount`}),`, `,_jsx(_components.code,{children:`su`}),`, `,_jsx(_components.code,{children:`passwd`}),`, and friends), none of which an unprivileged agent needs. Its filesystem view is exactly the `,_jsx(_components.a,{href:`/concepts/container-lifecycle#spawn--the-world-rebuilt-every-time`,children:`mount table`}),`: the session folder, the group workspace, and read-only shared code. No host home directory, no `,_jsx(_components.code,{children:`.ssh`}),`, no Docker socket. Its own `,_jsx(_components.code,{children:`container.json`}),` and composed project document are re-mounted read-only on top of the writable workspace, so the agent can read its configuration but not rewrite it to grant itself more. Stamped plugin code under `,_jsx(_components.code,{children:`plugins/`}),` is also mounted read-only; runtime writes belong in the separate `,_jsx(_components.code,{children:`plugin-data/`}),` tree, which remains writable through the group mount.`]}),`\n`,_jsxs(_components.p,{children:[`One boundary the container does `,_jsx(_components.em,{children:`not`}),` draw: every session of an agent group mounts the same group workspace read-write, so what a compromised session reaches is the `,_jsx(_components.strong,{children:`group’s`}),` files and `,_jsx(_components.code,{children:`memory/`}),` tree, not just its own conversation’s. Session databases and provider continuations stay separate, but an agent can retrieve other sessions’ transcripts within its group through `,_jsx(_components.code,{children:`ncl sessions history`}),`. That command rejects cross-group reads. The honest unit of blast radius is therefore the agent group, which is why different trust domains belong in `,_jsx(_components.a,{href:`/concepts/isolation-levels`,children:`separate agent groups`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`The container is also disposable by design — the host kills it on a stale heartbeat and rebuilds the entire environment at next spawn, so a compromised process doesn’t outlive its session. See `,_jsx(_components.a,{href:`/concepts/container-lifecycle`,children:`Container lifecycle`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`Files flow `,_jsx(_components.em,{children:`into`}),` the container too — channel attachments and files forwarded from other agents are written by the `,_jsx(_components.strong,{children:`host`}),` into the session’s writable `,_jsx(_components.code,{children:`inbox/`}),`. That’s a classic symlink trap: a compromised agent could replace its inbox with a symlink and redirect the host’s write anywhere the host can reach. The shared inbox guard (`,_jsx(_components.code,{children:`src/inbox-safety.ts`}),`) closes it — before any write, the host verifies the inbox root and the per-message directory are real directories (not symlinks), resolves the final path, and refuses anything that lands outside the session folder; the writes themselves use exclusive-create flags so a pre-placed symlinked `,_jsx(_components.em,{children:`file`}),` can’t be followed either.`]}),`\n`,_jsx(Heading,{level:`2`,id:`no-raw-credentials-in-containers`,children:`No raw credentials in containers`}),`\n`,_jsxs(_components.p,{children:[`The single most valuable thing to exfiltrate from an agent is an API key, so the invariant is that there are none: secrets live in the OneCLI Agent Vault on the host, and the vault’s gateway injects them into outbound HTTPS requests in transit. Where a tool insists on finding a credential file locally, it gets a stub whose secret value is the placeholder `,_jsx(_components.code,{children:`onecli-managed`}),`. A compromised agent can dump its environment, its filesystem, and `,_jsx(_components.code,{children:`/proc`}),` and find nothing worth stealing — it can `,_jsx(_components.em,{children:`use`}),` the credentials its group was granted (the gateway injects them per request), but it can never `,_jsx(_components.em,{children:`extract`}),` them to use elsewhere. If the vault is unreachable, the spawn aborts rather than falling back to raw keys.`]}),`\n`,_jsxs(_components.p,{children:[`Setup, per-group secret grants, and the credential admission rules are in `,_jsx(_components.a,{href:`/operate/credentials`,children:`Credentials`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`egress-lockdown-—-closing-the-exfiltration-path`,children:`Egress lockdown — closing the exfiltration path`}),`\n`,_jsxs(_components.p,{children:[`Vault injection works through `,_jsx(_components.code,{children:`HTTPS_PROXY`}),`, and a proxy env var only governs clients that honor it. A compromised agent could open a raw socket — or run any non-proxy-aware tool — and ship data anywhere on the internet, bypassing credential injection, approvals, and audit. Egress lockdown closes that hole at the network layer: agent containers join a Docker `,_jsx(_components.code,{children:`--internal`}),` network with no route to the internet, where the vault gateway is the only reachable hop. The agent runs non-root without `,_jsx(_components.code,{children:`NET_ADMIN`}),`, so it can’t reconfigure its way out, and NanoClaw refuses to spawn at all if lockdown is on but can’t be established.`]}),`\n`,_jsxs(_components.p,{children:[`It’s `,_jsx(_components.strong,{children:`off by default`}),` because it breaks by design: any workflow that needs a non-proxy-aware tool to reach the internet directly will fail. Turn it on when agents face untrusted audiences or hold credentials worth stealing — setup in `,_jsx(_components.a,{href:`/operate/hardening#lock-down-network-egress`,children:`Hardening`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`the-mount-allowlist-—-bounding-filesystem-reach`,children:`The mount allowlist — bounding filesystem reach`}),`\n`,_jsxs(_components.p,{children:[`Mounts define the agent’s entire filesystem reach, so extending them is the most direct way to widen the blast radius. Per-group `,_jsx(_components.code,{children:`additional_mounts`}),` requests are therefore validated against an allowlist at `,_jsx(_components.code,{children:`~/.config/nanoclaw/mount-allowlist.json`}),` that is deny-by-default — `,_jsx(_components.strong,{children:`no allowlist file means no additional mounts`}),` — and lives outside the project root precisely so no container can ever reach it. Built-in blocked patterns (`,_jsx(_components.code,{children:`.ssh`}),`, `,_jsx(_components.code,{children:`.aws`}),`, `,_jsx(_components.code,{children:`.env`}),`, `,_jsx(_components.code,{children:`id_rsa`}),`, and friends) can’t be removed, host paths are resolved through symlinks before checking so the agent can’t alias its way past the rules, and everything is read-only unless both the request and the matching root opt into read-write.`]}),`\n`,_jsxs(_components.p,{children:[`Rules and tooling in `,_jsx(_components.a,{href:`/operate/hardening#gate-additional-mounts`,children:`Hardening`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`who-can-talk-to-your-agents`,children:`Who can talk to your agents`}),`\n`,_jsxs(_components.p,{children:[`Every layer above limits what a compromised agent can do; this one limits who gets to attempt the compromise. Prompt injection needs a delivery channel, and the cheapest one is just `,_jsx(_components.em,{children:`messaging the bot`}),`. Three gates stand in the way:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Channel registration`}),` — a mention or DM in a chat NanoClaw has never seen doesn’t reach any agent. It escalates to a human (group admins, then global admins, then owners) with an approve/deny card; denied chats drop silently forever.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Sender policies`}),` — each known chat carries an `,_jsx(_components.code,{children:`unknown_sender_policy`}),`: `,_jsx(_components.code,{children:`strict`}),` silently drops strangers, `,_jsx(_components.code,{children:`request_approval`}),` asks an admin before admitting them, `,_jsx(_components.code,{children:`decline_notify`}),` politely declines unknown DM senders and sends an owner or admin a one-line FYI, and `,_jsx(_components.code,{children:`public`}),` lets anyone in. On group chats, `,_jsx(_components.code,{children:`decline_notify`}),` falls back to a silent drop. Auto-created chats use the receiving adapter’s declared DM or group policy; undeclared adapters fall back to `,_jsx(_components.code,{children:`request_approval`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Per-wiring scope`}),` — `,_jsx(_components.code,{children:`sender_scope: known`}),` requires owner, admin, or group membership even in a `,_jsx(_components.code,{children:`public`}),` chat. Crucially, messages refused here are never even accumulated as context, so a rejected sender can’t smuggle instructions into the agent’s next batch.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Configuration in `,_jsx(_components.a,{href:`/operate/hardening#restrict-who-can-talk-to-your-agents`,children:`Hardening`}),`; how senders, roles, and wirings fit together in the `,_jsx(_components.a,{href:`/concepts/entity-model`,children:`entity model`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`human-in-the-loop-approvals`,children:`Human-in-the-loop approvals`}),`\n`,_jsxs(_components.p,{children:[`Some actions are too consequential to leave to a model that might be acting on injected instructions, so they pause for a human. The approvals module delivers a card to an admin DM — group admins first, then global admins, then owners, and `,_jsx(_components.strong,{children:`no reachable approver means auto-deny`}),` — and nothing happens until someone taps Approve:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Credential use`}),` — vault secrets can require per-request approval; the gateway holds the HTTPS request open while the card shows an admin the method, host, path, and body preview.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Self-modification`}),` — an agent asking to install packages or add an MCP server into its own container is asking to expand its own capabilities, which is exactly what an attacker would ask for. Both actions queue an approval card.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Channel registration`}),` — new chats reaching agents, as above.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Agent-to-agent messages`}),` — an operator can gate a connection between two agents so every message from one to the other is held until a human approves it, without un-wiring the connection. Set it with `,_jsx(_components.a,{href:`/guides/multi-agent-swarm#gate-a-connection`,children:_jsx(_components.code,{children:`ncl policies set`})}),`; no policy means messages flow freely.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Two refinements control who decides and what the agent learns:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Named approver`}),` — most cards fall to the first reachable admin, but an agent-to-agent message policy pins a specific approver. Only that exact user (or an owner) can resolve the card, recorded as `,_jsx(_components.code,{children:`approver_user_id`}),` on the request — so a held message can’t be cleared by just anyone with admin rights.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Reject with reason`}),` — alongside Approve and Reject, an admin can decline with a one-line note. NanoClaw parks the request, prompts the admin in their DM, and relays their reply back to the requesting agent (`,_jsx(_components.code,{children:`Your \u003caction> request was rejected by admin: \"\u003creason>\"`}),`). If the admin doesn’t reply within ~5 minutes — or the host restarts mid-prompt — the host sweep finalizes a plain reject, so the agent is never left waiting on a decision that never comes.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`The credential flow in detail, including card expiry and `,_jsx(_components.code,{children:`ncl approvals`}),`, is in `,_jsx(_components.a,{href:`/operate/credentials#approving-credential-use`,children:`Credentials`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`the-command-gate`,children:`The command gate`}),`\n`,_jsxs(_components.p,{children:[`Platform slash-commands are classified on the host before any container sees them. Commands that manipulate host-side CLI state (`,_jsx(_components.code,{children:`/login`}),`, `,_jsx(_components.code,{children:`/logout`}),`, `,_jsx(_components.code,{children:`/config`}),`, and friends) are dropped outright, and operational commands like `,_jsx(_components.code,{children:`/clear`}),` or `,_jsx(_components.code,{children:`/upload-trace`}),` require an owner or admin role — so a stranger in a `,_jsx(_components.code,{children:`public`}),` chat can’t wipe an agent’s context or exfiltrate a trace just by typing. The full classification is in `,_jsx(_components.a,{href:`/operate/hardening#the-command-gate`,children:`Hardening`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`supply-chain`,children:`Supply chain`}),`\n`,_jsxs(_components.p,{children:[`The host process itself is in the trusted zone, so its dependencies matter: pnpm is configured with a three-day `,_jsx(_components.code,{children:`minimumReleaseAge`}),` (newly published package versions wait before they can be installed) and an `,_jsx(_components.code,{children:`onlyBuiltDependencies`}),` allowlist limiting install scripts to `,_jsx(_components.code,{children:`esbuild`}),`, `,_jsx(_components.code,{children:`protobufjs`}),`, and `,_jsx(_components.code,{children:`sharp`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`what-this-model-doesn’t-defend`,children:`What this model doesn’t defend`}),`\n`,_jsxs(_components.p,{children:[`Be honest about the gap: `,_jsx(_components.strong,{children:`anything the agent can legitimately do, it can do while manipulated.`}),` No layer above distinguishes a sincere action from an injected one — they only bound the action space. A compromised agent can still:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Message anyone in its destinations`}),` — including posting attacker-chosen content to your team channel, or leaking conversation context to another chat it legitimately serves.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Read and destroy its whole workspace`}),` — every session of an agent group shares the group folder read-write, including its `,_jsx(_components.code,{children:`memory/`}),` tree and `,_jsx(_components.code,{children:`instructions.prepend.md`}),` — and it can read whatever its allowlisted mounts expose (read-write only where explicitly granted).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Spend through granted credentials`}),` — it can’t extract keys, but it can make authenticated API calls with everything its group was granted (per-request approval, where enabled, is the brake).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Poison its own memory`}),` — instructions written to its `,_jsxs(_components.a,{href:`/concepts/agent-memory`,children:[_jsx(_components.code,{children:`memory/`}),` tree`]}),` or `,_jsx(_components.code,{children:`instructions.prepend.md`}),` today shape every future session of that group.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`The defense against all four is the same and it’s yours, not the software’s: `,_jsx(_components.strong,{children:`scoping`}),`. An agent that talks to strangers should not share a group with your code repo and deploy credentials; chats with different audiences should not share files or memory. The decision guide is `,_jsx(_components.a,{href:`/concepts/isolation-levels`,children:`Isolation levels`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`related-pages`,children:`Related pages`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/operate/hardening`,children:`Hardening`}),` — the how for egress, mounts, sender policies, and the command gate`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/operate/credentials`,children:`Credentials`}),` — vault mechanics, stubs, and approval cards`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/container-lifecycle`,children:`Container lifecycle`}),` — the mount table and the spawn/kill cycle`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/isolation-levels`,children:`Isolation levels`}),` — choosing how much your agents share`]}),`\n`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Security model","description":"The threat model behind NanoClaw's design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.","keywords":["security","threat model","prompt injection","container isolation","egress lockdown","mount allowlist","approvals","credentials","defense in depth"],"href":"/concepts/security"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Container isolation — the blast radius is one agent group","slug":"container-isolation-—-the-blast-radius-is-one-agent-group","depth":2,"children":[]},{"title":"No raw credentials in containers","slug":"no-raw-credentials-in-containers","depth":2,"children":[]},{"title":"Egress lockdown — closing the exfiltration path","slug":"egress-lockdown-—-closing-the-exfiltration-path","depth":2,"children":[]},{"title":"The mount allowlist — bounding filesystem reach","slug":"the-mount-allowlist-—-bounding-filesystem-reach","depth":2,"children":[]},{"title":"Who can talk to your agents","slug":"who-can-talk-to-your-agents","depth":2,"children":[]},{"title":"Human-in-the-loop approvals","slug":"human-in-the-loop-approvals","depth":2,"children":[]},{"title":"The command gate","slug":"the-command-gate","depth":2,"children":[]},{"title":"Supply chain","slug":"supply-chain","depth":2,"children":[]},{"title":"What this model doesn’t defend","slug":"what-this-model-doesn’t-defend","depth":2,"children":[]},{"title":"Related pages","slug":"related-pages","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"dynamicCss":".mint-block {\n display: block\n}\n.mint-hidden {\n display: none\n}\n.dark\\:mint-block:is(.dark *) {\n display: block\n}\n.dark\\:mint-hidden:is(.dark *) {\n display: none\n}","subdomain":"qwibitai-nanoclaw-8","siteName":"NanoClaw","title":"Security model - NanoClaw","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-09-04T22:14:31.927Z","gitLastModified":"2026-09-04T21:44:10.000Z","originalFileLocation":"concepts/security.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=\"dark\";if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"The threat model behind NanoClaw\u0026#x27;s design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.\">\u003cmeta data-mint-head name=\"application-name\" content=\"NanoClaw\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Security model - NanoClaw\">\u003cmeta data-mint-head property=\"og:description\" content=\"The threat model behind NanoClaw\u0026#x27;s design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"NanoClaw\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.nanoclaw.dev/concepts/security\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://qwibitai-nanoclaw-8.mintlify.site/_mintlify/api/og/v2?division=Understand\u0026amp;title=Security+model\u0026amp;description=The+threat+model+behind+NanoClaw%27s+design%3A+assume+the+agent+gets+prompt-injected%2C+then+make+sure+a+compromised+agent+has+nothing+to+steal+and+nowhere+to+go.\u0026amp;theme=0741fc673de6ce854a783acf\u0026amp;signature=d022009e839fc0f98b520b8331a4f06c1ad9ccbba9292f0c2cb61fa4810ccc0e\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Security model - NanoClaw\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"The threat model behind NanoClaw\u0026#x27;s design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://qwibitai-nanoclaw-8.mintlify.site/_mintlify/api/og/v2?division=Understand\u0026amp;title=Security+model\u0026amp;description=The+threat+model+behind+NanoClaw%27s+design%3A+assume+the+agent+gets+prompt-injected%2C+then+make+sure+a+compromised+agent+has+nothing+to+steal+and+nowhere+to+go.\u0026amp;theme=0741fc673de6ce854a783acf\u0026amp;signature=d022009e839fc0f98b520b8331a4f06c1ad9ccbba9292f0c2cb61fa4810ccc0e\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"NanoClaw\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#2DD4BF\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.nanoclaw.dev/concepts/security\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/concepts/security.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.nanoclaw.dev/#organization\",\"name\":\"NanoClaw\",\"url\":\"https://docs.nanoclaw.dev\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/nanoclaw-logotype-dark.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=7fabf1abf0eb70d18a0235dd67255fcb\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.nanoclaw.dev#website\",\"name\":\"NanoClaw\",\"url\":\"https://docs.nanoclaw.dev\",\"publisher\":{\"@id\":\"https://docs.nanoclaw.dev/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.nanoclaw.dev/concepts/security#webpage\",\"url\":\"https://docs.nanoclaw.dev/concepts/security\",\"name\":\"Security model\",\"description\":\"The threat model behind NanoClaw's design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.\",\"dateModified\":\"2026-09-04T21:44:10.000Z\",\"isPartOf\":{\"@id\":\"https://docs.nanoclaw.dev#website\"},\"breadcrumb\":{\"@id\":\"https://docs.nanoclaw.dev/concepts/security#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.nanoclaw.dev/concepts/security#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Understand\",\"item\":\"https://docs.nanoclaw.dev/concepts/architecture\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Security model\",\"item\":\"https://docs.nanoclaw.dev/concepts/security\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.nanoclaw.dev/concepts/security#article\",\"headline\":\"Security model\",\"name\":\"Security model\",\"description\":\"The threat model behind NanoClaw's design: assume the agent gets prompt-injected, then make sure a compromised agent has nothing to steal and nowhere to go.\",\"url\":\"https://docs.nanoclaw.dev/concepts/security\",\"mainEntityOfPage\":{\"@id\":\"https://docs.nanoclaw.dev/concepts/security#webpage\"},\"dateModified\":\"2026-09-04T21:44:10.000Z\",\"keywords\":\"security, threat model, prompt injection, container isolation, egress lockdown, mount allowlist, approvals, credentials, defense in depth\",\"publisher\":{\"@id\":\"https://docs.nanoclaw.dev/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.nanoclaw.dev#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"NanoClaw\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"NanoClaw","nav":{"global":{"anchors":[{"anchor":"Changelog","icon":"clock-rotate-left","href":"/changelog"}]},"tabs":[{"tab":"Documentation","groups":[{"group":"Get started","icon":"rocket","pages":[{"title":"What is NanoClaw?","icon":"house","sidebarTitle":"Introduction","tag":"UPDATED","href":"/introduction"},{"title":"Quick start","icon":"bolt","tag":"UPDATED","href":"/quickstart"},{"title":"Installation","icon":"download","href":"/installation"},{"title":"Migrate from v1 or OpenClaw","icon":"route","href":"/migrate-from-v1"}]},{"group":"Channels","icon":"tower-broadcast","pages":[{"title":"Channels overview","href":"/channels/overview"},{"title":"WhatsApp","href":"/channels/whatsapp"},{"title":"Telegram","href":"/channels/telegram"},{"title":"Discord","href":"/channels/discord"},{"title":"Slack","sidebarTitle":"Slack","tag":"UPDATED","href":"/channels/slack"},{"title":"Mattermost","href":"/channels/mattermost"},{"title":"Signal","href":"/channels/signal"},{"title":"iMessage","href":"/channels/imessage"},{"title":"Microsoft Teams","tag":"UPDATED","href":"/channels/teams"},{"title":"Dial","href":"/channels/dial"},{"title":"CLI channel","href":"/channels/cli"},{"title":"More channels","href":"/channels/more-channels"}]},{"group":"Operate","icon":"gauge","pages":[{"title":"Configuration","tag":"UPDATED","href":"/operate/configuration"},{"title":"The ncl admin CLI","href":"/operate/ncl-cli"},{"title":"Credentials","tag":"UPDATED","href":"/operate/credentials"},{"title":"Credential gateways","tag":"NEW","href":"/operate/gateways"},{"title":"Hardening","href":"/operate/hardening"},{"title":"Get started with the Echo hardened runtime","sidebarTitle":"Hardened runtime","href":"/guides/echo-hardened-runtime"},{"title":"Upgrading","href":"/operate/upgrading"},{"title":"Uninstall NanoClaw","icon":"trash","href":"/operate/uninstall"},{"title":"Troubleshooting","href":"/operate/troubleshooting"}]},{"group":"Build with agents","icon":"wand-magic-sparkles","pages":[{"title":"Your first agent","href":"/guides/first-agent"},{"title":"Customize an agent","href":"/guides/customize-an-agent"},{"title":"Schedule recurring work","href":"/guides/scheduled-tasks"},{"title":"Build a multi-agent team","href":"/guides/multi-agent-swarm"}]},{"group":"Templates","icon":"layer-group","pages":[{"title":"Getting started with a template","tag":"UPDATED","href":"/templates/getting-started"},{"title":"Templates","href":"/templates/overview"},{"title":"Using a template","tag":"UPDATED","href":"/templates/using-templates"},{"title":"Updating a stamped agent","href":"/templates/updating"},{"title":"Building a template","href":"/templates/building"},{"title":"Submitting a template","href":"/templates/submitting"}]},{"group":"Extend","icon":"puzzle-piece","pages":[{"title":"Skills: how NanoClaw extends itself","href":"/extend/overview"},{"title":"Give agents tools","href":"/extend/tools"},{"title":"Agent providers","tag":"UPDATED","href":"/extend/providers"},{"title":"Agent self-modification","href":"/extend/self-modification"},{"title":"Writing skills","href":"/extend/writing-skills"}]},{"group":"Understand","icon":"lightbulb","pages":[{"title":"Architecture","href":"/concepts/architecture"},{"title":"Entity model","href":"/concepts/entity-model"},{"title":"Agent memory","href":"/concepts/agent-memory"},{"title":"Isolation levels","tag":"UPDATED","href":"/concepts/isolation-levels"},{"title":"Container lifecycle","description":"The full container story: how the agent image is built, what gets assembled at every spawn, how wakes are deduplicated, and what kills a container.","tag":"UPDATED","href":"/concepts/container-lifecycle"},{"title":"Security model","href":"/concepts/security"},{"title":"Contributing to NanoClaw","description":"How the nanoclaw repo is laid out, the dev loop, where channel and provider code lives, and what kinds of contributions get accepted.","href":"/concepts/contributing"}]},{"group":"Changelog","icon":"clock-rotate-left","pages":[{"title":"Releases","icon":"tag","href":"/changelog/index"},{"title":"Docs updates","icon":"file-pen","href":"/changelog/docs-updates"}]}]},{"tab":"Reference","pages":[{"title":"Reference","href":"/reference/ncl-cli"}]}]},"basePath":"","title":"Security model - NanoClaw","buildId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","deploymentId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","dynamics":["10e4515597","a74be98ba7"],"chunks":["BannerMarkdown-BcDCEkh6.js","dist-CwjSCXIK.js","lib-DuNMoZ05.js","lib-B8HbPgcC.js","clsx-eKJ4rcii.js","html-void-elements-mCSPHezU.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js","zoom-image-lightbox-DMoL6lHz.js","lightbox-handle-r17bVUz3.js"]}