# 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/articles/ai-security-audit-ground-truth/
- 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-zh.mp3" type="audio/mpeg" />
    您的瀏覽器不支援音訊播放，請點此 <a href="https://audio.paulkuo.tw/ai-security-audit-ground-truth-zh.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 負責讀 code、設計方案、寫交接，還引導瀏覽器裡的 Claude 去點 Cloudflare 後台。Code 負責實作，改完自己 curl、自己跑 audit 驗收，然後才 commit。

還有一條我給自己的硬規矩：不做 big-bang。一批只動一個安全面向，commit 的時候指名要進去的檔案，部署前先確認沒把站弄壞，部署後立刻 curl 看線上實況，每個決策當場寫進紀錄。一次只推一塊，壞了也知道是哪一塊壞的。這聽起來慢，但我後來愈來愈相信，這種慢是一種治理成本。它換來的是：每一步都可以被單獨追溯、單獨驗證，也可以在出錯時被單獨拆解。

## 為什麼「已經 commit」不等於「線上已生效」？

這裡要先交代一個背景，因為它幾乎是後面所有判斷落差的鑰匙。

我這個網站的自動部署，從四月底就壞了。那是[上一次帳號被停權後的那場韌性重建](/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，甚至是我自己，很有把握地說「這個搞定了」，你我還是會回到源頭，自己再檢查一次證據。

我去看，不是因為我天生比較謹慎，是我知道自己其實有多想省事。我們有多希望可以不必親自檢查，大腦多麼希望可以輕鬆。
