NanoClaw nanoclaw.dev ↗
Node.js 20+ host process that spawns per-agent Linux Docker containers running Bun + the Anthropic Claude Agent SDK. Messaging-channel AI assistant (WhatsApp/Telegram/Discord/Slack/iMessage/Matrix/GitHub/Linear/Webex/WeChat/Teams/Google Chat/email). Credentials never live in containers — OneCLI Agent Vault injects them at the gateway. CVE-2026-7875 (host filesystem read/delete via crafted outbox messages) fixed in v2.0.63.
Version Pinning / Install Provenance
NanoClaw is install-from-git only (no npm/pypi package); canonical clone is https://github.com/nanocoai/nanoclaw.git. Releases became reliable only at v2.0.63 (May 2026). Pin to a signed release tag rather than tracking main, and verify the GitHub org is nanocoai (project was renamed from qwibitai/nanoclaw; stale forks under the old name still appear in CVE feeds).
git clone --branch v2.0.63 --depth 1 https://github.com/nanocoai/nanoclaw.git nanoclaw-v2
cd nanoclaw-v2 && git verify-tag v2.0.63
bash nanoclaw.shTip: check out a specific release tag, record the commit SHA in your config-management system, re-run pnpm install --frozen-lockfile after every pull.
Server / UI Exposure
NanoClaw's host process does not expose a public HTTP API or admin UI by default. The only network ingress is via channel adapters that you explicitly install (Slack uses Socket Mode and needs no public URL; WhatsApp/Telegram use vendor APIs; the optional Dashboard and Emacs-bridge skills bind locally). Service names are per-install: com.nanoclaw.<sha1(projectRoot)[:8]> on launchd, nanoclaw-<slug>.service on systemd.
lsof -iTCP -sTCP:LISTEN -P | grep -Ei 'node|nanoclaw|bun'
source setup/lib/install-slug.sh && launchd_label # macOS
source setup/lib/install-slug.sh && systemd_unit # LinuxTip: never install Dashboard or Emacs-bridge skills on a multi-user machine without firewalling them to 127.0.0.1; audit lsof after every /add-<channel> skill install.
Authentication
Three-level authorization model: roles (Owner/Admin/Member), unknown-sender policy (public / strict / request_approval), and per-channel sender-scope (all / known). No password/login — identity is the channel-account-ID of the message sender. The Main group ("self-chat") is trusted; every other group is treated as untrusted input.
# In chat, as Owner:
@Andy set channel <channel-id> unknown-sender-policy strict
@Andy set channel <channel-id> sender-scope known
@Andy list members of <group>Tip: default to unknown-sender-policy: strict on every non-Main group; reserve Owner role for one identity; use request_approval only on channels where the admin actually monitors approval cards.
Isolation (Docker / Sandbox)
Isolation is the primary security boundary. Each agent group runs in its own ephemeral Linux container (--rm, uid 1000 node, tini as PID 1). On macOS you can opt into Apple Container via /convert-to-apple-container; Docker Sandboxes provides micro-VM isolation. Only directories you explicitly mount are visible; project root is mounted read-only for Main group, and .env is shadowed with /dev/null inside containers.
docker ps --filter "label=nanoclaw" --format '{{.Names}}\t{{.Image}}'
docker inspect <agent-container> | jq '.[0].Config.User, .[0].HostConfig.ReadonlyRootfs, .[0].Mounts'
# Opt into stronger isolation on macOS:
# @Andy /convert-to-apple-containerTip: turn on Docker Sandboxes (micro-VM) for any agent that touches untrusted channels (public Discord, GitHub PR comments); never mount ~, /, or any parent of credential directories.
Tool Allowlist / Permission System
Tool gating is enforced primarily by mount scope rather than per-tool allowlists — the agent's Bash/Read/Write runs inside the container, so it can only touch what's mounted. Cross-group operations (sending to another chat, scheduling for another user) are blocked at the IPC layer: non-Main groups can act only on themselves. v2.0.63 explicitly hardened this: scopeField now fails closed when scope is missing, and sessions get is guarded against cross-group oracle access.
cat ~/.config/nanoclaw/mount-allowlist.json
# Force read-only for untrusted groups
# @Andy /manage-mounts (set nonMainReadOnly: true for the group)Tip: enable nonMainReadOnly on every non-Main group, keep MCP tool installs minimal, review mount-allowlist.json after every skill install.
Credential / API Key Handling — OneCLI Agent Vault
Real API credentials never enter containers. OneCLI Agent Vault ships as a single Docker container (ghcr.io/onecli/onecli) running two co-located services: a Rust HTTP gateway on port 10255 (intercepts outbound agent requests, swaps placeholder keys with real credentials) and a Next.js dashboard on port 10254. Production deployments use Docker Compose with PostgreSQL — credentials are AES-256-GCM encrypted at rest, decrypted only at request time.
Container routing works by MITM TLS proxy: the gateway generates a local CA, NanoClaw containers trust it via REQUESTS_CA_BUNDLE, and applyContainerConfig({ agent: agentIdentifier }) from @onecli-sh/sdk injects HTTPS_PROXY=http://localhost:10255 into the container environment. The gateway terminates TLS from the agent, rewrites Authorization headers, and re-encrypts upstream with Rustls — same model as mitmproxy. Agents cannot read keys from env, stdin, files, or /proc; they only ever see placeholder tokens.
# Create an identity for a NanoClaw group, register a generic
# bearer-style secret, then a rule rate-limiting outbound calls
onecli agents create --name acme-bot --identifier acme-bot
onecli secrets create \
--name anthropic \
--type generic \
--value sk-ant-... \
--header-name Authorization \
--value-format "Bearer {value}" \
--host-pattern api.anthropic.com \
--agent-id acme-bot
onecli rules create \
--name "Anthropic 1k/hr" \
--host-pattern "api.anthropic.com" \
--action rate_limit --rate-limit 1000 --rate-window 1h \
--agent-id acme-bot
# Rotation = update, revocation = delete (no rotate/revoke commands)
onecli secrets update --id <secret-id> --value sk-ant-new...
onecli secrets delete --id <secret-id>
onecli agents regenerate-token --id acme-botRules are evaluated deterministically at the proxy before any upstream call. Documented flags: --host-pattern, --path-pattern, --method (GET|POST|PUT|PATCH|DELETE), --action (block or rate_limit), --agent-id, --rate-limit + --rate-limit-window (minute|hour|day). Agents authenticate to the gateway with a token presented in the Proxy-Authorization header, issued by onecli agents create. Audit data surfaces in the dashboard Logs pane (agent name, target host, path, timestamp per proxied request) — there is no documented CLI subcommand and no published log-shipping schema, so SIEM export is currently a manual scrape.
Limitations documented or implied as not-yet-shipped: time-of-day windows in rules, source-IP predicates, mTLS client-cert auth to upstream, first-class connectors for HashiCorp Vault / 1Password / SOPS / cloud KMS — NanoClaw's SECURITY.md notes "time-bound access and approval flows are on the roadmap." If you need any of these today, layer them at your egress proxy / IdP, not at OneCLI.
Tip: never put ANTHROPIC_API_KEY directly in .env, container.json, or NanoClaw's central DB — always go through OneCLI. Bind the dashboard to loopback only (127.0.0.1:10254:10254) or VPN — upstream docs are explicit that "the web dashboard should not be exposed to the internet." Set placeholder values (e.g. OPENAI_API_KEY=placeholder) inside containers so accidental direct-API fallback paths fail closed. Run the multi-user deployment Postgres-backed, not embedded-SQLite. Give each NanoClaw agent group its own OneCLI identity with the narrowest secret scope + rate-limit policy; rotate tokens (agents regenerate-token) on any suspected compromise.
Plugin / Skill / MCP Server Safety
Trunk ships only the registry and infra; channels and providers are installed as skills from the channels and providers branches via /add-<name>. Trunk .mcp.json is empty ({"mcpServers": {}}) — MCP servers arrive only when a skill adds them. v2.0.63 fixed a bug where MCP servers added via add_mcp_server were not inheriting OneCLI gateway routing, so older installs may have leaked keys to MCP tools.
git log --oneline channels..HEAD
cat .mcp.json
grep -r "add_mcp_server\|mcpServers" groups/ container/Tip: install only the channel and provider skills you actively use; review the diff every /add-<name> produces before committing; upgrade to v2.0.63+ so MCP servers route credentials through OneCLI.
Prompt Injection Defense
Prompt injection is treated as inevitable, mitigated by blast-radius reduction rather than input filtering. A compromised agent is limited to its own session DB, its own mounts, and its own OneCLI identity. The host enforces destination wrapping (<message> tags) and v2.0.63 hardened compaction-reminder placement so it survives SDK auto-compaction. CVE-2026-7875 showed why the host/container boundary matters: a prompt-injected agent supplied crafted messages_out.id and content.files (and symlinked outbox files) to make the host read/delete files outside the outbox.
content.files array — host sweeper followed the symlinks and read/deleted host-side files outside the outbox boundary. Fixed in v2.0.63. TheHackerWiregit fetch --tags origin && git checkout v2.0.63
grep -rn "messages_out.id\|content.files\|outbox" src/delivery.ts src/host-sweep.tsTip: run v2.0.63 or later, never set unknown-sender-policy: public, never mount ~ or anything containing credentials into any container, never grant a non-Main group cross-channel send rights.
Update / Telemetry Control
Telemetry is opt-in and skill-driven: diagnostics only run during /setup and /update-nanoclaw skill workflows, written as markdown instructions. Updates are pull-from-git plus skill re-application; /update-nanoclaw previews changes with rollback. Supply-chain defenses on the host: pnpm-workspace.yaml sets minimumReleaseAge: 4320 (3 days) and onlyBuiltDependencies restricts install/postinstall scripts to exactly four packages by name: better-sqlite3, esbuild, protobufjs, sharp. .npmrc minReleaseAge=3d is a fallback layer beneath the workspace setting.
# @Andy /update-nanoclaw
grep -E "minimumReleaseAge|onlyBuiltDependencies" pnpm-workspace.yaml
pnpm install --frozen-lockfileTip: keep minimumReleaseAge: 4320; never add minimumReleaseAgeExclude entries without a human-approved CVE reference and exact-version pin; subscribe to the GitHub Releases feed.
Logging / Monitoring / Audit Trail
NanoClaw deliberately ships no monitoring dashboard or debugging UI on trunk — the AI-native model is to ask Claude Code via /debug. Per-session state lives in two SQLite files (inbound.db, outbound.db) with exactly one writer each, plus a central DB tracking users, roles, agent groups, messaging-group wirings and migrations. The optional Dashboard skill adds a local UI for sessions, agents, and token usage; container logs available via docker logs.
docker logs --since 24h <agent-container> | tee /var/log/nanoclaw/<group>-$(date +%F).log
sqlite3 store/nanoclaw.db ".tables"
sqlite3 data/sessions/<group>/inbound.db "SELECT id, ts, sender FROM messages ORDER BY ts DESC LIMIT 50;"
sqlite3 data/sessions/<group>/outbound.db "SELECT id, ts, files FROM messages_out ORDER BY ts DESC LIMIT 50;"Tip: ship docker logs to a write-only off-host store (CVE-2026-7875 showed the outbox can be abused for host-side delete, so don't keep your only copy of logs on the same host); install Dashboard skill only on a trusted local network; periodically diff mount-allowlist.json against a known-good baseline.
References & further reading
- NanoClaw homepage
- nanocoai/nanoclaw on GitHub
- NanoClaw Skills catalog
- NanoClaw security model (docs.nanoclaw.dev)
- SECURITY.md in repo
- The New Stack: NanoClaw containerized AI agents
- CVE-2026-7875 NanoClaw filesystem boundary — RedPacket
- NanoClaw Container Escape — TheHackerWire
- v2.0.63 release notes