AI Agent 编程的四层验证防线
创业者分享在生产环境中安全信任 AI Agent 的核心检查清单,确保自动化代码提交的质量可控。
创业者分享在生产环境中安全信任 AI Agent 的核心检查清单,确保自动化代码提交的质量可控。
我是一个初创公司的独立创始人。为了在合理的时间内推出 TribeROI,我使用 AI agents 来读取代码库、编写代码、运行测试并开启拉取请求。我的工作是审查和合并代码。
让这个过程运作的不是 agents 本身,而是我为了信任它们的输出而构建的一套检查机制。
每个人都担心 AI 会写坏代码。坏代码从来都不是我的问题。问题在于 agents 生成的代码超过了一个人能够正确审查的量,这时缺陷就会显现。几周前,我在一天之内遇到了十二个大缺陷,它们全都通过了我的测试。我只有通过阅读实际运行的系统才找到了它们。
这些失败归纳为三种模式。首先,存在的制品但没有任何东西运行它们。其次,系统无法找到答案而替换了一个看起来合理的默认值。第三,重新构建的工作没有检查已经存在的能解决同一问题的代码。
第二种模式让我吃了两次亏。当 agent 无法确定某些内容时,它会写一个降级方案。虽然这对 agent 来说似乎是合理的,但结果是在生产环境中产生了重复的资源。
以下是我现在建立的四条规则,以提高对 agent 代码的信心。
我保留的每一条安全规则都必须指明当某人破坏它时会失败的具体自动化检查。如果我说不出来,那它就不是一个保证,而是被列在标题为"我必须记住检查的事项"的清单上。
当我用这个标准来审视自己的规则时,我代码审查清单中的十一项中有八项根本没有强制执行任何东西。它们是你勾选但改变不了任何结果的复选框。我标记为"硬性要求"的八条规则中有三条情况相同,还有一批警报自我写下来的那天起就一直没有起作用。我一直在读那个清单并认为一切都很好。
未强制执行的规则比没有规则更糟糕,因为你会依赖它。
这是四条规则中价值最高的,也是最便宜的实施方式。找出你的代码中所有将"我无法确定这一点"转换为值的地方,并让它停止。
它在应用代码中会发生。我的代码遇到了它无法识别的权限角色,因为那感起来像安全的选择而降级到最小权限,并将八个付费客户锁定在自己账户的管理员屏幕之外。没有任何东西抛出异常。降级方案完全按照我告诉它的方式行动。
昂贵的版本出现在创建或更新云资源的 ops 脚本中:
CHANNEL=$(gcloud beta monitoring channels list --format="value(name)" | head -n1)
管道将退出状态交给 head,它很乐意成功,所以 set -e 永远不会触发。瞬时的认证故障变成一个空字符串,空字符串读作"不存在",脚本愉快地创建了一份已经存在的资源的第二份副本。那是 7 月 7 日。我修补了那个脚本,告诉自己我完成了,结果在 7 月 21 日同样的问题在其他地方出现,并孤立了一个十一个告警策略都在指向的通知渠道。
第二次比第一次教会了我更多东西。每个代码路径都依赖的事实应该在一个它们都调用的地方:
must_read() {
if ! MR_OUT=$("$@" 2>"$MR_ERR"); then
echo "!! READ FAILED, refusing to continue: $*" >&2
echo " A failed lookup is not proof the resource is absent." >&2
exit 1
fi
printf '%s\n' "$MR_OUT" | head -n1
}
写作 VAR=$(must_read ...),它会传播失败,赋值失败,set -e 结束运行。只有两种结果存活:真实的值,或一个停止。
存在的文件看起来像它能工作的证明,而几乎没有任何东西检查是否有东西运行它。我以 JSON 形式提交了一个告警策略,在清单中声明了它,而应用脚本从未调用过该策略的 apply_policy。干运行隐藏了整个事情,因为验证器扫描整个目录,而应用者逐个手动命名每个策略,一行一个策略。其中一个看到了该文件,另一个则没有。
所以现在有了一个测试,它在应用脚本中 grep 自己的调用,并在两个方向上检查。每个声明的策略都被应用,每个调用都指向声明的东西。这很丑陋,我为此不骄傲,但这是唯一证明该文件被访问的东西。
另一个断言值得复制:
def test_the_policy_pair_is_not_vacuous() -> None:
assert ALERT_POLICIES, "no declared policies — the guard would be vacuous"
assert _applied_policy_patterns(), "no apply_policy calls — same"
在空集合上迭代的守卫通过查看什么都不做而通过。重命名它读取的常数,两边都变成空的,所以检查保持绿色,同时证明绝对什么都没有。那是同样的失败向上一个级别。如果你写守卫,假设你已经有了这样的一个。
我运行一个模式,其中 agent 无人值守工作数小时,它直接获得 Bash(gcloud:*),因为没有人醒着来回答的权限提示只是一个多步骤的挂起。这只有在真正的边界坐在其他地方才是安全的:一个 gcloud 是默认拒绝的钩子,只允许读取和一组命名的加法命令,拒绝其他所有东西。
配置文件无法完成这项工作。在 Claude Code 中,ask 无论允许规则有多具体都会击败 allow,所以一个坐在广泛 ask(gcloud *) 旁边的窄 allow 是死代码。前缀匹配也很脆弱,gcloud run 后跟两个空格与 Bash(gcloud run *) 不匹配。作为允许事情的一种方式,那是一个漏洞。在默认拒绝的钩子内它是无害的,因为任何无法识别的东西都会被拒绝。
钩子的捕获块也拒绝。我写的所有其他守卫都失败开放,这在几乎所有地方都是正确的选择。这个是唯一站在广泛授予和生产之间的东西,所以崩溃必须拒绝而不是猜测。
采用第二条规则。在你的代码库中搜索当无法确定某些内容时触发的降级方案,并让每一个停止。这很快,不需要新的工具,而且这是一个出现得令人惊讶的失败。
所有这些规则的见解是,系统在显然不是这种情况下自信地报告成功。在 AI 开发中,agents 使这成为更高的优先级,因为他们产生代码的速度超过你能读它的速度,所以这些检查必须扩展。
我很想听听其他人在构建什么来强化他们的 agentic 开发工作流,特别是如果你遇到了一类我没有遇到的失败。请在评论中告诉我。