昨天,我把 Gemini 回傳的 JSON 轉成 Markdown,讓工具分類、風險判讀、證據與修正建議可以直接閱讀。
不過,報告上方仍然寫著「匯出草稿」。Day 7 已經找出一個具體問題:摘要提到 q 參數,引用清單卻沒有 parameter: q。Day 8 只是忠實轉換格式,沒有偷偷替模型補上這段證據。
所以今天不增加新的掃描能力,也不重新呼叫模型。我想補上前幾天工具缺少的最後一段流程:保留 Gemini 原始回答,另存修訂副本,記錄改了什麼、為什麼改,再附上作者逐項填寫的核對紀錄。
這次使用 Day 7 已保存的真實回答與證據。報告內容的修訂由我逐項核對,程式負責限制變更範圍、檢查紀錄與保存結果;兩者不能混成同一件事。
PASS 到底代表什麼?Day 7 驗證器會檢查:
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 規則檢查修訂版。本次結果通過;如果之後任一產物被改動,重新驗證會失敗。
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 個離線測試,除了成功流程,也刻意測試:
changes_required。tool_verdict。18 個測試全部通過。接著,我重新執行 Day 6 的 5 個瀏覽器整合測試、Day 7 的 9 個離線測試與 Day 8 的固定報告匯出檢查,結果也都通過。
Day 8 仍然能以原本的 3 段引用產生草稿,表示今天增加可選狀態後,沒有把昨天的重現結果改成修訂版。最後,我再直接使用 Day 7 驗證器檢查 Day 9 的 revised-report.json,修訂版也通過既有規則。
這些測試證明的是程式按照目前寫下的限制運作,不是語意審查已經能自動化,更不是 Gemini 風險分析的準確率。
今天完成後,我可以回答:
目前仍不能據此推論:
needs_context 已經是正式環境的最終風險等級。Day 9 的報告產製流程沒有重新執行探針或呼叫模型 API;修訂版只使用已保存的 Day 7 輸入。前一節重跑的 Day 6 探針僅用於回歸測試,沒有成為新報告輸入。今天的目標是補足報告生命週期,不是增加新的漏洞發現。
今天,我把 Day 7 發現的 q 引用缺口真正補進工具流程,也處理了截斷且不易閱讀的 E3 引用。
比起直接改掉原始 JSON,更重要的是保留了三份可以互相核對的資料:Gemini 原始回答、宣告增刪理由的 revision.json,以及實際產生、附作者核對紀錄的修訂版。來源雜湊、欄位差異、摘要主張與驗證結果也一起寫進紀錄。
格式通過不等於語意正確;反過來,完成語意核對也需要留下可重現的依據。今天的工具把這兩層接在一起,但沒有假裝它們是同一件事。
那就…
明天見!