修補後驗證
驗證安全修補程式是否正確修復了回報的錯誤,以及其對周圍程式碼的影響。提供可重現的失敗案例給修補程式作者修復,並識別仍需測試的區域。此流程同樣適用於人類與 AI 代理產生的修補程式。僅憑差異、作者、上游實作或原始漏洞概念驗證(PoC)無法證明修補程式的正確性。
使用時機
- 已存在安全修復、修復提交、修補檔案或拉取請求。
- AI 生成的修補程式需要在人工審查或合併前進行驗證。
- 修復可能覆蓋了單一漏洞路徑,但遺漏了相同根本原因的其他變種。
- 安全修復可能改變了正常行為或引入了新的漏洞。
- 修補程式作者在進入下一次修訂前,需要具體的失敗案例與覆蓋率缺口。
何時不應使用
- 尚無修補程式存在,應先進行漏洞發現或修復實作。
- 任務是根據報告審查稽核發現,而不執行修補證據。
- 任務只是將發現轉換為專案的永久測試。
- 目標是遠端或生產環境。此技能僅在本地執行程式碼與測試。
- 使用者尚未授權執行存放庫的程式碼或測試套件。
快速開始
-
鎖定有漏洞的基礎版本與修補後的輸入。優先使用不可變的提交。對於未提交的工作,請先建立二進位修補檔案;請勿在使用者的工作目錄中進行驗證。
-
建立一個鎖定的計畫骨架:
uv run {baseDir}/scripts/post_patch_validation.py scaffold \ --repo . \ --base-ref <vulnerable-ref> \ --patched-ref <patched-ref> \ --finding-id <stable-id> \ --finding-summary "<root cause and impact>" \ --evidence-level runtime \ --output post-patch-validation/plan.json對於修補成品,請使用
--patch-file <path>替代--patched-ref。選擇最高且誠實的證據等級:source(僅限原始碼/修補不變量)、build(目標程式碼經編譯或分析,但未執行回報的行為)、或runtime(檢查會執行回報的行為及其安全斷言)。 -
檢查發現、差異、呼叫者、同級路徑、清理/錯誤路徑以及現有測試。在生成的計畫中填入
checks。執行print-schema以查看結構化綱要:uv run {baseDir}/scripts/post_patch_validation.py print-schema -
在執行程式碼之前,執行
validate-plan以進行完整驗證,包括覆蓋率、命令限制和鎖定的輸入:uv run {baseDir}/scripts/post_patch_validation.py validate-plan \ --plan post-patch-validation/plan.json -
執行證據計畫:
uv run {baseDir}/scripts/post_patch_validation.py run \ --plan post-patch-validation/plan.json \ --output post-patch-validation/results -
回報
result.json、report.md、證據等級以及完整的assessment。將每個發現(連同其檢查 ID、斷言和儲存的日誌)回傳給修補程式作者。分別識別每個驗證缺口,包括與支持的發現共存的缺口。修補程式作者修訂修補後,鎖定新的輸入並儲存新的驗證執行。保留先前的證據。通過提供的檢查仍需人工審查後方可接受。
證據合約
執行器會拒絕不完整的計畫。請提供每種類別至少一項檢查:
| 類別 | 必要觀察 |
|---|---|
control | 良性測試載體在基礎版本與修補版本上均成功 |
exploit | 原始安全斷言在基礎版本上失敗,在修補版本上成功 |
variant | 一個不同的根本原因變種在基礎版本上失敗,在修補版本上成功 |
behavior | 未受影響的行為成功,且選擇性輸出位元組一致 |
regression | 針對性的非安全回歸檢查在兩個版本上均成功 |
security | 鄰近/新漏洞檢查在基礎版本與修補版本上均成功 |
suite | 現有專案測試套件、清理器或確定性模糊測試在修補版本上成功 |
命令為 argv 陣列,絕非 shell 字串。複雜設定應放在已簽入或計畫成品的腳本中,並使用 {plan_dir} 進行調用。執行器會固定語言環境/時區/雜湊種子輸入,按詞彙 ID 順序執行檢查,記錄原始 stdout/stderr,且絕不編輯原始工作目錄。每個檢查的 timeout_seconds 預設為 300,可接受 1 到 3600 的整數。超時會留下驗證缺口。僅超時本身不能證明回歸。每個計畫還包含一個排序的 submodules 陣列(無子模組時為 [])。骨架化會從變更的檔案清單中推斷受影響的 Gitlink。執行器會從來源存放庫的現有 Git 模組物件初始化這些鎖定的提交,絕不會從 .gitmodules 網路 URL 初始化;請在驗證前於來源存放庫中初始化或擷取它們。
漏洞利用和變種檢查必須證明它們已執行
非零退出碼並不表示漏洞已重現。匯入錯誤、建置失敗、缺少依賴項以及安全斷言失敗都會退出非零,且執行器無法區分。每個 exploit 和 variant 檢查必須在兩個版本上,於評估其斷言之前立即輸出並刷新 PPV_REACHED:
"argv": ["python3", "-c", "import app; value = app.render('<'); print('PPV_REACHED', flush=True); assert value == '<'"]
該標記也存在於環境變數中,名為 PPV_REACHED_MARKER。它必須出現在 stdout,作為獨立一行。不掃描 stderr,因為 Python SyntaxError 回溯會回顯有問題的原始碼,否則對於一個從未執行的測試載體來說,這將滿足檢查條件。未輸出標記的執行會留下 marker_missing 缺口。來自其他檢查的獨立發現仍會保留在結果中。務必明確刷新:如果測試載體的承載導致段錯誤或呼叫 _exit,則會遺失緩衝輸出並喪失其證據。
這些檢查也是側盲(side-blind) 運行的。{side} 不會為它們展開,PPV_SIDE 不會出現在它們的環境中,檢出目錄名稱是隨機的,且計畫驗證器會拒絕任何 argv 或 env 中提及這些項目的 exploit 或 variant 檢查。一個能夠看見自己在哪個版本上運行的斷言可以針對那個版本進行斷言,而不是針對程式碼,這是偽裝重現與修復最簡單的方式。
環境
檢查在一個固定的最小環境中運行:PATH、HOME,以及少量暫存/使用者金鑰,外加 LANG/LC_ALL=C、TZ=UTC、PYTHONHASHSEED=0、NO_COLOR、TERM=dumb。呼叫者環境中的所有其他項目都會被丟棄。需要更多設定的工具鏈可以明確傳遞:
uv run {baseDir}/scripts/post_patch_validation.py run \
--plan post-patch-validation/plan.json \
--output post-patch-validation/results \
--allow-env JAVA_HOME --allow-env CARGO_HOME
被轉發的名稱和值會記錄在 result.json 中。請求的變數若未設定則視為錯誤,而非空字串。兩類變數會被直接拒絕:可被解讀為憑證的名稱(*SECRET*、*TOKEN*、*API_KEY* 等),因為值會寫入結果;以及會改變執行內容的名稱(LD_PRELOAD、BASH_ENV、NODE_OPTIONS、GIT_SSH_COMMAND 等),因為這些變數可以改變將執行的程式碼。執行器的固定變數及所有 PPV_* 名稱也被保留,無法轉發。
可在 argv 和每項檢查的 env 值中展開的佔位符:{checkout}(正在測試的修訂版本)、{plan_dir}(供該次調用使用的隔離計畫成品副本)、{scratch}(一個全新的不透明目錄,供該次檢查調用使用),以及 {side}(base 或 patched,不適用於 exploit/variant 檢查)。相同的值也會作為 PPV_CHECKOUT、PPV_PLAN_DIR、PPV_SCRATCH、PPV_SIDE 和 PPV_CASE_ID 傳遞。僅寫入 {scratch};證據目錄路徑不會傳遞給檢查。基礎版本與修補版本的調用不共享執行器管理的暫存、計畫或工作目錄根。每次調用退出後,其暫存樹會歸檔到確定性路徑 results/scratch/<check-id-and-side> 下,其私有計畫副本會被丟棄,且每個可讀取且解析為檔案的 argv 元素都會在 argv_files 中進行雜湊處理。隔離計畫或檢出目錄中的檔案會額外保留在 results/helpers/<sha256> 下(最多 16 MiB);記錄會說明任何其他檔案未被歸檔的原因。請為 plan.json 使用專用目錄:其同級檔案和目錄會被複製到每次調用的 {plan_dir} 中。將輔助程式的程式碼放在該目錄的 checks/ 目錄下,或簽入目標存放庫,以便其位元組可供審查。包含提交鎖定的機器計畫、當前輸出目錄以及偵測到的先前結果樹會被排除;符號連結會被拒絕。乾淨的快照僅保留在執行器記憶體中,且漏洞利用/變種兩側會以隨機順序執行,同時證據檔案名稱保持確定性。stdout/stderr 使用匿名或隨機命名的擷取描述子,並僅在子行程退出後才複製到命名的證據檔案中,因此 fd 檢查無法洩漏側標籤。
此隔離並非主機沙箱:檢查以呼叫者的權限執行,惡意輔助程式可能使用任意外部狀態或刻意從原始碼或 Git 元資料推斷修訂版本。請檢查以內容定址的輔助成品,當檢查程式碼本身不受信任時,請使用作業系統/容器沙箱。
正在進行驗證的工作目錄會以隨機的擁有者權杖(由核心檔案鎖支援)進行 Git 鎖定,因此另一個並行驗證者無法清除它們,且 PID 重用無法冒充擁有者。如果執行器被強制終止,下一次運行會解鎖陳舊的驗證者註冊。如需手動復原,請檢查 git worktree list,然後使用 git worktree unlock <path> 和 git worktree remove --force <path>(或在路徑消失後使用 git worktree prune)。
在設計覆蓋率、選擇變種或解讀發現和驗證缺口時,請閱讀 evidence-model.md。請勿為例行 CLI 執行而閱讀它。
覆蓋率規則
- 從根本原因推導變種,而非原始承載的外觀變異。
- 列舉受修復影響的同級呼叫點、替代回呼/輸出、錯誤路徑、拆卸、所有權、序列化以及邊界值。
- 使每個漏洞利用或變種測試都斷言安全行為。它必須在有漏洞的基礎版本上失敗;在兩個版本上都通過的測試無法證明任何修復措施。請閱讀基礎版本的 stderr 並確認失敗是由於你撰寫的斷言,而非從未執行到的測試載體。
- 將漏洞利用和變種斷言限制在安全不變量內。將活躍度、確切錯誤類型/訊息、時序和相容性分別作為
behavior或regression檢查來測試;否則不相關的合約變更可能偽裝成漏洞仍然存在的證據。 - 保持
control測試載體為良性,並使其執行被變更的元件。它證明測試載體在兩個版本上都能運作。控制組失敗會留下缺口,並阻止將其他失敗歸因於修補程式。原始觀察結果仍可供審查。 - 僅對應保持不變的行為使用
behavior。精確輸出比較是刻意的;將不穩定的值移至確定性測試載體後方,而非在文字中將其標準化。 - 在將修補後的失敗視為新引入的問題之前,請讓
security檢查在易受攻擊的基礎版本上通過。基礎版本失敗會使歸因無法解決。 - 在驗證期間請勿編輯修補程式。將失敗回傳給修補程式作者,並開始一個新的、重新鎖定的運行。
解讀結果
result.json schema 2.0 包含一個 assessment,其中包含 status、findings、gaps 和 human_review_required。當所有必要的檢查都產生了可用的證據時,status 為 complete,即使某些檢查發現了失敗。當有任何缺口存在時,status 為 incomplete。findings 指出檢查 ID、檢查類別和失敗的期望。gaps 指出檢查 ID 和缺失的證據,對於運行級別的問題(例如清理失敗),檢查 ID 為 null。
套件檢查會先在修補版本上執行。若發生完成失敗,則會對基礎版本觸發相同的檢查。如果基礎版本通過,則回報修補後的失敗。如果兩者都失敗,則保留兩份日誌並回報歸因無法解決。請勿假設匹配的退出碼代表相同的失敗。
執行器在檢查完整且無發現時退出 0,檢查完整但有發現時退出 1,驗證不完整時退出 10,輸入無效時退出 64。即使退出碼為 10,也請閱讀成品:它可能包含受支援的發現和缺口。原始碼或建置證據無法建立執行時行為。請在每個通過的結果旁說明證據等級。
Claude 動態工作流程
Claude Code 將捆綁的工作流程公開為 /post-patch-validation:validate-patch。要透過 Workflow 工具傳遞結構化輸入,請使用不帶前導斜線的名稱:
Workflow({
name: 'post-patch-validation:validate-patch',
args: {
finding: '<finding text or local path>',
baseRef: '<vulnerable-ref>',
patchRef: '<patched-ref>',
workdir: 'post-patch-validation',
},
})
請在適當時使用 patchFile 替代 patchRef。該工作流程使用固定的覆蓋率視角來提出檢查,並使用固定的執行器來執行此技能。代理可以撰寫測試成品。Python 執行器會記錄發現與缺口,審查者則分別報告覆蓋率或證據問題。NEEDS_REPAIR 會回傳受支援的失敗,即使其他檢查留下了缺口。BLOCKED 表示證據不完整且沒有受支援的失敗。僅在證據審查者都批准後,通過的檢查才會推進到 READY_FOR_HUMAN_REVIEW,否則為 REVIEW_REQUIRED。工作流程在啟動後無法提問,因此請事先傳遞所有輸入。
應拒絕的合理化說詞
| 合理化說詞 | 必要回應
…
[已截斷以進行翻譯]