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 模式和 RPC 模式也必须走同一权限门。否则用户换一个运行壳就绕过安全策略。这是“一份内核,多种运行壳”在安全上的直接推论:权限判断属于内核,界面只负责把确认请求呈现给用户、把用户的选择送回内核。
这里要避免一个常见误解:权限门不是一张写死的“危险命令黑名单”。把 rm -rf 之类的字符串硬编码进内核,既挡不住换一种写法的破坏性命令,也会误伤正常用法。更稳的模型是:内核只提供“工具调用前有一个可返回 allow / deny / confirm 的判定点”这一机制,具体判定规则作为策略注入——它可以来自默认策略、用户配置,或第十四章的扩展钩子。命令分类只是策略的一种实现,而不是内核内置的普适清单。于是安全实际由两层叠加:一层是下面要讲的项目信任边界,决定项目本地的配置、扩展、技能是否加载;另一层是这个逐次调用的判定点,决定每个具体 tool call 能否执行。前者是加载时的一次性决定,后者在每次执行前都跑一遍,两者都不能被某个运行壳绕过。
项目信任
真实系统的第一道闸门不是命令分类,而是项目信任。原因很实际:Agent 会加载项目本地的配置、扩展、自定义 system prompt 和技能文件——这些本质上是会执行的代码。打开一个陌生仓库就无条件加载它们,等于运行来路不明的脚本。
所以第一次进入某个工作区时,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 内容写进日志或模型上下文,这同样是泄露。读取疑似敏感文件(.env、私钥、凭据文件)时应有专门策略:拒绝、确认或脱敏。
练习
实现权限门和项目信任状态。
验收标准:
- 未信任项目下,写工具和 bash 默认需要确认或拒绝,项目扩展和自定义提示词不加载。
- 信任决定按路径持久化,支持“信任父目录覆盖子目录”和“仅本次会话”两种粒度。
- 权限拒绝会形成
isError: truetool result。 - UI、CLI、JSON 模式、RPC 和 SDK 都调用同一权限门。
- 读取疑似敏感文件时有策略:拒绝、确认或脱敏。
- 工具操作通过接口抽象,未来可替换为容器或远程执行。