iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Build on Google AI

30 天打造 AI Web Security Agent:從 Google AI Studio 到 Gemini Agent系列 第 9 篇

Day 9|從匯出草稿到可追蹤修訂版:為 AI 報告補上核對紀錄

  • 分享至 

  • xImage
  •  

前言

昨天,我把 Gemini 回傳的 JSON 轉成 Markdown,讓工具分類、風險判讀、證據與修正建議可以直接閱讀。

不過,報告上方仍然寫著「匯出草稿」。Day 7 已經找出一個具體問題:摘要提到 q 參數,引用清單卻沒有 parameter: q。Day 8 只是忠實轉換格式,沒有偷偷替模型補上這段證據。

所以今天不增加新的掃描能力,也不重新呼叫模型。我想補上前幾天工具缺少的最後一段流程:保留 Gemini 原始回答,另存修訂副本,記錄改了什麼、為什麼改,再附上作者逐項填寫的核對紀錄。

這次使用 Day 7 已保存的真實回答與證據。報告內容的修訂由我逐項核對,程式負責限制變更範圍、檢查紀錄與保存結果;兩者不能混成同一件事。

原本的 PASS 到底代表什麼?

Day 7 驗證器會檢查:

  • JSON 是否符合 Schema。
  • tool_verdict 是否與底層偵測器一致。
  • 每段引用是否逐字出現在指定來源。
  • 可能影響有沒有成立條件。
  • 修正建議有沒有驗證方式。

這些規則可以攔下不存在的來源、被改寫的引用與錯誤的工具分類,卻不會把摘要拆成每個主張,再判斷現有引用是否全部涵蓋。

因此,原始報告雖然通過驗證,下面這句仍然少了一塊引用:

在本機測試端點的 q 參數中觀察到腳本執行與 DOM 寫入,檢測工具確認觸發 alert(1)。

原本 3 段引用包含 verdict: confirmed_xss、execution_observed: true 與一段 HTTP 回應節錄,卻沒有直接列出 parameter: q。

這不是 Structured outputs 或 JSON Schema 能單獨解決的問題。Google 的 Gemini 文件說明,Structured outputs 能產生符合指定 JSON Schema 的可預期結構,但仍應在應用程式驗證欄位值,並處理格式正確、語意卻不符合需求的情況。

JSON Schema 規格也把驗證描述為對資料結構限制的檢查,並明確指出,單靠結構驗證可能不足以讓應用程式正確使用某些值。

所以今天不是再加一個更大的 PASS,而是把「程式可以檢查的規則」與「我實際核對的語意」分開保存。

為什麼不直接修改模型原文?

如果我直接打開 Day 7 的 report.json 加上一段引用,之後就很難回答:這是 Gemini 原本回傳的內容,還是我修改後的版本?

因此,流程採用副本修訂:

Day 7 原始 report.json 與 evidence.json
                ↓
確認輸入 SHA-256 與原始報告規則
                ↓
依 revision.json 在副本增刪引用
                ↓
記錄摘要主張與全部欄位的核對結果
                ↓
重新執行 Day 7 驗證與 Markdown 呈現檢查
                ↓
另存修訂版與 review-record.json

revision.json 先記錄原始報告與證據的 SHA-256。程式在開始與結束時重新計算,確認來源沒有改變。

本次輸入結果如下:

輸入 執行前 SHA-256 執行後結果
Day 7 report.json 353381affa950106a6d91e1bf33b0b0b5b3a36417b96653a599e83e954f18908 相同
Day 7 evidence.json c9a8c5969e410d5904c251d8f9ac36eb190179be9e42d1b06834fdd543928a57 相同

原始檔留在原本位置,新的內容寫入 day-09/results/revised-report.json。

把允許的修改寫成修訂清單

我沒有讓工具接受任意的 JSON 覆寫,而是只提供兩種操作:add_evidence 與 remove_evidence。

補上 q 引用的紀錄如下:

{
  "op": "add_evidence",
  "reference": {
    "source_id": "E1",
    "quote": "parameter: q"
  },
  "reason": "摘要明確提到 q 參數,加入對應的直接引用。"
}

新增引用必須逐字出現在指定來源。移除引用則同時指定 source_id 與原引用的 SHA-256,而且必須剛好命中 1 筆;我不會用「刪除所有 E3」這種範圍過大的規則。

程式先深度複製原始報告,再套用清單。完成後逐欄比對,除了 evidence 以外,只要 summary、tool_verdict、risk_rating 或其他欄位有任何差異,就停止產生修訂版。若增刪後與原本完全相同,也會被拒絕。

這項限制很重要:今天是在補證據、修訂狀態與作者核對紀錄,不是把模型敘述悄悄改寫成另一份內容。

實際補上哪些證據?

除了 parameter: q,我也重新檢查摘要裡的 alert(1) 與 DOM 寫入。原始 E2 已經保存直接欄位,因此修訂版加入:

alert_observed: true
dialog_message: 1

以及:

dom_excerpt: data-day6-xss="day6-dad61ea68046bba0"

Day 7 原本的 E3 引用很長,而且結尾停在 setAttribut。它確實逐字存在於來源,所以能通過舊驗證器;但截斷內容不容易閱讀,也不是支持 alert(1) 與 DOM 執行結果最直接的證據。

我依原引用 SHA-256 精確移除它,再從相同 E3 來源加入一段完整短片段:

<xss-probe data-token="day6-dad61ea68046bba0">probe</xss-probe>

摘要開頭還限定了「本機測試端點」,因此我也加入 E1 的目標網址。最後,修訂版共有 7 段引用:

來源 引用 用途
E1 verdict: confirmed_xss 保留底層工具分類
E2 execution_observed: true 保留瀏覽器執行狀態
E1 本機目標網址 支持摘要的測試範圍
E1 parameter: q 補上摘要中的參數名稱
E2 alert_observed 與 dialog_message 直接支持 alert(1)
E2 dom_excerpt 直接支持 DOM 旗標
E3 完整的 <xss-probe> 短片段 保存回應中的探針元素

摘要、風險等級、可能影響、未知事項、修正建議與限制都保持原樣。

核對紀錄不能只靠一個參數切換

若匯出指令可以直接加上 --reviewed,任何尚未完成核對的報告也可能被標示為已審查。因此,我沒有讓指令直接宣稱語意正確,而是把修訂與核對紀錄的成立條件寫進流程。

revision.json 除了變更清單,也先保存原始摘要的 SHA-256,再把摘要拆成 5 項主張:本機測試端點、測試參數、腳本與 alert(1)、DOM 識別碼,以及底層工具分類。每一項都要記錄摘要原文片段、supported、對應來源、逐字引用與核對說明。

另外,Day 7 報告的 9 個頂層欄位都要有 approved 或 approved_after_evidence_revision 狀態。只要出現 changes_required、缺少欄位,或某段主張引用沒有列入修訂版,程式就會在 last-attempt.json 留下 rejected 紀錄,不會取代前次成功的發布狀態。

這仍然不是程式自行理解語意。它能確認核對紀錄綁定這一版摘要、原文片段與引用真的存在,但不能自行判斷主張拆分是否完整,也不能驗證 reviewer 欄位背後的身分。因此,輸出狀態是 review_recorded,而不是「程式已完成語意審查」。語意判讀由我填寫,程式只把決定、依據與限制變成可重現的資料。

實際執行與驗證結果

我在專案根目錄執行:

python3 day-09/review_report.py

這次沒有重新呼叫 Gemini,終端機顯示:

完成:自動檢查通過,作者核對紀錄已保存
結果目錄:.../day-09/results

實際檢查結果如下:

檢查項目 結果
修訂紀錄 Schema 通過
輸入 SHA-256 通過
原始證據結構 通過
核對紀錄與摘要 SHA-256 通過
輸入與輸出路徑隔離 通過
Day 7 原始報告重新驗證 通過
只允許宣告的證據變更 通過
5 項摘要主張的引用 通過
9 個欄位核對紀錄 通過
Day 7 修訂版重新驗證 通過
Markdown 預覽安全呈現 通過
原始來源前後一致 通過

修訂版 JSON 的 SHA-256 是 4a334c444a408078c4dea90de3337dd46b561aa8b9c291834fbf0b30649aca76。完整變更、來源與環境保存在 review-record.json,成功發布狀態保存在 review-check.json,最近一次嘗試則保存在 last-attempt.json。

我也執行:

python3 day-09/review_report.py --verify-existing

這個模式不重新產生檔案,而是重新計算原始來源、修訂版 JSON、Markdown 與 HTML 預覽的雜湊,再以 Day 7 規則檢查修訂版。本次結果通過;如果之後任一產物被改動,重新驗證會失敗。

讓 Markdown 狀態跟著核對紀錄

Day 8 的匯出函式原本固定顯示「匯出草稿,內容仍需核對」。今天,我讓函式接受狀態與說明,但保留原本的草稿預設值。

換句話說,直接執行 Day 8 工具仍然會得到草稿;只有 Day 9 全部條件通過後,才由審查流程傳入:

修訂版(已附作者核對紀錄:day9-q-evidence-review)

證據裡的 <xss-probe> 仍放在圍欄程式碼區塊。CommonMark 將這類區塊的內容視為文字,不繼續解析其中的行內標記。

程式也實際把 Markdown 轉成 HTML,確認 7 段引用都留在程式碼區塊,且沒有建立 xss-probe、img、script、iframe、object 或 embed 元素。狀態與說明也先當成純文字處理,不能藉由自訂內容插入 HTML。

加上負向測試與前幾天的回歸檢查

我為 Day 9 建立 18 個離線測試,除了成功流程,也刻意測試:

  • 新增非逐字引用。
  • 使用不存在的來源。
  • 原始報告 SHA-256 不一致。
  • 要移除的引用沒有唯一命中。
  • 日期無效或摘要片段不存在。
  • 審查決定仍是 changes_required。
  • 任一欄位仍未完成核對。
  • 摘要主張的引用沒有列入修訂版。
  • 企圖修改 tool_verdict。
  • HTML 探針在預覽中變成元素。
  • 輸出符號連結指回原始報告。
  • 成功後失敗重跑,是否保留前次成功紀錄並另存失敗嘗試。
  • 修訂版、Markdown 或預覽在產出後遭到修改。

18 個測試全部通過。接著,我重新執行 Day 6 的 5 個瀏覽器整合測試、Day 7 的 9 個離線測試與 Day 8 的固定報告匯出檢查,結果也都通過。

Day 8 仍然能以原本的 3 段引用產生草稿,表示今天增加可選狀態後,沒有把昨天的重現結果改成修訂版。最後,我再直接使用 Day 7 驗證器檢查 Day 9 的 revised-report.json,修訂版也通過既有規則。

這些測試證明的是程式按照目前寫下的限制運作,不是語意審查已經能自動化,更不是 Gemini 風險分析的準確率。

這一版能確認到哪裡?

今天完成後,我可以回答:

  • Gemini 原始回答是哪一份,而且執行前後沒有被修改。
  • 修訂版相對原稿增加與移除了哪些引用。
  • 每項變更的理由、來源與逐字內容是什麼。
  • 摘要中的 5 項具體主張如何回到證據。
  • 哪些欄位已核對,以及狀態是直接通過還是補證據後通過。
  • Markdown 的修訂狀態由哪一筆修訂與核對紀錄產生。

目前仍不能據此推論:

  • 工具已涵蓋 HTML 屬性、JavaScript 字串、DOM 型或儲存型 XSS。
  • 已完成真實網站或整站掃描。
  • needs_context 已經是正式環境的最終風險等級。
  • 只要提供一份修訂紀錄,程式就能自行判斷所有主張是否有足夠證據。
  • 附有作者核對紀錄的修訂版等同身分已驗證、第三方審查或不可再修改的最終報告。

Day 9 的報告產製流程沒有重新執行探針或呼叫模型 API;修訂版只使用已保存的 Day 7 輸入。前一節重跑的 Day 6 探針僅用於回歸測試,沒有成為新報告輸入。今天的目標是補足報告生命週期,不是增加新的漏洞發現。

Day 9:讓修訂有來源,讓狀態有條件

今天,我把 Day 7 發現的 q 引用缺口真正補進工具流程,也處理了截斷且不易閱讀的 E3 引用。

比起直接改掉原始 JSON,更重要的是保留了三份可以互相核對的資料:Gemini 原始回答、宣告增刪理由的 revision.json,以及實際產生、附作者核對紀錄的修訂版。來源雜湊、欄位差異、摘要主張與驗證結果也一起寫進紀錄。

格式通過不等於語意正確;反過來,完成語意核對也需要留下可重現的依據。今天的工具把這兩層接在一起,但沒有假裝它們是同一件事。

那就…
明天見!


上一篇
Day 8|把 AI 分析變成報告:將 JSON 匯出為 Markdown
下一篇
Day 10|同樣的輸入放進 HTML 屬性,瀏覽器會怎麼處理?
系列文
30 天打造 AI Web Security Agent:從 Google AI Studio 到 Gemini Agent 共 12 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言