Day 22,我讓工作台每次都從保存來源重新驗證 5 種情境、15 組案例。接下來的問題不是「怎麼把 JSON 接在一起」,而是「怎麼讓不同實驗能一起查詢,又不改掉各自的語意」。
最省事的做法,是替每組資料加一個 safe 或 unsafe。但這正好會破壞前 20 天留下的差異:未反射、fragment 沒被頁面使用、輸入已儲存但顯示為文字,以及提交遭拒,都沒有觀察到固定探針執行,原因卻完全不同。Day 23,我建立統一的 evidence bundle,同時保留 tool_verdict 和 canonical_outcome,拒絕把所有結果壓成二選一。
Evidence Bundle v1 的頂層不是只有案例陣列。它先記錄 schema_version、產品、處理範圍和摘要,再放入入口盤點、5 種情境、發布關卡與解讀限制。contexts 裡的每個情境都有原實驗日、顯示名稱、scenario ID、XSS 類型、來源摘要路徑與 SHA-256。
案例 ID 採用 day-<日>-<組別>。例如 Day 14 的弱點組是 day-14-vulnerable,Day 18 的拒絕組是 day-18-rejected。這樣即使不同實驗都使用 vulnerable,整份 bundle 仍不會撞名,也不必靠陣列位置猜來源。
一組案例的核心資料如下:
{
"id": "day-14-safe",
"name": "safe",
"tool_verdict": "fragment_rendered_as_text",
"canonical_outcome": "rendered_as_text",
"execution_observed": false,
"response_sha256": "…",
"form_sources": []
}
response_sha256 讓案例回到原始回應,POST 與儲存型案例另保留表單來源;報告驗證資料也掛在同一組案例下,但不覆蓋工具觀察。這些欄位讓 bundle 成為索引,而不是把原始檔重新包裝後丟掉路徑。
目前 15 組案例共有 12 種原始分類。我用明確對照表產生 8 種標準化結果:
tool_verdict |
canonical_outcome |
|---|---|
confirmed_xss |
execution_confirmed |
confirmed_dom_xss |
execution_confirmed |
confirmed_post_reflected_xss |
execution_confirmed |
confirmed_stored_xss |
execution_confirmed |
reflected_but_not_executed |
reflected_not_executed |
not_reflected、post_not_reflected |
not_reflected |
fragment_rendered_as_text |
rendered_as_text |
fragment_not_rendered |
source_not_used |
post_reflected_as_text |
reflected_as_text |
stored_as_text |
stored_as_text |
submission_rejected |
submission_rejected |
這裡只有 4 種已確認執行分類被合併為 execution_confirmed,因為跨情境統計時,我確實想知道固定探針在哪幾組執行;原本是哪一種 XSS,仍由情境的 xss_kind 與逐字保留的 tool_verdict 表達。
其餘結果沒有硬湊。例如 fragment_not_rendered 代表頁面沒有把 fragment 放進目標內容,post_not_reflected 代表 POST 值沒有出現在結果回應,submission_rejected 則是提交已被拒絕。它們都不是 execution_confirmed,卻也不能互換。
如果未來 detector 新增一個對照表裡沒有的 verdict,工作台會中止並要求我明確決定映射,而不是依字串猜成「可能安全」。這個 fail closed 行為能避免新語意悄悄被舊分類吞掉。
摘要顯示 5 組觀察到固定探針執行、10 組未觀察到。這個數字適合做整體導航,卻不適合單獨當結論。我要知道某一組為什麼未執行,仍須看 tool_verdict、canonical_outcome、情境、回應雜湊和來源檔。
例如 Day 14 的純文字組是 rendered_as_text,表示 fragment 確實進入目標 DOM,但透過 textContent 顯示;Day 14 的忽略組是 source_not_used,目標內容根本沒使用 fragment。兩組的 execution_observed 都是 false,資料流卻停在不同位置。若只留下 false 或 safe,後面便無法重建這個差異。
同樣地,canonical_outcome 也不是正式風險等級。它描述本次固定探針的技術結果,不知道真實資產、使用者、權限、資料敏感度或部署控制。工作台頂層因此保留 interpretation_limits,提醒「未觀察到」不等於網站安全。
Day 20 的 portfolio 帶有建立時間,但時間不是證據內容。我在標準化時沒有把它帶進 bundle,JSON 輸出則固定使用 UTF-8、相同縮排與鍵名排序。如此一來,相同來源不會只因執行時間不同就改變雜湊。
我執行工作台測試,發布合約確認 5 種情境、15 組案例、6 份表單和執行訊號數量完全符合;另一個測試則在兩個暫存目錄各建立一次,逐位元比較 bundle、Markdown、HTML 與 manifest,結果一致。可重現輸出讓後面的 SHA-256 有實際意義。
Day 23 的成果不是替 15 組資料貼上更簡單的標籤,而是建立兩層語言:tool_verdict 保存當時工具真正說了什麼,canonical_outcome 提供跨情境查詢。明天,我會把 5 個固定的 AI 報告批次接進這個結構,重新執行各日 validator,並把自動驗證和作者語意核對分開呈現。
那就…
明天見!