Day 12,我讓查詢參數 q 進入行內 <script> 的單引號字串,得到三種本機結果:原樣插入時,本次探針跳出字串並執行;經過字串序列化與腳本邊界處理後,探針仍出現在回應中,卻只是資料;不反射頁則完全沒有本次探針識別碼。
編碼頁最容易讓報告寫錯。它的正常腳本確實執行,並把完整探針顯示在頁面上,但探針內的 alert(1) 沒有執行。如果 AI 只看到「腳本有執行」或回應中的 alert(1) 文字,很可能把兩件事混在一起。今天我把 Day 12 保存的原始回應、DOM 觀察與探針執行訊號接回 Day 7 的九欄風險報告,檢查模型能否保留這個差別。
我先把 Day 12 的三組 JSON 直接交給 Day 7 的 build_evidence()。每組都只得到 4 份來源,而且都沒有 inline_script_single_quoted_string、response_raw_breakout、dom_script_ran 或 dom_query_value。舊流程能提供工具分類與瀏覽器執行狀態,卻不能讓模型知道輸入落在什麼語法位置,也無法區分正常腳本讀到的資料與跳出字串的程式碼。
這個缺口不能靠模型猜。Day 13 的流程改讀 Day 12 已保存的三組 JSON、完整 HTML 回應和執行摘要,先核對探針、端點、原始回應、SHA-256 與分類,再依不同層次產生具名證據。這次沒有重新對網站送出探針。
| 來源 | 提供給模型的證據 |
|---|---|
| E1 | 工具分類、q 參數與行內單引號字串情境 |
| E2 | 原始回應中的腳本文字、探針是否反射、原始字串是否被跳出 |
| E3 | 正常腳本是否執行、DOM 中讀回的查詢值及其是否等於完整探針 |
| E4 | alert、對話框訊息、DOM 識別碼與探針執行結果 |
| E5 | 本機實驗的適用範圍與限制 |
Day 12 的編碼頁原始回應中,探針留在一個完整的雙引號 JavaScript 字串常值內。瀏覽器讀回的查詢值等於整段探針,data-script-ran 也顯示正常腳本完成執行;但沒有 alert,也沒有與本次識別碼相同的 DOM 旗標。因此,Day 13 的證據把這幾件事分開記錄,而不是只給模型一個「執行/未執行」欄位。
編碼組送給模型的關鍵證據如下。dom_script_ran: true 說的是頁面正常腳本;execution_observed: false 說的是本次探針,兩者不能互換:
E2 response_probe_in_script: true
E2 response_raw_breakout: false
E3 dom_script_ran: true
E3 dom_query_value_matches_payload: true
E4 alert_observed: false
E4 execution_observed: false
我沿用 Day 7 的九欄 JSON Schema 和逐字引用檢查,但要求報告額外引用字串邊界、DOM 查詢值、正常腳本狀態與探針執行訊號。三組 tool_verdict 必須保留 Day 12 的分類;本機案例缺少真實資產與業務背景,risk_rating 應是 needs_context。編碼與不反射組沒有確認 XSS,這次報告的 possible_impacts 和 remediation 必須是空陣列。
Schema、引用及這些欄位規則通過,仍不能證明每一句風險敘述都正確。符合 Schema 的輸出仍可能有語意錯誤;Day 11 也實際遇過模型把固定探針的結果寫得比證據更廣。因此,我保存模型原文,另外核對每組摘要、可能影響與修正建議,再用作者自己的文字寫下本次可支持的結論。
我先執行乾跑,只驗證保存的來源並產生模型提示:
三組都通過輸入核對;這一步的 api_called 是 false,沒有模型回答,也不能當成 AI 報告通過。程式不只比對 JSON 中的分類,也重建 Day 12 的預期腳本,檢查請求探針、完整回應、腳本節錄、SHA-256、瀏覽器訊號與執行摘要是否一致。三組來源還須來自同一個本機站點與瀏覽器,探針識別碼彼此不同。
我另執行 Day 13 的 19 個離線測試與 Day 7 的 9 個回歸測試。反向測試包括竄改原始回應、探針、字串跳脫真值、DOM 查詢值、瀏覽器訊號和摘要;這些不一致的輸入都被拒絕。這些測試證明的是來源轉換與驗證規則在設計情境下運作,尚不能代替模型回答的語意審查。
2026 年 9 月 27 日,我使用 gemini-3.5-flash-lite,對三組已核對的來源各送出一次請求。每組都保存模型原始回答、九欄報告、逐字引用及驗證結果,與 Day 12 的原始實驗資料分開。這次沒有重新啟動網站,也沒有再次送出 XSS 探針。
| 組別 | Day 12 工具分類 | Gemini 保留的分類 | 正常腳本/探針執行 | 自動檢查 |
|---|---|---|---|---|
| 弱點頁 | confirmed_xss |
confirmed_xss |
兩者皆觀察到 | 通過 |
| 編碼頁 | reflected_but_not_executed |
reflected_but_not_executed |
正常腳本執行;未觀察到探針執行 | 通過 |
| 不反射頁 | not_reflected |
not_reflected |
正常腳本執行;未觀察到探針執行 | 通過 |
三份回答都使用 needs_context,編碼與不反射組的可能影響及修正建議皆為空陣列。編碼組引用了「原始腳本有探針」「沒有跳出字串」「正常腳本有執行」與「未觀察到探針執行」;不反射組引用了未反射及 DOM 查詢值為固定內容。這些關鍵差異沒有在自動檢查階段遺失。
我接著逐項對照模型文字與原始回應。弱點組的摘要正確描述本次探針跳出字串,Chrome 捕捉到 alert(1),DOM 識別碼也相符;但它在「可能影響」寫了「在受害者瀏覽器中執行未經授權的 JavaScript 程式碼」。本次只有本機 Chrome 執行固定的 DOM 旗標與 alert(1),沒有真實受害者或其他程式碼的實測。修正建議雖指出 JavaScript 字串與 HTML 腳本邊界,驗證欄位也寫了「確認輸入已被正確跳脫」,卻只指定重送同一探針及檢查對話框與旗標,未要求核對原始腳本和 </script> 邊界,不足以證明兩層邊界都處理正確。
編碼組的分類與執行判斷也對了,但摘要稱探針進入「單引號字串」。原始回應實際使用 json.dumps() 產生的雙引號字串常值;探針裡的單引號只是其中的資料。模型把「原本要測的單引號輸出位置」誤寫成「編碼後實際的字串定界符」。它還直接把 response_raw_breakout: false 欄位名放進摘要,公開文章改寫為「沒有跳出字串」較清楚。
不反射組正確指出本次識別碼沒有出現在回應、DOM 查詢值為固定內容,也沒有把它寫成整站安全。它的「探針未執行」依觀察宜收斂為「未觀察到本次探針執行」;「真實端點」則容易與已知的本機端點混淆,應改成「真實部署環境」。我把這些逐項判讀另存為作者核對紀錄,模型原始回答保持不變。
這次 Gemini 保住三組技術分類,也沒有把編碼頁的正常腳本執行誤判成探針執行;但這批通過 Schema 與逐字引用檢查的回答,仍分別出現編碼頁字串引號寫錯,以及弱點組可能影響超出本次固定探針的問題。具名證據和必要引用讓我能找到錯在哪裡,作者核對則決定哪些話可以寫進文章。
本次直接支持的結論只限於 Day 12 保存的本機 GET、行內 <script> 單引號輸出情境與固定探針。它不能推論其他 JavaScript 語法位置、DOM 型或儲存型 XSS、CSP、真實使用者影響或正式風險等級。
那就…
明天見!