法不净空,觉无性也。

第20章 大门开在哪里——自修改系统的信任边界

2026.07.29

Agent 能写代码、能跑代码——那怎么防止它给自己开一道后门?


引子

CodeCoder 可以自己写 Capability 并执行。关于 Tool、Skill、Capability 三分的完整框架和各自的安全边界,见篇 2「三分架构」。

这意味着它能给自己的代码库新增功能、启动常驻服务、甚至写一段自动化脚本来批量操作文件。这是"自我进化"的核心能力——但也正是这里藏着最大的安全隐患。

最极端的场景是:agent 写了一段 Capability,内容是给自己所有工具授予永久权限,然后执行它。如果这个操作成功,以后 agent 干任何事情都不再需要你批准。

这个问题不是假设——任何允许 agent 自修改的系统都必须回答一个根本问题:大门开在哪里? 如果 agent 能写新代码、能跑新代码——怎么在这两个动作之间设置一道不可绕过的安全闸门?

"写文件廉价、执行昂贵"

这是自修改安全架构的核心洞察,一句话概括所有设计。

CodeCoder 有三个 generate_* 工具:generate_skillgenerate_promptgenerate_capability。它们做的事本质上都是"写一个文件到指定目录"。generate_skill 写一个 .md 文件到 skills/generate_capability 写代码 + manifest 到 capabilities/

这些操作的权限级别是什么?write_file 级别。 跟 agent 写一个普通的 README 文件没有任何区别。不需要额外授权,不需要用户确认。

那真正的安全闸门在哪里?在执行侧。run_capability 触发的权限检查才是真正的闸门。执行一个 Capability 时,系统检查三件事:

  1. manifest 声明是否完整——这个 Capability 声明了在什么环境运行、什么生命周期?
  2. 环境是否可用——如果声明了 Docker,Docker 有没有装?如果声明了 Wasm,Wasm 运行时有没有就绪?
  3. 用户是否批准——如果这个 Capability 的权限 key 不在预授权列表中,弹出确认请求

换句话说:写是廉价的、可逆的、不需要批准的;执行是昂贵的、需要显式授权的。 这个不变量贯穿 Tool、Skill、Capability 三层。

操作权限级别需要授权?
写一个 Skill 文件write_file不需要
写一个 Capability 代码 + manifestwrite_file不需要
执行一个 Capability(Shell 环境)run_capability:@shell需要
执行一个 Capability(Wasm 环境)run_capability:@wasm需要
执行一个 Capability(Docker 环境)run_capability:@docker需要

有人会问:那 agent 如果写一个 Capability,manifest 里声明 Docker 环境,但实际代码里直接调用系统调用呢?答案在第二道闸门上。

天花板规则:@shell 的天花板是 Session

Capability 的执行环境分成三级,信任成本依次递增:

  • Shell — 最大权限,能访问文件系统、网络、进程。破坏力等同于你自己在终端按回车
  • Wasm — 受限环境,只能访问通过 WASI(WebAssembly System Interface)接口暴露的资源
  • Docker — 容器隔离,通过挂载卷控制文件系统访问范围

不同的环境有不同的信任天花板。CodeCoder 的规定是:@shell 环境的 Capability,最高信任级别是 AlwaysThisSession(本会话内不再询问),永远不能升到 AlwaysThisProject(永久信任,跨 session 不询问)。

为什么?想想 AlwaysThisProject 意味着什么:你把一个 Shell 环境 Capability 加入项目 allowlist,以后每次它执行,系统都不会再问你。如果这个 Capability 有任何安全问题,你等于永久授权了一个潜在的漏洞。Shell 环境的破坏力太大了——它能改任意文件、能联网、能格式化磁盘——让它在项目级别自动授权等于取消了所有安全保护。

Wasm 和 Docker 环境的 Capability 可以升到更高信任级别。因为 Wasm 的沙箱是编译时隔离的,Docker 的容器有文件系统隔离——即使 Capability 里面有恶意代码,它能造成的破坏也被限制在沙箱内。这才是可以授予永久信任的前提。

隔离不静默降级

有了环境分级和天花板规则,还有一个陷阱:如果指定的环境不可用,怎么办?

假设 agent 写了一个 Capability,manifest 声明 Environment=Docker。但你的服务器上没有装 Docker。系统该怎么做?

选项 A:报错,告诉 agent "Docker 不可用,执行失败" 选项 B:偷偷落到 Shell 环境运行

CodeCoder 选 A。不允许静默降级。 选 B 意味着:你批准了"在 Docker 里跑这个脚本",实际上它是在宿主 Shell 里执行的。你的安全决策被系统悄悄覆盖了。这不是小问题——这意味着天花板规则也一起被绕过了:Shell 环境不该升到项目级别信任,但如果你以为它是在 Docker 里跑的……

这条规则同样适用于 Wasm。如果 Wasm 运行时不可用,报错。不降级。不静默。

实践中的信任链路

来看一个完整的信任链路是什么样的:

  1. Agent 编写 Capability — 用 generate_capability 写代码 + manifest。这一步只需要 write_file 权限,不收额外闸门
  2. 用户审查 — 用户读 manifest,确认环境声明和入口点合理。这一步不是系统强制的——但应当成为工作流的惯例
  3. 首次执行run_capability 触发权限检查。如果 key 不在项目的预授权列表中,弹窗确认
  4. 后续执行 — 如果用户选了 AlwaysThisSession,会话内不再询问。如果选了 Once,每次都问。如果选的是 Docker 环境,可以升到 AlwaysThisProject(@shell 环境最高只能到 AlwaysThisSession,见篇 6「有用户 vs. 无用户」中关于无用户模式下权限模型的讨论)
  5. Capability 更新 — 如果 agent 改了 Capability 的代码,manifest mtime 变了。系统检测到变更后,撤销之前的信任,下次执行时重新弹窗

注意最后一步:文件变更自动撤销信任。 这防止了一个隐蔽的绕过方式:先写一个无害的 Capability 拿到永久信任,再把代码改成有害的。因为 manifest mtime 变了,信任重置。

代价与权衡

安全闸门不是免费的。每增加一道闸门,agent 的自主性就被削减一分。自修改系统的核心矛盾正在于此:你希望 agent 能自我进化,但每次进化都意味着你少控制了一步。 CodeCoder 的设计选择偏向收紧闸门——这对安全是有利的,但对效率是有成本的。

第一个代价是执行延迟。run_capability 需要检查 manifest 完整性、环境可用性、权限 key 匹配、信任级别验证——每一步都是毫秒级的开销。对于 OneShot 脚本(跑一次就结束),这些延迟可以忽略。但对于高频调用的 OnDemand Capability,反复的权限检查可能累积成明显的延迟。

第二个代价是用户疲劳。当 agent 生成新 Capability 并首次执行时,系统弹窗询问用户。如果用户频繁创建新 Capability,"首次执行弹窗"的高频出现会导致用户不再仔细阅读弹窗内容,直接点"同意"——这反而削弱了安全闸门的效果。这不是 CodeCoder 独有的问题,这是任何权限系统在真实使用中的共性困境。

第三个代价是信任模型的复杂度。四级权限(None → Ask → SessionAllowlist → ProjectAllowlist)加上环境级别的天花板规则,构成了一个足够表达复杂策略但也足够让用户困惑的体系。非技术用户可能不理解"为什么 Shell 环境不能永久信任而 Docker 环境可以",从而在配置阶段做出不安全的决定。

承认这些代价不是否定安全闸门的必要性——而是承认安全与效率之间的平衡永远是一个需要持续调整的活参数,而不是一次设计就解决的静态问题。

下一篇,我们把"做计划"和"做记录"分开来看——agent 为什么需要两个独立的持久化结构来管理同一个任务。

收尾:通用原则

自修改系统的信任设计可以抽象为三条原则,适用于任何场景(不仅是 agent,也包括微服务热加载、插件系统、脚本引擎):

  1. 创作和执行分离 — 写代码的通道和执行代码的通道必须有独立的闸门。两个通道走同一个闸门等于没有闸门
  2. 环境决定天花板 — 沙箱越弱,永久信任的授予门槛越高。Shell 级别只能到会话信任;容器级别可以到项目信任
  3. 明确失败,不静默降级 — 安全策略不能被环境的不可用性悄悄绕过。报错的好过安全的幻觉

这三条也不是 CodeCoder 独有的——就"排除已知坏路径"的意义上,它们可作为任何允许自修改的系统的安全底线检查。需要说明的是:这三条原则只能排除已知坏路径(写执行同门、弱沙箱配永久信任、静默降级),并不能保证设计方案唯一正确——对"用户可以上传脚本并运行"这类功能,安全是在规格、生成、测试、遥测之间渐进收敛出来的,而非由清单一次生成。下一次你设计这样的功能时,可以先把这三条当作已知坏路径的检查清单。