Day 16,我們把 AI 稽核拆成 Recon、Hunt、Validate、Report、Structured Output 與 Independent Verification。
這條 Pipeline 解決了角色混在一起的問題,但還留下一個更實際的問題:
Validator 要看到多少證據,才可以把 Candidate 升級成 Confirmed Finding?
如果門檻太低,報告會充滿理論風險。
如果門檻太高,只有已經造成事故的問題才會被接受。
今天會替 Vibe Guard 定義一組最小證據門檻,並用四個候選問題測試分類結果:
目標不是讓報告看起來沒有問題,而是讓每個結論都能回答:
攻擊者是誰、控制什麼輸入、經過哪條路徑、跨越哪個邊界,以及實際得到什麼?
假設 AI 掃描完成後列出 40 個高風險問題。
工程師逐項調查,發現其中:
這份報告的 Precision 是:
4 / 40 = 10%
即使它成功找到 4 個漏洞,團隊仍然必須替另外 36 個項目付出調查成本。
更糟的是,連續收到誤報後,Reviewer 容易把下一份報告也當成雜訊。真正嚴重的 Finding 可能因此被延後處理。
所以 AI 稽核的價值不能只看「找到幾個問題」,還要看:
Precision = True Positives / (True Positives + False Positives)
完整評估還需要 Recall。這部分會在 Day 27 建立測試集後實際計算。今天先處理進入報告前的品質門檻。
Hunt 階段應該允許 Agent 大膽提出 Candidate。
例如:
Repository 中沒有 Rate Limit Middleware。
這是一項值得調查的觀察,但不能直接改寫成:
攻擊者可以讓正式服務發生 Denial of Service。
兩句話之間缺少部署架構、平台 Quota、CDN、Gateway、身分驗證與容量測試等證據。
Vibe Guard 第一版會使用四種 Verdict:
| Verdict | 意義 | 是否進入漏洞清單 |
|---|---|---|
confirmed |
攻擊路徑與影響都有證據 | 是 |
requires_evidence |
假設合理,但關鍵事實未知 | 否 |
hardening |
防禦可以改善,但未證明可利用 | 否 |
rejected |
已有反證推翻原始判斷 | 否 |
requires_evidence 與 hardening 的差異在於下一步。
requires_evidence 表示只要取得部署設定、執行結果或其他檔案,就可能確認或駁回問題。
hardening 則表示目前已理解行為,也認為可以改善,但沒有足夠的攻擊影響把它稱為漏洞。
今天先定義六項必要證據。
| 證據 | 要回答的問題 |
|---|---|
| Attacker Control | 攻擊者能控制哪個輸入或狀態? |
| Reachable Sink | 輸入真的能到達危險操作嗎? |
| Boundary Crossing | 行為跨越了哪個授權或信任邊界? |
| Reproduction | 能否用測試、命令或最小 Harness 重現? |
| Impact | 攻擊成功後,資料或服務發生什麼變化? |
| Mitigation Review | 是否有其他防護層阻止這條路徑? |
這六項不是適用所有漏洞類型的最終標準,但足以防止第一版 Agent 把單一程式碼味道直接升級成漏洞。
先確認攻擊者真的能控制資料。
看到以下程式:
execFile("git", ["show", commitId]);
不能只因為它執行外部程式就回報 Command Injection。
Validator 還要確認:
commitId 是否來自不可信使用者?如果值是程式內固定常數,就沒有外部攻擊者可以利用這條路徑。
輸入出現在程式中,不代表它會到達 Sink。
例如使用者輸入先經過:
output.textContent = userInput;
如果 Agent 只搜尋 userInput 和 HTML 頁面,可能回報 XSS。但 textContent 不會把輸入解讀為 HTML。
Validator 必須追蹤真實資料流,而不是只列出 Source 和附近看起來危險的 API。
資安問題通常涉及邊界被突破。
常見邊界包含:
如果一般使用者只能修改自己的專案,這是正常功能。
如果同一個請求可以修改其他帳號的專案,才構成 Authorization Boundary Crossing。
最有力的證據通常是可以重跑的測試:
1. 使用 Alice 建立 Project。
2. 使用 Bob 登入。
3. Bob 指定 Alice 的 Project ID 執行 Update。
4. Update 成功。
5. Alice 重新讀取後看到資料被改寫。
Reproduction 不一定要攻擊正式環境。
它可以是:
不能安全執行時,Validator 應明確標示缺少動態證據,而不是假裝已經重現。
「缺少檢查」不是 Impact。
以下句子只描述 Root Cause:
Firestore Rule 沒有比較 ownerId。
Impact 應描述攻擊成功後發生什麼:
任何已登入帳號都能讀取並改寫其他使用者的 Project Name 與 Launch Goal。
這樣團隊才能判斷資料敏感度、受影響範圍與修復優先級。
Repository 不是完整 Production Environment。
即使應用程式沒有某項防護,其他層仍可能包含:
Validator 不應假設這些防護一定存在,也不能假設它們一定不存在。
正確結果是:
Requires Evidence:需要部署設定確認 Edge Layer 是否限制請求。
未知資訊必須維持未知。
今天的 Demo 放在:
demo-app/finding-quality-demo/scenario.js
進入專案:
cd /media/mickey/777/ithome/demo-app
執行:
npm run finding-quality:demo
程式會逐項檢查六道 Evidence Gate,再將 Candidate 分類。
第一個候選是 Day 7 已經重現的越權問題:
Any signed-in user can modify another user's project
它具備完整證據:
| 門檻 | 證據 |
|---|---|
| Attacker Control | Bob 可以指定另一筆 Project Document ID |
| Reachable Sink | 請求到達 Firestore Write |
| Boundary Crossing | Bob 修改 Alice 擁有的 Resource |
| Reproduction | 兩個測試帳號在 Emulator 中成功重現 |
| Impact | Project Name 與 Launch Goal 被改寫 |
| Mitigation Review | Rules 唯一條件是使用者已登入 |
因此輸出:
AUTH-01 CONFIRMED
這個 Finding 不依賴「看起來危險」。它具有完整攻擊敘事與可重跑證據。
第二個候選把以下內容判定為 Credential Leak:
apiKey: "demo-api-key"
但 Day 16 已確認:
因此輸出:
SECRET-01 REJECTED
注意,Rejected 不代表刪掉紀錄。
系統應保存候選、原始理由與反證。未來相同字串再次被 Hunter 找到時,可以避免重複調查。
第三個候選指出 Repository 沒有 Rate Limit Middleware。
我們確實能證明:
但目前不能證明:
因此輸出:
RATE-01 REQUIRES_EVIDENCE
下一步不是立刻修 Code,而是取得 Deployment Configuration 並執行受控容量測試。
第四個候選是本機開發 Log 顯示使用者 Email。
Email 屬於應該謹慎處理的個人資料,因此減少 Log 內容是合理改善。
但目前資料只出現在本機 Demo,也沒有證據顯示未授權的人可以讀取這份 Log。
因此輸出:
LOG-01 HARDENING
這不是忽略隱私風險,而是把修正建議與已確認漏洞分開。
Demo 最後輸出:
SUMMARY
confirmed: 1
requires_evidence: 1
hardening: 1
rejected: 1
如果沒有證據門檻,四個候選可能全部被寫進漏洞報告。
加入分類後,漏洞清單只留下已經證明能跨越權限邊界的 AUTH-01。
其他結果沒有消失,而是進入符合目前證據狀態的 Queue。
另一個常見錯誤,是把 Severity 當成證據強度。
兩者回答不同問題:
例如公開管理介面可能具有 Critical Impact,但如果 Agent 尚未確認 Route 是否真的部署,就只能是低 Confidence 的 Candidate。
相反地,本機 Log 暴露一個測試 Email 可以有高 Confidence,但 Severity 很低。
Structured Finding 應分開保存:
{
"severity": "high",
"confidence": "high",
"verdict": "confirmed"
}
不能用「可能很嚴重」補償「尚未證明」。
LLM 很擅長把不完整觀察寫成完整敘事。
輸入:
沒有 Rate Limit Middleware
模型可能擴寫成:
攻擊者可大量呼叫 API,耗盡 Server Resource,
造成所有使用者無法存取服務。
這段話語意合理,卻增加了原始資料中不存在的事實。
Validator 的 Prompt 應要求:
unknown。confirmed。好的報告不是把空白補滿,而是讓讀者看見證據在哪裡停止。
只靠靜態搜尋,容易漏掉 Runtime 行為。
只靠動態測試,也可能只覆蓋一條路徑。
Vibe Guard 應組合兩種證據:
Static Trace
Source → Validation → Authorization → Sink
Dynamic Proof
Setup → Attack Input → Observed Result → Cleanup
以 AUTH-01 為例:
兩者一起使用,比單獨看到寬鬆 Rule 更可靠。
Agent 不能無限制搜尋證據。
第一版可以使用以下停止條件:
Confirmed
六項證據齊全,且沒有有效反證。
Rejected
找到足以推翻核心攻擊假設的反證。
Requires Evidence
必要證據位於目前不可見的系統或需要人工授權。
Hardening
行為已理解,但沒有可證明的安全邊界與影響。
這讓每次驗證都有明確出口,也讓後續流程知道該要求程式修正、補充設定,還是單純排入改善清單。
AI 找到可疑程式碼不等於找到漏洞。
一個 Confirmed Finding 至少要說清楚:
今天的 Demo 把四個候選分成 Confirmed、Requires Evidence、Hardening 與 Rejected,各自保留正確的證據狀態。
降低誤報不是少找問題,而是不讓推測冒充事實。
明天,我們會正式實作 Recon:讓 Agent 在開始 Review 前,先建立系統架構、資料流、信任邊界與部署假設。