06-security.md 12 KB

06 安全:隔离、沙箱、文件访问策略、网络与凭证

1. 威胁模型

调用方(自己的上层平台)可信,但 Agent 的行为不完全可信:它会执行命令、读写文件、访问网络,而且可能被网页或文档中的提示注入误导。要防的是 Agent 越界。

风险 例子
R1 读取敏感文件 读 ~/.ssh、CLI 登录凭证、个人文档、其他会话的工作区
R2 越界写入/破坏 删改工作区以外的文件
R3 访问局域网 访问路由器、NAS、内网服务的管理接口
R4 数据外传 把资料内容发到外部网站
R5 资源耗尽 死循环脚本、大文件把机器拖垮

2. 分层防御

L3 应用层     文件访问策略校验(canUseTool)· 审批 · WebFetch 私网拦截
L2 引擎沙箱   Claude Bash 沙箱 · Codex 权限配置(bubblewrap)
L1 systemd    ProtectHome=tmpfs / ProtectSystem=strict / 资源上限
(L0 专用用户:暂不启用,见 §5.3)

3. 引擎自带沙箱具体限制了什么

3.1 Claude Code(@anthropic-ai/sandbox-runtime,Linux 上用 bubblewrap + socat)

方面 默认行为 配置
作用范围 只作用于 Bash 等 shell 命令及其子进程 —
写 工作目录、临时目录、additionalDirectories sandbox.filesystem.allowWrite / denyWrite
读 默认能读机器上大部分路径,包括 ~/.ssh sandbox.filesystem.denyRead / allowRead(范围更窄的规则优先)
网络 没有直连出口,经本机代理按域名白名单放行(默认为空) sandbox.network.allowedDomains / deniedDomains
逃逸口 Claude 可以申请 dangerouslyDisableSandbox 在沙箱外重试 allowUnsandboxedCommands: false
不可用时 默认静默降级为无沙箱运行 failIfUnavailable: true

不在沙箱内:Read/Edit/Write/Grep/Glob/WebFetch 只受权限规则约束(denyRead 拦不住 Read 工具);MCP、hooks 以完整权限运行。

3.2 Codex(bubblewrap;权限配置 permission profiles)

  • 作用于 Codex 拉起的所有命令(Codex 读文件也是通过命令完成的)。
  • 新版的权限配置按路径设置 read / write / deny,范围更窄的路径优先;同一路径上 deny 优先于 write,write 优先于 read。可以从 ":root" = "deny" 开始做"默认拒绝"。
  • 内置路径别名::minimal(运行时必需的系统路径)、:workspace_roots、:tmpdir。
  • 网络默认关闭;开启后可以用代理加域名规则过滤。
  • 注意:使用权限配置时不要再设置 sandbox_mode,两者混用时 Codex 会退回旧的沙箱设置。

3.3 共同缺口

  • Claude 的文件工具不在沙箱内 → 需要 L3 校验。
  • 两者都依赖 bubblewrap 创建 user namespace → systemd 不能开启 RestrictNamespaces。

4. 文件访问策略(读权限可在 Agent 配置中设定)

可以设定,而且两个引擎都有对应的原生机制。读写范围作为 Profile 的一部分,在后台编辑。

4.1 策略模型

interface FileAccessPolicy {
  mode: 'restricted' | 'broad'      // restricted:只能读列出的路径(推荐);broad:全盘可读,只排除 deny
  read:  PathRule[]                 // 额外可读路径:资料库引用 或 绝对路径
  write: PathRule[]                 // 额外可写路径(少用;工作区始终可写)
  deny:  string[]                   // 额外禁止路径/通配,如 "**/*.env"
}
type PathRule = { knowledgeRootId: string } | { path: string }

最终生效的权限由三层合并而成,上层不能被下层放宽:

层 内容 谁来设定
① 全局强制禁止(不可覆盖) ~/.claude/.credentials.json、~/.claude.json、~/.codex/auth.json、~/.ssh、~/.gnupg、/srv/agent-server/data、其他会话的工作区 后台"设置 → 安全",可追加,不能删除内置项
② Profile 策略 上面的 FileAccessPolicy 后台"Profiles"
③ 基线(始终存在) 当前工作区读写;Python 运行环境、技能目录只读;:minimal 系统路径只读 内置

4.2 映射到引擎

合并后的策略 Claude Codex
可读路径 additionalDirectories(资料库)+ sandbox.filesystem.allowRead + canUseTool 对 Read/Grep/Glob 的路径白名单校验 权限配置:":root"="deny"(restricted 模式)、":minimal"="read"、各可读路径 ="read"
可写路径 工作区 + sandbox.filesystem.allowWrite + canUseTool 对 Edit/Write 的校验 ":workspace_roots": "."="write" + 额外可写路径
禁止路径 sandbox.filesystem.denyRead + permissions.deny: Read(...) + canUseTool 各禁止路径 ="deny"
restricted 模式下的 Bash denyRead 覆盖 home 和 /srv/agent-server 等大块目录,再用 allowRead 重新开放工作区和可读路径 由 ":root"="deny" 天然覆盖

canUseTool 中的路径校验是 Claude 侧的权威判断:路径先 realpath 规范化(防止 .. 和软链接绕过),再与合并后的策略比对。命中 deny 时直接拒绝,不在策略范围内的路径按配置拒绝或转人工审批。

4.3 后台体验

  • Profile 编辑页有一个"文件访问"区块:选择模式,从资料库列表勾选可读目录,添加自定义路径和禁止规则。
  • 右侧显示"生效预览":合并三层之后的最终可读、可写、禁止清单。
  • 保存时校验:路径是否存在、服务进程能否看到(见 §5.2 中 ProtectHome 的影响)、是否与全局禁止项冲突。

5. 进程隔离(systemd)

5.1 现阶段:以 jerry-remote 身份运行,用 systemd 加固

CLI 登录态保存在你的 home 目录中,所以服务需要访问 ~/.claude、~/.claude.json、~/.codex 和 CLI 安装目录。做法是先用 ProtectHome=tmpfs 把整个 home 隐藏起来,再只把需要的路径绑定回来:

[Service]
User=jerry-remote
Group=jerry-remote
WorkingDirectory=/srv/agent-server
ExecStart=/usr/bin/node /srv/agent-server/app/apps/server/dist/main.js
Environment=HOME=/home/jerry-remote

# 文件系统:整盘只读,home 不可见,只回绑必要路径
ProtectSystem=strict
ProtectHome=tmpfs
BindPaths=/home/jerry-remote/.claude /home/jerry-remote/.claude.json /home/jerry-remote/.codex
BindReadOnlyPaths=/home/jerry-remote/.local/bin /home/jerry-remote/.local/share/claude
ReadWritePaths=/srv/agent-server
PrivateTmp=yes

# 内核与权限
NoNewPrivileges=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
LockPersonality=yes
# RestrictNamespaces 不能开:bubblewrap 需要 user/mount/net namespace

# 资源上限
MemoryMax=8G
CPUQuota=600%
TasksMax=512

效果:Agent 看不到你的 ~/web、~/.ssh、~/Documents 等目录。CLI 凭证目录虽然对进程可见,但由 §4.1 第①层的全局强制禁止在引擎沙箱和应用层两处拦截。

5.2 对资料库路径的影响

在 home 目录下的资料(例如 ~/Documents/资料)默认对服务不可见。有两种处理方式:

  • 推荐:把资料放到 home 以外(如 /srv/agent-server/knowledge、/data/...)。
  • 或者在 unit 中追加一行 BindReadOnlyPaths=,然后重启服务。后台在保存资料库时会检测路径是否可见,不可见时直接给出需要追加的配置行。

5.3 专用用户(已于 2026-10-23 启用,ADR-0012)

下面是启用前的分析,保留作为背景。当前的部署方式见 10-deployment.md。

同 UID 运行时仍存在的缺口:服务可以向你的其他进程发信号(systemd 249 没有 PrivatePIDs);home 以外属于你的文件按权限仍可访问;加固配置出错时没有兜底。以后需要时,迁移步骤是:创建 agentsvc 用户 → 用该用户重新登录 CLI → 修改 unit 的 User 和绑定路径。服务代码不受影响,因为所有路径都从配置中读取。

6. 网络策略

通道 控制方式
Claude Bash 命令 沙箱代理 + 域名白名单(后台可配,默认只放行 pip 源)
Claude WebFetch 在 canUseTool 中解析域名,拒绝私网、回环和链路本地地址
Claude WebSearch / Codex web_search 由厂商服务端执行,不经过本机网络
Codex 命令 权限配置中 network.enabled=false;需要时走审批

7. 凭证与合规

  • 使用方式:你在终端中自己登录 claude 和 codex,服务直接复用这份登录态,供你自己的平台使用。按 Anthropic 条款的划分,这属于订阅所有者本人的使用;不涉及他人使用你的订阅。
  • 两点约束:
    1. Pro/Max 的额度"假设为普通的个人使用"。服务化之后,常驻和多平台的调用量会和你在终端里的使用共享同一份额度。
    2. 以后如果开放给其他人使用,需要改用 API Key(凭证类型已预留,见 ADR-0008)。
  • 实现要求:
    • 服务进程环境中不设置 ANTHROPIC_API_KEY / OPENAI_API_KEY,避免意外切换到 API 计费。
    • 不复制凭证文件。ChatGPT 的 refresh token 会轮换,复制一份会导致你终端里的登录失效。
    • Codex 使用默认的 ~/.codex,因此会加载你个人 config.toml 中的插件和 MCP(当前有 browser、computer-use、node_repl 等)。app-server 启动时必须用 -c 覆盖并禁用这些项,只启用服务需要的配置。实现时用 --strict-config 加启动自检来验证。
    • Claude 侧通过 settingSources: [] 不加载个人的 settings、插件和 hooks。
  • 后台"引擎状态"页只显示登录状态、账户摘要和额度,不显示 token。
  • 条款参考:Claude Code Legal and compliance,2026-10 核对。本文不是法律意见。

8. M1 实测补充(2026-10-22)

  • Bash 沙箱禁止创建 Unix socket(seccomp:socket(AF_UNIX) = EPERM),LibreOffice 因此无法在 Bash 中运行。
    • 不采用 sandbox.network.allowAllUnixSockets: true:运行账号在 docker、lxd 组中,一旦放开,Agent 就能连接 /var/run/docker.sock,等同获得宿主 root 权限。
    • 改为提供受控工具 convert_document(services/office.ts):服务进程自行用 bubblewrap 启动 LibreOffice,--unshare-all(断网、独立 IPC/PID),白名单挂载(只有 /usr、/etc、字体缓存和当前工作区),/usr、/etc 中零星的 socket 用 /dev/null 覆盖;输入和输出路径都必须在工作区内。
    • 2026-10-23 复查:最初的实现是"整盘只读挂载,再隐藏 /run、home 和数据目录",这样 LXD 的 socket(/var/snap/lxd/common/lxd/unix.socket)在沙箱内仍然可以连通(实测 API 返回 200),所以改成了白名单方式,并加了回归测试(test/office.test.ts)。
  • restricted 模式下 Bash 的读取范围:沙箱只能整块屏蔽目录(home、/srv、/mnt、/media、/root),/tmp、/etc 等系统路径对 Bash 仍然可读。文件工具(Read/Grep/Glob)由 hook 严格限制在白名单内。这是已知、可接受的缺口;systemd 部署时 PrivateTmp=yes 会让 /tmp 成为服务私有。
  • Claude CLI 为每个工作目录分配的临时目录(/tmp/claude-<uid>/<工作目录转义>)已加入可写基线,否则模型会在写临时脚本时被拦截。
  • 建议(未实施):把运行账号移出 docker / lxd 组,或者尽早迁移到专用用户(§5.3)。这两个组本身就是本机上权限最大的入口。

9. 专用用户启用后(2026-10-23)

  • 服务以 agentsvc 运行:不在 docker/lxd/sudo 组;程序、运行环境和技能由 root 所有、服务只读;数据目录 0700
  • 内置禁止项中的 ~ 现在指 /srv/agent-server/home:~/.claude/.credentials.json、~/.claude.json 保护的是服务账号自己的登录态;home 位于数据目录内,同时受"服务数据目录"的软禁止保护(其他会话的 Claude 会话记录也在这里)
  • 官方技能的许可约束与使用方式见 ADR-0013