从零构建 Coding Agent
English

12. 安全边界与权限模型

Coding Agent 能读写文件、运行命令、访问网络和调用模型。只要它运行在你的机器上,它就拥有进程权限范围内的能力。安全设计的第一步是诚实:不要把 prompt 当权限系统,不要把“模型应该不会这么做”当隔离边界。

威胁模型

至少考虑四类风险:

  • 用户误操作:让 Agent 删除文件、覆盖改动、运行昂贵命令。
  • 模型误判:模型把测试命令写成破坏性命令。
  • Prompt 注入:仓库文本诱导模型泄露环境变量或绕过规则。
  • 工具漏洞:路径逃逸、命令注入、并行写覆盖、日志泄露密钥。

不同风险需要不同边界。Prompt 可以降低误判概率,但不能阻止恶意工具调用。真正的边界在工具执行前。

权限门

工具调用前可以经过权限门:

type PermissionDecision =
  | { type: "allow" }
  | { type: "deny"; reason: string }
  | { type: "confirm"; prompt: string };

read 通常允许,edit 可能根据文件状态允许,bash 根据命令分类确认或拒绝。权限门要在 tool call 进入执行器前运行。如果拒绝,应形成错误 tool result,让模型知道原因。

不要只在 UI 里拦截。SDK 和 JSON 模式也必须走同一权限门。否则用户换一个运行壳就绕过安全策略。

项目信任

第一次进入陌生仓库时,Agent 可以询问用户是否信任该项目。未信任状态下,允许只读工具,限制 bash、写文件和读取敏感路径。信任不是永久真理,应允许用户撤销。

信任策略要具体:

  • 当前工作区是否可信。
  • 是否允许执行项目脚本。
  • 是否允许网络访问。
  • 是否允许读取环境变量。
  • 是否允许写工作区外路径。

把这些都压成一个“允许/拒绝”会让用户无法做细粒度判断。

沙箱与外部边界

如果你需要更强安全性,应该把工具执行放进容器、虚拟机或远程沙箱。这样即使模型请求危险命令,损害也被限制在沙箱内。沙箱不是本书必须实现的第一版,但接口要提前留好:工具执行不应该直接绑死本机文件系统和 shell。

可以把文件操作抽象成 operations:

type FileOperations = {
  readText(path: string): Promise<string>;
  writeText(path: string, content: string): Promise<void>;
  realpath(path: string): Promise<string>;
};

本地、SSH、容器和远程运行时都可以实现同一接口。Agent 内核不需要知道工具在哪里执行。

日志也有安全边界

会话日志会保存用户输入、工具结果、文件片段和命令输出。它可能包含密钥、私有代码和错误堆栈。至少要考虑:

  • 日志存储位置。
  • 是否加密。
  • 导出前是否脱敏。
  • 上传远端前是否询问。
  • UI 是否默认折叠敏感输出。

安全不是只拦命令。一个 Agent 可以不执行危险命令,却把 .env 内容写进日志或模型上下文,这同样是泄露。

练习

实现权限门和项目信任状态。

验收标准:

  • 未信任项目下,写工具和 bash 默认需要确认或拒绝。
  • 权限拒绝会形成 isError: true tool result。
  • UI、CLI JSON 模式和 SDK 都调用同一权限门。
  • 读取疑似敏感文件时有策略:拒绝、确认或脱敏。
  • 工具操作通过接口抽象,未来可替换为容器或远程执行。