NanoClaw Logo
NanoClaw

NanoClaw 容器隔离边界:权限模型与数据流向拆解

12分钟阅读

NanoClaw(基于 Claude Agent SDK 的个人 AI 助手)把「助手能碰到什么」交给操作系统裁决。本文拆开三层:边界画在哪里、权限如何判定、数据在宿主与容器之间怎么走,并说明容器隔离不覆盖哪些风险。

NanoClaw 容器隔离边界:权限模型与数据流向拆解

NanoClaw(基于 Claude Agent SDK 的个人 AI 助手)把「助手能碰到什么」这件事交给操作系统裁决,而不是交给提示词或应用层的判断。它的宿主是一个很小的单进程程序,真正的隔离边界由每个群组独立的容器承担。

这篇文章拆三层:边界画在哪里、权限怎么判定、数据在宿主与容器之间怎么走。最后说明这套模型明确不覆盖什么。

一、先看架构:1 个进程、5 个核心文件、约 500 行核心逻辑

NanoClaw 的宿主是一个 Node.js 进程。核心逻辑集中在约 5 个 TypeScript 文件、总计约 500 行代码,主要集中在:

文件 职责
src/index.ts 进程入口与消息主循环
src/container-runner.ts 容器生命周期、挂载表构造
src/task-scheduler.ts 定时任务调度
src/db.ts SQLite 持久化

没有微服务,没有消息队列,没有中间件层。这个规模直接决定了安全审计的成本:要确认「助手究竟能做什么」,读这约 500 行比读一个几十万行的框架现实得多。

两个需要记住的数字:

  • 默认最多 3 个并发容器。 第 4 个群组触发消息时排队,而不是无上限地起容器。
  • 30.2k GitHub Stars。 项目在 GitHub 上约 30.2k Stars,代码可公开审计。

会话状态落在 SQLite 里。宿主只做路由与调度,真正的执行体在容器内。

二、边界有四层,最外层才是硬边界

官方安全文档把容器描述为硬安全层,其余几层是纵深防御。按由外到内排列:

第一层:进程隔离。 每个群组跑在自己的容器里,基于 Apple Container / Docker 的操作系统级容器隔离。容器以非特权用户(uid 1000)运行,用 --rm 启动,每次调用新建、退出销毁。挂载的群组与会话状态在容器销毁后保留,容器本身不留存。

第二层:文件系统可见范围 = 挂载表。 容器能看到的路径,正好等于挂载表里列出的路径。没有宿主 home 目录,没有 .ssh,没有 Docker socket。宿主应用代码以只读方式挂载;确有写入需求的部分(群组目录、IPC 目录、.claude/)单独挂载为可写。这样做的目的是:代理无法改写 src/package.json 之类的宿主代码,再借下次重启绕过沙箱。

同一逻辑还有一处细节:container.json 和组合后的项目文档会以只读方式重新挂载,覆盖在可写工作区之上,因此代理无法改写自己的配置来给自己扩权。plugins/ 只读,运行时写入走单独的可写数据目录。

第三层:会话隔离。 每个群组有自己的 Claude 会话目录(data/sessions/{group}/.claude/)、自己的 IPC 命名空间(data/ipc/{group}/)、自己的 CLAUDE.md 记忆文件。会话数据里包含消息历史和读过的文件内容,所以跨群组读取被明确阻断。

第四层:凭据隔离。 宿主根目录的 .env 从不整体挂载。只有白名单内的少量变量会被提取出来,写入一个只读文件后挂载。数据库凭据、WhatsApp 的 store/auth/ 登录态、SSH 密钥都不进容器。较新的实现进一步把密钥留在宿主侧的凭据代理里,容器内只拿到一个占位值,出站 HTTPS 请求在传输层被注入真实凭据;代理不可达时直接放弃启动,而不是回退到明文密钥。此外,每次执行 Bash 前这些环境变量会被 unset,阻断通过 shell 外带。

三、挂载白名单:默认值就是拒绝

这是权限模型里最容易被忽略、也最值得逐条看的部分。

配置文件位置是有意为之。 白名单位于 ~/.config/nanoclaw/mount-allowlist.json,在项目根目录之外,且从不挂载进任何容器。被接管的代理既读不到它,也改不了自己的权限。

文件不存在等于全部拒绝。 如果没有这个文件,所有额外挂载请求一律被拒。这是默认姿态,不需要额外配置。

校验顺序是固定的:

  1. 容器路径必须是相对路径、非空、不含 ..:,防止路径穿越与 Docker -v 参数注入。合法挂载落点固定在 /workspace/extra/ 下。
  2. 宿主路径先做波浪号展开,再解析符号链接到真实路径。先解析再校验,正是为了堵住用软链接别名绕过规则的做法。
  3. 屏蔽模式检查真实路径。用户自定义的屏蔽项会与一份不可移除的内置列表合并:.ssh.gnupg.aws.kube.dockercredentials.env.netrcid_rsaid_ed25519,以及 .config/nanoclaw.local/bin。最后两项的作用是阻止容器指涉 NanoClaw 自己的宿主状态——白名单文件、仓库凭据,以及由宿主执行的辅助二进制。
  4. 允许根检查真实路径是否落在配置的允许根之下。
  5. 读写权限默认关闭。 只有请求方与对应的允许根同时声明可写时才给读写,否则强制只读。非主群组另有 nonMainReadOnly 强制只读。

改动只能从宿主侧发起。 运维通过宿主命令添加或移除挂载,重启服务后生效。代理自身没有任何途径修改这份文件。

还有一处不那么显眼的防护:附件与其他代理转发过来的文件,是由宿主写入会话可写 inbox/ 目录的。如果代理把自己的 inbox 换成指向别处的软链接,就能把宿主的写入重定向出去。对应的 inbox 守卫(src/inbox-safety.ts)在任何写入前先验证 inbox 根目录与每条消息目录是真实目录而非符号链接,解析最终路径并拒绝落在会话目录之外的写入,写入使用独占创建标志,避免跟随预先放好的软链接文件。

需要说清楚定位:白名单是纵深防御的一层,主要作用是防止使用者误挂载本不该暴露的目录。硬安全层是容器边界本身。

四、群组就是信任边界的单位

NanoClaw 的安全模型把群组当作权限单元,而不是把单个会话当作权限单元。

主群组与非主群组的分野。 主群组通常是管理员自己的私聊,被视为可信;非主群组默认不可信,因为群里可能有其他人。二者的挂载表不一样:非主群组只拿到自己的群组目录(可写)、全局记忆(只读)、自己的会话目录和自己的 IPC 目录;主群组额外拥有项目级访问能力。

全局记忆只有主频道能写。 全局记忆目录对非主群组以只读方式挂载。

IPC 操作按群组身份授权。 向本群发消息、为自己安排任务,两类群组都允许;向其他聊天发消息、替其他群组安排任务、管理其他群组,仅限主群组;任务查看上,主群组能看全部,非主群组只能看自己的。

这里必须讲清楚边界单位,否则会高估隔离强度。 容器并不隔离同一群组内的多个会话。同一个群组的每个会话都以读写方式挂载同一份群组工作区,因此一处被攻破,影响面是这个群组的文件与 memory/ 记忆树,而不只是那一段对话。会话数据库和服务商侧的延续状态是彼此分开的,但群组内的历史记录可以通过宿主命令取出(该命令会拒绝跨群组读取)。

结论很直接:真正的爆炸半径单位是代理群组。 不同信任域应该放进不同的群组,而不是指望会话之间互相隔离。

五、数据流向:从 WhatsApp 进,从 WhatsApp 出

把上面的边界串成一条链路:

入站。 WhatsApp 没有面向个人的官方 bot API。NanoClaw 用开源的 WhatsApp Web 库(Baileys)连接,扫码关联设备即可用你自己的账号收发消息,不需要 Business API、不需要平台审批、没有按条计费。

宿主单进程接收消息后,判断这条消息是否指向助手;若是,写入 SQLite,然后看该群组是否已有运行中的容器——有则通过 IPC 转发,没有则起一个新容器。容器内的 Claude Agent SDK 处理完毕后,回复经宿主发回原聊天。

消息文本在这里的身份是「用户输入」,并且被明确标注存在提示词注入风险。

凭据流。 前面已述:.env 不整体进容器,只暴露白名单变量;密钥可留在宿主侧代理,容器内只有占位值;执行 Bash 前相关变量被 unset。

网络流。 默认状态是开放出站。可选的出站锁定会把代理容器接入一个没有互联网路由的内部网络,网关成为唯一可达跳点;代理以非 root 运行且没有 NET_ADMIN 能力,无法从容器内部改写网络配置。这个开关默认关闭,因为它会破坏需要直连、不感知代理的工作流。它的失败方式是「关不严就不启动」:如果内部网络或网关挂不上,宿主拒绝启动容器,而不是悄悄以开放出站运行。

六、这套边界明确不覆盖什么

安全文档的威胁模型假设是:提示词注入终将成功,代理会按攻击者的指令行事。 因此每一层都不尝试分辨一条指令是真心还是被注入的,只限制一个被完全接管的代理能做什么。

在这个前提下,以下行为是模型承认无法阻止的:

  • 向该代理目标列表内的人发消息;
  • 读取并销毁自己的整个工作区,包括群组共享目录、memory/ 记忆树、instructions.prepend.md,以及所有被白名单放行的挂载目录;
  • 使用已授予的凭据产生 API 费用;
  • 通过写入 memory/instructions.prepend.md 污染后续会话。

另外,群组内的成员同样不应被默认为可信——任何能在群里发消息的人,都有机会注入提示词。

对应的做法不是「配一个更严的开关」,而是收窄信任面:面向陌生人的代理不要和代码仓库、部署凭据放在一起;不要让不同受众共享文件与记忆;把不同的信任域拆成不同的群组。

七、自己动手核查:三步

官网把安装拆成三步:克隆仓库 → 准备配置 → 由 Claude Code 完成依赖、认证与容器配置。

第一步,克隆仓库。

# 仓库地址以官网 Get Started 入口指向的为准(社区存在多个 fork 与镜像)
git clone https://github.com/nanocoai/nanoclaw.git nanoclaw
cd nanoclaw

第二步,准备配置。 在项目根目录创建 .env,至少写入 Anthropic API Key 与助手名称:

echo 'ANTHROPIC_API_KEY=sk-ant-...' >> .env
echo 'ASSISTANT_NAME=你的助手名' >> .env

以微信 / WhatsApp 通道为例,首次启动需要扫码关联设备。

第三步,交给 Claude Code。 在该目录下启动 Claude Code,让它完成依赖安装、认证与容器运行时配置。之后 npm start,在目标群组里提到助手名称即可对话。

想自己验证隔离边界,最直接的路径是读三处:宿主侧的挂载构造逻辑(src/container-runner.ts)、挂载校验逻辑(src/mount-security.*)、以及仓库里的 docs/SECURITY.md读配置不如读判定代码 ——白名单的实际行为由校验顺序决定,而校验顺序只写在代码里。

八、平台与前提

支持环境:Linux、macOS、Windows WSL2、Docker、Raspberry Pi

运行前提:Node.js 20+,以及 Claude Code。macOS 上可用 Apple Container,其他环境用 Docker;两种方式提供的都是操作系统级容器隔离。

九、成本与许可

这是开源项目,使用者需自行承担 Anthropic API、服务器或本地硬件的运行成本。 不存在免费额度、固定价格或官方代付。

许可方面:官网同时出现 MIT License 与 Apache 2.0 两种表述,具体许可以仓库 LICENSE 文件为准

十、与同类项目的对比维度

与 OpenClaw、PicoClaw、OpenFang 等同类项目比较时,只有四个维度是可核实的,也建议只用这四个:

维度 NanoClaw 的情况
代码规模 1 个进程、约 5 个核心文件、约 500 行核心逻辑
隔离方式 基于 Apple Container / Docker 的操作系统级容器隔离,每个群组独立容器与文件系统
模型支持 基于 Claude Agent SDK
定制方式 直接修改核心代码、按群组配置挂载与 CLAUDE.md

对具体项目的性能与安全性指控不在可核实范围内,本文不做评价。选型时建议按上表逐项核对各项目自己的仓库与文档。

小结

NanoClaw 的隔离模型可以用一句话概括:把权限从应用层挪到操作系统层,把默认值设成拒绝,然后把爆炸半径的单位说清楚。 前两点由容器与挂载白名单实现;第三点是最容易被使用者忽略的一条——同群组内的会话并不互相隔离,不同信任域要放进不同群组。

这套模型不承诺「绝对安全」,也不试图分辨注入指令的真伪。它只做一件事:假设代理会被接管,然后限制接管之后能发生什么。


GitHub 仓库View on GitHub(社区存在多个 fork 与镜像,克隆前请核对官网 Get Started 给出的地址)

Get Started官方文档 · 安装与架构

本文涉及的隔离与权限细节来自 NanoClaw 官方文档与仓库安全文档。实现随版本演进,部署前请以你所用版本的仓库代码与 docs/SECURITY.md 为准。