昨天,我從 15 份保存 HTML 整理出表單與前端訊號。那份入口索引可以回答「哪裡值得往下查」,卻不能回答「先前的實驗結果現在還能不能信」。如果工作台只把 Day 20 的 portfolio.json 複製進新格式,來源檔即使已缺少或被改動,漂亮的總表仍可能原封不動。
所以 Day 22,我沒有增加第 6 種 XSS 情境,也沒有重新送出探針。我把 Day 20 的來源重驗接成工作台的前置關卡:每次建立 bundle,都從 Day 10、12、14、16、18 的保存來源重新讀起。任何一層對不上,就不進入後面的整合。
Day 20 已經有一套知道各日差異的驗證程式。工作台的 load_day20_portfolio() 會載入 day-20/build_portfolio.py,呼叫它的 build(),取得重新核對後的物件。這個做法保留一個共用的來源驗證入口;我不需要把 5 支 detector 的端點、selector 和分類條件再拼一次。
我也刻意不直接讀取 Day 20 已寫出的 portfolio.json。重新呼叫 build() 時,程式會逐日打開 run-summary.json、3 組案例 JSON 與對應的原始檔,再計算當前內容的 SHA-256。結果仍是 5 種情境、每種 3 組,共 15 組案例,但這個數量是由現有來源重新得到的,不是從舊總表照抄。
Day 10 與 Day 12 的原始回應保存在案例 JSON,Day 14、16、18 則要求獨立的 *-response.txt 必須存在。程式把實際位元組計算成 SHA-256,再同時對照案例 JSON 與摘要中的雜湊。若 JSON 內還留著舊文字、獨立回應檔已被改動,或摘要引用另一版結果,都會停止。
POST 與儲存型情境還有額外來源。Day 16 每組都要核對 *-request-body.txt、JSON 內的 request body 和其 SHA-256。Day 18 每組則固定要求 8 份 artifact:表單請求、表單回應、表單 HTML、提交請求、提交回應、讀取請求、讀取回應與最後的 response。少一份、多一份,或任何一份雜湊不相符,都不能當作完整的儲存後讀取證據。
表單也不是只確認檔案存在。Day 16、18 共 6 份保存 HTML 會重新解析,並和當時案例 JSON 記錄的 method、action、欄位及 enctype 對照。這讓「我以為送到這個端點」和「保存表單實際寫了什麼」不會悄悄分家。
前幾天的固定探針不只呼叫 alert(1),也會把本次唯一 token 寫到文件根元素的資料屬性。來源重驗要求兩條線同時成立:Chrome 捕捉到訊息正好為 1 的對話框,而且 DOM marker 正好等於本次 probe_token。只有這樣,execution_observed 才能是 true。
程式還會把這個布林值和各日工具分類交叉核對。confirmed_xss、confirmed_dom_xss、confirmed_post_reflected_xss、confirmed_stored_xss 必須有完整雙重訊號;其他分類則不能偷偷帶著 execution_observed: true。本次重驗仍得到 5 組觀察到固定探針執行,另外 10 組沒有完整訊號。
這個規則不是說雙重訊號可以證明任意 JavaScript 都能執行。它只能降低單一觀察誤判的機會,並確認當時那個固定探針的兩個預期效果同時出現。
我重新執行 Day 20 的離線測試:
.venv/bin/python -m unittest discover -s day-20 -p 'test_*.py' -v
7 個測試全部通過。它們會在暫存副本中修改或拿掉原始回應、破壞表單來源、刪減 Day 18 artifact、製造只有單一執行訊號的分類,並放入不屬於本次實驗摘要的舊報告。驗證器都會拒絕這些不一致,而不是盡量湊出一份成功總表。
我也執行工作台的 9 個測試;其中發布合約會實際走過這個來源重驗,確認 5 種情境、15 組案例、6 份表單,以及 5 組已觀察與 10 組未觀察的數量都符合設定。也就是說,Day 20 不再只是前一天產生過的成品,而是工作台每次重建時都會使用的驗證關卡。
我特別保留「不重新執行瀏覽器」這項選擇。若今天直接呼叫 Day 10~18 的 run_experiment.py,多數流程會啟動本機服務、覆寫原本的 results/,也可能讓新的 token 和舊報告失去對應。Day 22 要回答的是「現有保存證據能否一致地重建」,不是「同一個實驗今天再跑一次會不會得到相同畫面」。重新實測可以是另一個任務,但不能用新結果冒充舊報告的來源。
因此,來源驗證器只回傳通過核對的案例摘要,不替失敗案例製造部分 bundle。工作台捕捉 Day 20 拋出的錯誤,改成清楚的來源重驗失敗訊息,整個建立流程就此停止。這比輸出 14 組成功、1 組缺證據的混合報告更符合本作品的可追溯目標。
SHA-256 能揭露目前來源與記錄是否一致,卻不是外部可信簽章。如果有人同時修改原始檔、案例 JSON、摘要和所有雜湊,這套本機檢查無法證明歷史沒有被重寫。它也不會重開 Chrome,所以瀏覽器訊號仍是對保存紀錄的交叉核對,而不是今天重新執行探針。
Day 22 的成果,是在入口索引和最終報告之間加上一道「先回來源」的關卡。明天,我會把通過重驗的 15 組案例轉成同一個 evidence bundle;重點不是把分類壓成安全與不安全,而是讓不同情境既能比較,也保留原本的語意。
那就…
明天見!