security-audit-skill 是什麼?拆解 Cloudflare 讓 agent 稽核結果可信的設計
Cloudflare 在 2026 年 6 月發了一篇〈Build your own vulnerability harness〉,介紹他們內部用來大規模找漏洞的 agent 系統,同時把這個系統最早的起點,一個叫 security-audit 的 agent skill,放到 GitHub 上。我在一個 Agent 專案的 backend 和 frontend 各跑了一次。
跑完之後,我最在意的是它交出來的每個結論都附得出證據,找到幾個漏洞反而是其次。讀完原始碼再對照實際跑出來的東西,我發現這個 skill 的設計換掉「安全」兩個字也能用,它的規則都在處理同一件事:讓 agent 的產出可以被檢查、被相信。
讀完這篇,你會理解:
- 讓 agent 做稽核,結果為什麼難以相信
- security-audit 怎麼安裝、怎麼下指令,六個階段各做什麼
- 它背後的五個設計,以及每個設計在實戰裡怎麼發生作用
- 這些設計怎麼搬到 code review、migration 檢查這類其他 agent 工作
- 它為什麼取代不了滲透測試
這篇不是安全檢查清單,實戰結果只用來說明設計。
讓 agent 做稽核,難在哪裡?
讓 agent 找漏洞不難,難的是判斷它說的對不對。常見的情況有三種:agent 自己檢查自己找到的東西,當然每一個都覺得成立;它說「auth 看過了」,你沒辦法知道它實際讀了哪些檔案;它把部署環境的行為當成已知,例如假設 proxy 會擋掉某個 header,結論其實建立在猜測上。
Cloudflare 的文章把第一種講得很直接:
「If a Hunter is allowed to grade its own homework, it will confidently validate everything it outputs.」(Cloudflare,Build your own vulnerability harness)
另一個限制是一次跑不完。security-audit 的 README 寫:
「In our test runs, a single run found roughly half of the vulnerabilities that repeated runs found in total.」(security-audit README)
所以問題變成兩件事:每個結論要能被檢查,而且要能接著跑下一次。這個 skill 的設計幾乎都在回答這兩件事。
security-audit 是什麼,怎麼用?
security-audit 是一組 markdown 寫成的 agent 指令,加上兩支不需要任何套件的 Node 程式,用來檢查輸出格式。它本身不呼叫模型,整個流程靠 coding agent 讀指令照做:主 agent 負責協調和寫檔,其他工作派給 subagent。Cloudflare 後來把它的每個階段拆成獨立的 agent,加上資料庫和排程,變成跑上百個 repo 的系統,這個 repo 是單一 repo 的版本。
六個階段各做一件事。盤點階段由 4 個唯讀 agent 平行讀 repo,整理出產品、身分與權限、輸入點、能在本地跑的測試,切成一份檢查清單。找漏洞階段照清單分工,每一輪找完,另派一個 agent 看有沒有漏掉的單元。之後每個可疑項目交給沒找過它的 agent 試著推翻,結果寫成 findings.json 並用程式檢查格式,再找新的 agent 對照原始碼核對一次,最後才從 JSON 產生報告。
安裝
用 Skills CLI 安裝,加 --global 就是所有專案都能用:
npx skills add https://github.com/cloudflare/security-audit-skill \
--skill security-audit --global
也可以直接把 repo 裡的 skills/security-audit/ 複製到 ~/.claude/skills/。需要的環境有三樣:支援平行 subagent 的 coding agent、Node.js,以及一個能隔離執行的 sandbox。沒有 sandbox 它也能跑,只是不會執行你的程式。
下指令
skill 有兩種模式。一般問安全問題、請它看某段 code,走的是問答模式,不會建檔。要跑完整稽核,必須明確說出來:
/security-audit 對這個 repo 做一次 full security audit,用 quick profile,
budget 最多 13 個 agent,輸出到 ~/security-audit-skill/backend/run-1
quick 是只跑一輪的快速版本,standard 是預設,deep 會切得更細、驗證更多次。budget 是整次最多呼叫幾個 agent。報告預設寫在 repo 外面,跑完會有 REPORT.md、FINDINGS-DETAIL.md、NEEDS-VALIDATION.md,以及 findings.json 和 coverage-ledger.json 兩份給下一次讀的資料。
我測的是一個 Agent 專案:backend 是 TypeScript 服務,裡面有 LLM agent 會呼叫 tool,也接受使用者上傳 Excel 檔;frontend 是 React SPA,用 nginx 容器部署。兩邊都用 quick,各用了十幾個 agent,二、三十分鐘內跑完。backend 那次它自己想到用本機的 Docker 組出隔離環境;frontend 我在 prompt 裡要求它用 sandbox 實際測,它也是用 Docker 做的。
security-audit 怎麼定義什麼才算一個漏洞?
security-audit 規定,一個可疑項目要說出五件事才算漏洞:信任度較低的是誰、它送進了什麼、哪個檢查應該擋下它、越過了哪個邊界、對誰造成什麼具體結果。說不出來就不算:
「A candidate without a concrete affected principal, resource, or security outcome is not a confirmed finding.」(security-audit SKILL.md)
這條規則延伸出幾個限制。缺少某個最佳實踐,但沒有人會受害,只能算建議加強。A 層已經擋住攻擊,B 層沒做只是多一層防護的缺口,不是漏洞。嚴重度要依實際證明的影響來定,crash 不能被說成程式執行,自己影響自己不能被說成權限提升。
實戰裡,這條規則讓報告變得很克制。backend 確認的 3 筆都是可用性和成本問題:上傳 657 bytes 的 Excel 檔讓整個 Node process 卡住 6.7 秒;33KB 的壓縮檔解開後佔用 128MB 記憶體;使用者在 LLM 回應途中斷線,這一輪記成 0 token,可以一直花營運方的錢卻碰不到 budget 上限。報告直接寫身分驗證和帳號隔離守得住,沒有為了湊數去灌水。frontend 沒設 CSP、沒擋 iframe 嵌入,這些都放在建議加強,跟漏洞分開。
frontend 的 GET /.env 回了 200,掃描工具看到很容易當成洩漏,但內容其實是 SPA fallback 回的 index.html。skill 對這種情況的寫法是:
「A flag is not a finding — trace the impact before reporting.」(security-audit ATTACK-CLASSES.md)
換到 code review 也一樣,先規定產出的最低格式。叫 agent review code 時,可以要求每個意見都寫出「什麼輸入會造成什麼錯誤」,寫不出來的就歸到風格建議。門檻先講清楚,後面的驗證才有東西可以驗。
為什麼找到漏洞的 agent 不能自己驗收?
security-audit 規定,每個可疑項目都要交給一個沒參與找它的 agent,任務是推翻它,因為自己找到的東西自己驗,每一個都會被判成立。驗證 agent 的 prompt 開頭是:
「You did not write this candidate. Try to refute it from repository source and bounded local evidence.」(security-audit VALIDATION-AND-REPORTING.md)
它要重讀每一個被引用的原始碼位置,能安全重現的檢查要自己再跑一次,也拿不到其他驗證 agent 的結論。到了第五階段,如果新的 agent 想實質改寫一筆結果,例如把待驗證升成確認、改掉 root cause 或嚴重度,還要再找第三個 agent 確認,改寫才算數。
frontend 的 open redirect 是這樣被確認的。網站有三個回首頁的按鈕,直接拿 location.pathname 當連結。找到它的 agent 在 sandbox 裡起了 repo 的 nginx 設定,用 jsdom 載入打包好的 JS,打開 //evil.invalid/ 後讀出連結會導到 http://evil.invalid/。驗證 agent 沒有拿它的腳本來用,自己重寫一份,多加了正常網址當對照組,還發現不帶 ?session 參數也能觸發,而且全程沒有點下連結,只算出連結會導去哪裡。
同一次 run 也有一筆被推翻。nginx 會把使用者自己帶的身分 header 原樣轉給後端,這件事在 sandbox 裡有重現,但驗證 agent 去讀了 backend 的原始碼,發現後端只讀其中一個 header,而且會驗 JWT 簽章,偽造的 token 沒有用,這筆就列為 rejected。如果讓找到的 agent 自己判斷,這筆很可能會變成一個嚴重度不低的漏洞。
自己設計 agent 工作流程時,可以開一個專門負責推翻的 reviewer subagent,給它產出和原始資料,不給產出者的推理過程,要它回成立、推翻或缺資料這類結構化的判斷。
needs_validation 是什麼?為什麼「缺什麼才能下結論」也算結論?
security-audit 的結果只有三種:confirmed 有完整的 source trace 和本地實際觀察到的結果;needs_validation 在 source 上站得住,但缺一個 repo 外的關鍵事實;rejected 是被推翻的。最特別的是第二種,它沒有嚴重度,也不能拿來放猜測:
「
needs_validationis never a parking place for a speculative idea.」(security-audit VALIDATION-AND-REPORTING.md)
每一筆 needs_validation 都要寫出卡在哪個事實,以及兩種解法:能在本地補做的檢查,和要請負責部署的人去看的設定。被推翻的 rejected 也會留下來,下一次 run 才不會重複提出同一個錯誤主張。
實戰裡最完整的例子是每日上限。backend 依使用者 IP 限制每天能建立幾個 session,IP 取自 X-Forwarded-For 的第一個值。backend 的 run 只能列為待驗證,因為前面的 nginx 怎麼處理這個 header 不在 repo 裡。frontend 的 run 在 sandbox 裡證實 nginx 會保留使用者自己帶的值,驗證 agent 又去讀了 backend,確認兩邊程式碼合起來這條路是通的。最後剩下的 blocker 很具體:前面的 ingress 會不會先清掉使用者帶的值、有沒有辦法繞過 ingress 直接連到 nginx、部署中的 backend 是不是同一個版本。
比起一句「可能有 IP 偽造風險」,這三個問題好處理得多,負責部署的人看一眼設定就能回答。讓 agent 檢查 migration 時,也可以把「要看 production 的設定或資料才知道」列成獨立的一類,每一筆寫出要看什麼、去哪裡看,交給人確認。
security-audit 怎麼讓證據能被機器檢查、被人重現?
security-audit 有兩層證據規則:格式由程式檢查,內容要能在隔離環境裡重現。
格式這一層靠兩份 JSON 和兩支 validator。coverage-ledger.json 是檢查清單,每個單元由「入口 × 邊界 × 子系統 × 攻擊類型」組成,有固定的狀態,每個狀態該有哪些欄位都規定好了。標成「看過了」的單元,一定要列出讀過的路徑和做過的檢查:
「The ledger is the coverage claim. An architecture summary, agent count, or generic “auth reviewed” sentence is not coverage evidence.」(security-audit RECONNAISSANCE.md)
findings.json 的三種結論各有不能混用的欄位,例如 needs_validation 不准出現嚴重度。frontend 的 run 第一版就被擋下:它把三個導向點都標成 sink,但 schema 規定只有最後一個能是 sink,validator 報錯,主 agent 修正後才寫報告。skill 也講清楚這層檢查的範圍:「Validator success proves format and ledger consistency only.」格式對了,內容還是要靠驗證 agent。
內容這一層靠 sandbox。要執行 repo 的程式,必須在 OS 層級隔離的環境裡:不能連外網、環境變數只放白名單、程式碼唯讀、只能寫自己的暫存目錄、CPU 記憶體行程數和時間都有上限。少一項就不執行,這筆結果只能停在待驗證。兩次實戰它都用 Docker 做到了,參數類似 --network none --read-only --memory 512m --cpus 1 --pids-limit 64 --cap-drop ALL。
每份證據檔都寫了完整的重現指令、sandbox 參數、測試腳本和輸出,也標明證據的等級。只用 grep 掃過 bundle 的,寫 no target code executed;在 host 上算一行運算式的,寫 not backend code execution。skill 還規定證據檔要由主 agent 用一套防 symlink、防 hard link、限制大小的程序搬到正式目錄,規格寫得夠細,主 agent 就照著實作成約 50 行 Python。
有沒有 sandbox,決定了結論有多強。backend 確認的 3 筆都是在容器裡跑出數字;frontend 也是因為能實際起 nginx,open redirect 才能從推論變成確認,19 種 markdown XSS payload 全被擋下也才有實際證據。其他 agent 工作也能照做:輸出一律走 JSON schema 加 validator,每個結論附上能重跑的指令,不靠模型自己記得格式。
security-audit 怎麼讓一次跑不完的稽核接著跑?
security-audit 假設一次跑不完,所以每次 run 都把進度寫成檔案,讓下一次讀進來接著做。SKILL.md 寫:
「No one pass is complete.」(security-audit SKILL.md)
新的 run 會先讀前一次的 coverage-ledger.json 和 findings.json:上次列為待驗證、被延後、卡住的單元,這次變成要做的工作;source 沒變的確認結果帶過來但要重新驗證,source 變了就重新查;被推翻的結果只擋住同一個主張,不會讓整個單元被跳過。同一個 root cause 不管在哪一次、哪個狀態,都用同一個 fingerprint,才對得起來。
不同的 profile 只改變範圍,不降低門檻:「Profiles change breadth and redundancy, never the evidence bar.」quick 跑一輪就停,但會把沒做到的單元明確標成延後。backend 那次延後了 5 個單元,其中包括使用者上傳的資料進到模型 context 後的間接 prompt injection,報告直接寫這次沒有完整覆蓋,建議下一次用 standard 從這些單元接著跑。
Cloudflare 的文章提到,單一 session 的 skill 會用完 context、忘記自己前面的發現,crash 之後也得重跑。他們的解法是把狀態搬出模型的 context,存進資料庫,讓 agent 當成沒有記憶的計算單元。security-audit 用兩份 JSON 做了同一件事的單機版。任何一個 session 做不完的 agent 任務,都可以把「做到哪、哪些還沒做、為什麼沒做」寫成檔案,下一個 session 讀進來接著做,而且不准把沒做的說成做完。
security-audit 為什麼取代不了滲透測試?
security-audit 取代不了滲透測試,因為它只讀原始碼、只在本地 sandbox 執行,滲透測試打的是實際部署的系統。SKILL.md 第一段就寫:「This is a defensive, source-first workflow.」它從原始碼出發追出程式路徑,只在本地 sandbox 跑,做到能確認的最小結果就停,例如一個錯的回傳值、一筆不該讀到的假資料。部署環境的事實,像 proxy、身分供應商、IAM,它不假設有也不假設沒有,一律交給人確認。
| 問題 | security-audit | 滲透測試 |
|---|---|---|
| 從哪裡開始看 | 讀原始碼,追程式路徑 | 打實際部署的系統 |
| 能碰什麼環境 | 只在本地 sandbox,禁止碰部署中的服務 | 就是要碰部署中的服務 |
| 做到哪裡停 | 能確認的最小結果就停 | 串成完整攻擊鏈,證明能造成多大傷害 |
| 部署設定怎麼處理 | 不假設,列為待驗證交給人 | 直接觀察實際部署 |
| 交出什麼 | 修原始碼用的證據和最小修法 | 實際能被利用到什麼程度 |
每日上限那筆剛好落在兩者的分界上。
兩個 repo 的程式碼合起來,這條路是通的,skill 能做的到這裡為止。前面的 ingress 會不會清掉使用者帶的值,只有對著部署環境測才知道,這是滲透測試的工作。Cloudflare 自己的系統也是這樣分工,source 階段找到的結果,之後還要交給另一個步驟判斷在 production 上能不能被利用,文章的說法是分出「exploitable now」和「real but latent」。
另一個盲點是跨 repo。單一 repo 的 skill 看不到另一個 repo 怎麼使用它,這次每日上限能接起來,是因為驗證 agent 自己去讀了 backend,skill 沒有規定要這樣做。Cloudflare 後來在系統裡加了一個 Tracer agent,專門查不同元件怎麼串在一起,再到使用端的 repo 開新的檢查。
所以 security-audit 適合在程式碼層面先把問題清一輪,交出能直接拿去修的證據,以及一份要請人確認的部署問題清單。它和滲透測試是前後兩段。