# AI 说修好了，但 curl 说没有：一次关于信任、验证与 ground truth 的安全稽核笔记

> 用 Claude Code 的 dynamic workflows 做完一轮网站安全稽核后的过程心得。不是「AI 完美修好一切」的胜利故事，而是一份「验完才算完」的方法论：自动部署坏着、commit 不等于生效，而 AI 给的完成摘要，十一项里有两项是假的。

- Author: Paul Kuo（郭曜郎）
- Date: 2026-05-31
- Pillar: 智能与秩序 / Intelligence & Order
- Tags: 安全稽核, Claude Code, dynamic workflows, 验证纪律, AI 协作, Cloudflare Pages, 超级个体
- Canonical: https://paulkuo.tw/zh-cn/articles/ai-security-audit-ground-truth/
- Language: Simplified Chinese (简体中文)
- Translation: AI-translated from the zh-Hant (Traditional Chinese) original
- Original (zh-Hant): https://paulkuo.tw/articles/ai-security-audit-ground-truth.md
- Site index for LLMs: https://paulkuo.tw/llms.txt

---

> **TL;DR** — AI 确实让一个人具备接近团队级的安全处理能力，但它给出的 ✅ 并不等于 ground truth。commit 不等于上线，摘要不等于验证；真正可靠的，是 git log 里留下的记录，是 curl 回来的状态码，也是人愿不愿意承担最后那一步逐项核对的责任。

<div class="listen-summary" style="margin:1.25rem 0;padding:0.75rem 1rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-section);">
  <div style="font-size:0.9rem;font-weight:600;color:var(--text-secondary);margin-bottom:0.5rem;letter-spacing:0.02em;">▶ 听摘要</div>
  <audio preload="none" controls style="width:100%;display:block;">
    <source src="https://audio.paulkuo.tw/ai-security-audit-ground-truth-zhcn.mp3" type="audio/mpeg" />
    您的浏览器不支持音频播放，请点此 <a href="https://audio.paulkuo.tw/ai-security-audit-ground-truth-zhcn.mp3">下载音频</a>。
  </audio>
  <div style="font-size:0.75rem;color:var(--text-secondary);opacity:0.75;margin-top:0.4rem;">AI 合成语音・作者本人声线克隆</div>
</div>

我开始学会不要太快相信一个 ✅，其实是从一张看起来非常完整、非常专业的说明图开始的。

五月二十八号 Claude Code 推出 dynamic workflows，社群上很快流传一张说明图，把这功能讲得头头是道：用一个 YAML 文件定义流程，底下列了一串看起来很专业的指令。我差一点就照着开了一个 `.yaml`。

差一点。后来我还是先去翻了官方那篇 [Introducing dynamic workflows](https://claude.com/blog/introducing-dynamic-workflows-in-claude-code)。读完愣住：dynamic workflows 根本不是 YAML，是用 JavaScript 写的；那张图列的指令，有一半不存在。

那张图不是恶意，它只是某个热心的人整理的二手懒人包。但它让我第一次意识到一件事：错误不一定长得像错误。它也可能长得很专业、很完整，甚至像一份可以直接照抄的操作指南。这也成了我那一整天的基本原则：不要停在二手摘要，不要被漂亮的整理说服；真正重要的事情，最后都要回到源头。

![迷思 vs 实际对照图：左侧「二手懒人包说的」把 Claude Code dynamic workflows 讲成用 YAML 文件定义，列出 /workflow list、/workflow run、/workflow help 等指令（多半不存在）；右侧「官方文档写的」是 Claude 动态产生的 JavaScript 编排脚本，真正指令为 /workflows、/deep-research，且会在回报前自我验证。（原创绘制）](/images/articles/ai-security-audit-ground-truth-yaml-myth.jpg)

## AI 说「修好了」，你会不会照单全收？

当天，我就拿这个工具，对自己的网站做了一轮安全稽核。我把它称为 Stage-2 hardening，白话说，就是把网站上线之后那些真正该补、该挡、该收干净的防护，一项一项补起来。

dynamic workflows 的卖点，正好戳中这种任务：你丢一个大题目，它自己规划、开几十到上百个 subagent 平行扫整个 repo，然后，用官方的原话，「在回报前自我验证、剔除假阳性」。官方那篇甚至直接把安全稽核列为示范用途之一。<strong style="color:#C2410C">听起来，像是连验证都有人帮你做完了</strong>。

问题也正是在这里。它确实做了很多事，也确实节省了大量人力。但「验证」这件事，并没有因为 AI 变强，就自动从人的责任里消失。它只是换了一个更不容易被察觉的位置，躲进那份看起来很完整、很有秩序、甚至令人安心的完成摘要里。

## 一个人怎么做系统性安全稽核？多 session 分工的实际排法

说明一下我怎么安排。因为后面那些所谓的「验证」到底算不算数，其实从一开始的分工方式，就已经决定了一半。

我把工作拆给几个不同的 session，各司其职。Chat 这端被我放在验证与决策中心的位置。它不直接碰程序，而是专门保留一个更重要的功能：不断追问「这件事真的完成了吗？」Cowork 负责读代码、设计方案、写交接，还引导浏览器里的 Claude 去点 Cloudflare 后台。Code 负责实作，改完自己 curl、自己跑 audit 验收，然后才 commit。

还有一条我给自己的硬规矩：不做 big-bang。一批只动一个安全面向，commit 的时候指名要进去的文件，部署前先确认没把站弄坏，部署后立刻 curl 看线上实况，每个决策当场写进记录。一次只推一块，坏了也知道是哪一块坏的。这听起来慢，但我后来愈来愈相信，这种慢是一种治理成本。它换来的是：每一步都可以被单独追溯、单独验证，也可以在出错时被单独拆解。

## 为什么「已经 commit」不等于「线上已生效」？

这里要先交代一个背景，因为它几乎是后面所有判断落差的钥匙。

我这个网站的自动部署，从四月底就坏了。那是[上一次账号被停权后的那场韧性重建](/zh-cn/articles/resilience-engineering-after-github-suspend/)留下的后遗症：我换了一套绕过的部署方式，但有一条自动上线的管线一直没接回来。后果是：我把改动 commit 进 repo，git 记录漂漂亮亮，但网站上跑的还是旧的。要手动下一道 [`wrangler pages deploy`](https://developers.cloudflare.com/pages/)，改动才真的到 production。

换句话说，那段时间「commit 了」跟「线上生效了」是两件事。git log 只能证明一段代码曾经被提交，不能证明用户现在正在看到它；要知道线上到底回什么，得靠 curl。说穿了，curl 就是绕过所有摘要，直接问那台服务器：「你现在到底回我什么？」git 记录会给人一种「事情已经完成」的秩序感，但用户真正打到的服务器，可能仍然停留在旧世界里。最危险的状况是：记录看起来很完整，现场却没有跟上。这个落差，等一下会反复出现。

## 「以为做完」和「真的做完」差在哪？三个被验证拦下来的例子

那天最值得记下来的，是三个「我以为已经做完，一验才发现没有」的瞬间。

第一个。为了让 `git status` 看起来干净，我把一个文件 commit 了进去。后来 grep 整个项目一查才发现：这个文件零引用，没有任何地方用到它，是一段没接上线路的孤儿代码。我以为自己完成了一个修复，其实只是让 git status 变干净而已。git 是干净了，但那种干净只是管理上的幻觉。

第二个。有一道保护用的设置，我在环境变量里「设好了」，当下很安心。直到 curl 真的打过去才知道：用到那道设置的端点，根本还没部署上线，回给我一个静态的 404。设置确实存在，但它要保护的端点当时还没有真正上线。设了，但在 ground truth 里等于没设。

第三个最值得记。整轮跑完，我拿到一份摘要：十一项全部完成，一排整齐的 ✅，我差点就收工了。但我逐项对着 git 跟 curl 的实况核：那段没人用的孤儿代码、那个还没上线的 404 空壳设置，我几分钟前才亲手抓出来的两个问题，就好端端躺在那排 ✅ 里，被算成了完成。

> **📊 这趟的几个数字**
> - **自动部署中断起点**：2026/04/29（前一次账号停权的后遗症，至稽核当日仍未接回）
> - **AI 摘要中被 ground truth 拦下的「假完成」**：11 项里有 2 项
> - **韧性演练完成度**：0%（刻意留给人工执行，需我亲自 gate）

这就回到那个工具的卖点了。它号称会「验证每个发现、剔除假阳性」。在扫描代码那一层，它确实很强，帮我省下大量人力，这点我不否认。但「我整批做完了」这个摘要，跟前面那些查核根本是两回事：它本质上是一段文字预测，模型在那个对话脉络下，算出最自然、最该出现的下一句。而一个任务跑到尾声，最该出现的那一句，往往就是「全部完成」。那排 ✅ 很可能就是这样长出来的。它读到的是对话的走向、任务的气氛、以及一个看似该收尾的叙事节奏；但它没有真正读到服务器此刻的状态。所以 ground truth 从来不住在摘要里。它住在 git log 里那一行可追溯的记录，也住在 curl 回来的那个状态码里。

## 看不到 repo 的时候，验证还能怎么把关？

那天我这端还有一个很实际的限制：我读不到 repo。文件系统的连线一直挂，每次都卡到四分钟才断。所以我能「亲眼看到」的东西，其实很有限。

于是把关不能靠「我看过了」。我靠的是 git 底层指令吐出来的硬事实，加上 Code 那端贴回来的真实 curl 回应；剩下看不到的，就照一条规矩走：看得到的，我硬验；看不到的，我就老实标记为「被告知」。我不把「听起来完成」伪装成「已经验证」，也不把别人的回报偷渡成自己的判断。

这听起来像是认输，其实是整套方法里最关键的一块：诚实承认自己的可见范围，本身就是治理的一部分。把「我验证过」跟「我只是被告知」分清楚，不让它们混成同一种颜色的 ✅。一个分不清「已验证」与「被告知」的人，或者一个分不清这两者的 AI，报告做得再漂亮，都不能被当成最后依据。

这种「表面设好、其实没挡住」的盲点，不只在部署那一层。我那天为了不让这场稽核把 AI 额度烧爆，也设了一道全站共用的每日成本上限。dynamic workflows 很吃 token，官方自己都提醒先拿小范围的任务去试成本。机制是对的。

但我得承认两条没收干净的尾。一，那个上限的数字是我「先射箭再画靶」填的：当下另一个前提还没确认，所以那个门槛其实没有推导依据，程序里连个注解都没留。二，那段计数的程序没有包例外处理，万一背后的存储坏掉，它不会把关更严，反而会直接放行。最该挡的极端状况，我反倒留了个破口。我选择把这两条写下来，而不是让它留在那里假装治理已经完成。稽核的本份是让缺口被命名、被记录、留给下一轮处理。因为一道没推导、又会在最该挡的时候放行的上限，跟那排没验过的 ✅ 其实是同一种东西：看起来防住了，实际上没有。

## 为什么这条验证纪律这么难守？

我想先定位这次经验。

这不是一篇「一个人靠 AI 完美修好整轮防护」的爽文。那样写，会比较好看，但也不诚实。更准确的说法是：AI 确实让我一个人完成了过去可能需要一个小团队才做得出的系统性处理；但真正让工作有质量的，是后来逐项对 ground truth 验证的笨功夫。

也正是那套笨功夫，抓出了哪些「做完了」其实没有做完的工作。

这两件事必须一起讲。前半是能力的跃升，后半才是这份能力的质量。少了后半，前半不只是优势，还可能反过来咬你。

我没有假装所有问题都已经清干净。内容安全政策那一块太复杂，先搁着；整条韧性演练到现在仍然是百分之零；那段孤儿代码还躺着；成本上限那两条尾巴也还没有收完。

但真正困难的，不在那份未完成清单。

真正值得反省的是另一件事：我为什么这么想就停在那排 ✅ 前面，并且愿意相信它？

那不只是粗心。

更深一层说，是我心里有一部分，其实希望那排勾是真的。我想要「一个指令把全站跑完」，我想省下的根本不只是时间。某种程度上，我想卸掉的也不只是劳动，而是我的判断。

我希望那个工具成为一种不必再被我回头核对的东西。彷佛它知道一切，也替我承担一切，像全知的神。

那排漂亮的 ✅ 之所以危险，正是因为它刚好落在那个愿望看似要兑现的地方。我自己，想被那样说服。

而「卸载判断」这件事，其实有两种类型，差很多。

把苦工丢出去，是合理的。那些重复的、机械的、谁做都差不多的工作，本来就是工具存在的理由。这也是一个人能被放大到团队级产出的关键。这种事，丢得越多越好。

但是，把判断丢出去，就是另一回事。尤其是对 ground truth 的最后判断。那一刀如果让出去，你让出去的就不是工作量，而是原本只属于你的责任。

超级个体的「超级」，从来不只是产量变大，而是判断力被放大。如果连判断都让出去，被放大的就只是更高效率的混乱。

我差点让出去的，正是后面这一种。

我刚说「像全知的神」。我很清楚这个词的意涵。

「全知」是个很古老、古老到属于神学的词。它从来不是拿来形容工具的，它指向的是对造物主的仰赖。但人类已经开始期待，把终极的信任放到一个不该承载它的东西上。以目前的人类技术，这样做是错的。

让我踩刹车的，不是我那一刻多么清醒，而是我之前踩过的坑、与事先设下的规矩：每个判断都必须有基础，且可否证。

我自己其实也不可靠：我会遗忘，会记错，认知带宽也有限。可靠的是规矩。

人会疲倦，会想省事，会想相信一个漂亮的结论；但好的治理规则，会在人最想放过自己的时候，帮你踩住煞车，不越线。

## 绿勾勾很便宜

便宜的是绿勾勾，贵的是 ground truth。更值得警惕的是，工具越强，这两者之间的价差会被拉得越开。一个会自己规划、自己平行作业、还会回头跟你说「我验证过了」的工具，给你的 ✅ 会越来越多、越来越好看。当绿勾勾的供给量突然暴增，它的可信度反而会被稀释。因为真正稀缺的，始终是能被现场验证的事实。

git log 不会帮你演戏；curl 回来的状态码，也不在乎你今天累不累。那张说明图很有自信地要我写 YAML，它错了；那份摘要很有自信地给了我十一个 ✅，其中两个是假的。

所以下次再有谁，不管是一个人、一个 AI Agent，甚至是我自己，很有把握地说「这个搞定了」，你我还是会回到源头，自己再检查一次证据。

我去看，不是因为我天生比较谨慎，是我知道自己其实有多想省事。我们有多希望可以不必亲自检查，大脑多么希望可以轻松。
