Validation post-correctif
Valider un correctif de sécurité par rapport au bogue signalé et au code environnant qu'il affecte. Fournissez à l'auteur du correctif des échecs reproductibles à corriger et identifiez ce qui nécessite encore des tests. Appliquez les mêmes vérifications aux correctifs humains et aux correctifs générés par des agents. Le diff, l'auteur, l'implémentation en amont et la preuve de concept originale ne peuvent à eux seuls établir la correction.
Quand l'utiliser
- Un correctif de sécurité, un commit de correction, un fichier de correctif ou une pull request existe déjà.
- Un correctif généré par une IA nécessite une validation avant une relecture humaine ou une fusion.
- Un correctif peut couvrir un chemin d'exploitation tout en ignorant des variantes de la même cause racine.
- Un correctif de sécurité peut altérer un comportement légitime ou introduire une nouvelle vulnérabilité.
- Un auteur de correctif a besoin d'échecs concrets et de lacunes de couverture avant une autre révision.
Quand NE PAS l'utiliser
- Aucun correctif n'existe encore ; utilisez d'abord la découverte de vulnérabilité ou l'implémentation du correctif.
- La tâche consiste à examiner une constatation d'audit par rapport à un rapport sans exécuter de preuves de correctif.
- La tâche consiste uniquement à convertir une constatation en un test permanent du projet.
- La cible est distante ou en production. Cette compétence exécute uniquement du code local et des tests.
- L'utilisateur n'a pas autorisé l'exécution du code ou de la suite de tests du dépôt.
Démarrage rapide
-
Épinglez la base vulnérable et l'entrée corrigée. Préférez les commits immuables. Pour un travail non commité, créez d'abord un fichier de correctif binaire ; ne validez pas dans l'arbre de travail de l'utilisateur.
-
Échafaudez un plan épinglé :
uv run {baseDir}/scripts/post_patch_validation.py scaffold \ --repo . \ --base-ref <ref-vulnérable> \ --patched-ref <ref-corrigée> \ --finding-id <id-stable> \ --finding-summary "<cause racine et impact>" \ --evidence-level runtime \ --output post-patch-validation/plan.jsonUtilisez
--patch-file <chemin>au lieu de--patched-refpour un artefact de correctif. Choisissez le niveau de preuve le plus élevé honnête :sourcepour les invariants source/correctif uniquement,buildlorsque le code cible est compilé ou analysé mais que le comportement signalé n'est pas exécuté, ouruntimelorsque les vérifications exécutent le comportement signalé et ses assertions de sécurité. -
Inspectez la constatation, le diff, les appelants, les chemins frères, les chemins de nettoyage/d'erreur et les tests existants. Remplissez
checksdans le plan généré. Exécutezprint-schemapour le schéma structurel :uv run {baseDir}/scripts/post_patch_validation.py print-schema -
Exécutez
validate-planpour la validation complète, y compris la couverture, les restrictions de commande et les entrées épinglées, avant d'exécuter le code :uv run {baseDir}/scripts/post_patch_validation.py validate-plan \ --plan post-patch-validation/plan.json -
Exécutez le plan de preuve :
uv run {baseDir}/scripts/post_patch_validation.py run \ --plan post-patch-validation/plan.json \ --output post-patch-validation/results -
Rapportez
result.json,report.md, le niveau de preuve et l'assessmentcomplet. Renvoyez chaque constatation à l'auteur du correctif avec son ID de vérification, son assertion et les journaux sauvegardés. Identifiez chaque lacune de validation séparément, y compris les lacunes qui coexistent avec des constatations valides. Après que l'auteur a révisé le correctif, épinglez les nouvelles entrées et sauvegardez une nouvelle exécution de validation. Conservez les preuves antérieures. Les vérifications fournies qui réussissent nécessitent toujours une relecture humaine avant acceptation.
Contrat de preuve
L'exécuteur rejette les plans incomplets. Fournissez au moins une vérification de chaque type :
| Type | Observation requise |
|---|---|
control | Le harnais inoffensif réussit sur la base et le correctif |
exploit | L'assertion de sécurité d'origine échoue sur la base et réussit sur le correctif |
variant | Une variante distincte de la cause racine échoue sur la base et réussit sur le correctif |
behavior | Le comportement non affecté réussit avec une sortie sélectionnée identique octet par octet |
regression | La vérification de régression non ciblée réussit sur les deux révisions |
security | La vérification de vulnérabilité adjacente/nouvelle réussit sur la base et le correctif |
suite | La suite de tests existante, le désinfectant ou la campagne de fuzzing déterministe réussit sur le correctif |
Les commandes sont des tableaux argv, jamais des chaînes shell. Placez la configuration complexe dans un script vérifié ou un artefact de plan et invoquez-le avec {plan_dir}. L'exécuteur fixe les entrées de locale/fuseau horaire/graine de hachage, exécute les vérifications dans l'ordre lexical des ID, enregistre la sortie standard/erreur brute et ne modifie jamais l'arbre de travail d'origine. Le timeout_seconds de chaque vérification est par défaut de 300 et accepte des entiers de 1 à 3600. Dépasser le délai d'attente laisse une lacune de validation. Un délai d'attente seul n'établit pas une régression. Chaque plan contient également un tableau submodules trié ([] quand il n'y en a pas). L'échafaudage déduit les Gitlinks affectés à partir de l'inventaire des fichiers modifiés. L'exécuteur initialise ces commits épinglés à partir des objets de module Git existants du dépôt source, jamais à partir des URL réseau .gitmodules ; initialisez ou récupérez-les dans le dépôt source avant la validation.
Les vérifications d'exploit et de variante doivent prouver qu'elles ont été exécutées
Un code de sortie non nul ne signifie pas que la vulnérabilité a été reproduite. Une erreur d'importation, une construction échouée, une dépendance manquante et une assertion de sécurité échouée se terminent tous par un code non nul et sont indistinguables pour l'exécuteur. Chaque vérification exploit et variant doit imprimer et vider PPV_REACHED immédiatement avant d'évaluer son assertion, sur les deux révisions :
"argv": ["python3", "-c", "import app; value = app.render('<'); print('PPV_REACHED', flush=True); assert value == '<'"]
Le jeton est également dans l'environnement en tant que PPV_REACHED_MARKER. Il doit atterrir sur la sortie standard, sur sa propre ligne. La sortie d'erreur n'est pas analysée, car une trace d'erreur SyntaxError de Python fait écho à la source incriminée et satisferait autrement la vérification pour un harnais qui n'a rien exécuté. Une exécution sans cela laisse une lacune marker_missing. Les constatations indépendantes d'autres vérifications restent dans le résultat. Videz explicitement : un harnais dont la charge utile plante ou appelle _exit perd la sortie mise en mémoire tampon et renonce à ses propres preuves.
Ces vérifications s'exécutent également en aveugle par rapport au côté. {side} n'est pas développé pour elles, PPV_SIDE est absent de leur environnement, le répertoire de vérification est nommé aléatoirement et le validateur de plan rejette toute vérification exploit ou variant dont argv ou env mentionne l'un ou l'autre. Une assertion qui peut voir sur quelle révision elle se trouve peut affirmer cela au lieu du code, ce qui est le moyen le moins cher possible de simuler une reproduction suivie d'un correctif.
Environnement
Les vérifications s'exécutent sous un environnement minimal fixe : PATH, HOME et une poignée de clés temporaires/utilisateur, plus LANG/LC_ALL=C, TZ=UTC, PYTHONHASHSEED=0, NO_COLOR, TERM=dumb. Tout le reste dans l'environnement de l'appelant est supprimé. Les chaînes d'outils qui ont besoin de plus l'obtiennent explicitement :
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
Les noms et valeurs transmis sont enregistrés dans result.json. Une variable demandée qui n'est pas définie est une erreur, pas une chaîne vide. Deux classes sont refusées catégoriquement : les noms qui ressemblent à des informations d'identification (*SECRET*, *TOKEN*, *API_KEY*, …), car la valeur serait écrite dans le résultat ; et les noms qui modifient ce qui s'exécute (LD_PRELOAD, BASH_ENV, NODE_OPTIONS, GIT_SSH_COMMAND, …), car ces variables peuvent modifier le code qui s'exécute. Les variables fixes de l'exécuteur et chaque nom PPV_* sont également réservés et ne peuvent pas être transmis.
Les espaces réservés développés dans argv et les valeurs env par vérification : {checkout} (la révision en cours de test), {plan_dir} (une copie isolée des artefacts du plan pour cette seule invocation), {scratch} (un répertoire opaque frais pour cette seule invocation de vérification) et {side} (base ou patched, et non disponible pour les vérifications exploit/variant). Les mêmes valeurs arrivent sous les noms PPV_CHECKOUT, PPV_PLAN_DIR, PPV_SCRATCH, PPV_SIDE et PPV_CASE_ID. Écrivez uniquement sous {scratch} ; le chemin du répertoire de preuves n'est pas passé aux vérifications. Les invocations de base et corrigées ne partagent pas les racines de travail, de plan ou de scratch gérées par l'exécuteur. Après la sortie de chaque invocation, son arbre scratch est archivé sous le chemin déterministe results/scratch/<id-de-vérification-et-côté>, sa copie de plan privée est supprimée et chaque élément argv lisible qui résout un fichier est haché dans argv_files. Les fichiers à l'intérieur des racines de plan ou de vérification isolées sont en outre conservés sous results/helpers/<sha256> jusqu'à 16 Mio ; l'enregistrement explique pourquoi tout autre fichier n'a pas été archivé. Utilisez un répertoire dédié pour plan.json : ses fichiers et répertoires frères sont copiés dans {plan_dir} de chaque invocation. Gardez le code d'assistance sous le répertoire checks/ de ce répertoire ou vérifié dans le dépôt cible afin que ses octets soient examinables. Le plan machine contenant les épingles de commit, le répertoire de sortie actuel et les arbres de résultats antérieurs détectés sont exclus ; les liens symboliques sont rejetés. L'instantané propre ne reste que dans la mémoire de l'exécuteur, et les côtés exploit/variant s'exécutent dans un ordre aléatoire tandis que les noms de fichiers de preuve restent déterministes. La sortie standard/erreur utilise des descripteurs de capture anonymes ou nommés aléatoirement et sont copiés vers les fichiers de preuve nommés uniquement après la fin du processus fils, de sorte que l'inspection du descripteur de fichier ne peut pas divulguer l'étiquette du côté.
Cet isolement n'est pas un bac à sable hôte : les vérifications s'exécutent avec les privilèges de l'appelant et un assistant malveillant pourrait utiliser un état externe arbitraire ou déduire délibérément la révision à partir de la source ou des métadonnées Git. Inspectez les artefacts d'assistance adressés par contenu et utilisez un bac à sable OS/conteneur lorsque le code de vérification lui-même n'est pas fiable.
Les arbres de travail de validation actifs sont verrouillés par Git avec des jetons de propriétaire aléatoires soutenus par des verrous de fichiers noyau, de sorte qu'un autre validateur simultané ne peut pas les élaguer et la réutilisation du PID ne peut pas usurper un propriétaire. Si l'exécuteur est tué de force, l'exécution suivante déverrouille les enregistrements de propriétaires obsolètes. Pour une récupération manuelle, inspectez git worktree list, puis utilisez git worktree unlock <chemin> et git worktree remove --force <chemin> (ou git worktree prune après la suppression du chemin).
Lisez evidence-model.md lors de la conception de la couverture, de la sélection des variantes ou de l'interprétation des constatations et des lacunes de validation. Ne le lisez pas pour une exécution CLI de routine.
Règles de couverture
- Dérivez les variantes de la cause racine, pas des mutations cosmétiques de la charge utile d'origine.
- Énumérez les sites d'appel frères, les rappels/sorties alternatifs, les chemins d'erreur, le démontage, la propriété, la sérialisation et les valeurs limites touchés par le correctif.
- Faites en sorte que chaque test d'exploit ou de variante affirme le comportement sûr. Il doit échouer sur la base vulnérable ; un test qui réussit sur les deux révisions ne prouve rien concernant la correction. Lisez la sortie d'erreur du côté de base et confirmez que l'échec est l'assertion que vous avez écrite, pas un harnais qui n'est jamais arrivé là.
- Limitez les assertions d'exploit et de variante à l'invariant de sécurité. Testez la vivacité, les types/messages d'erreur exacts, le timing et la compatibilité séparément en tant que vérifications
behaviorouregression; sinon, un changement de contrat sans rapport peut se faire passer pour une preuve que la vulnérabilité persiste. - Gardez le harnais
controlinoffensif et faites-lui exercer le composant modifié. Il établit que le harnais fonctionne sur les deux révisions. Les contrôles échoués laissent des lacunes et empêchent d'attribuer d'autres échecs au correctif. Les observations brutes restent disponibles pour examen. - Utilisez
behavioruniquement pour un comportement qui doit rester inchangé. La comparaison exacte de la sortie est délibérée ; déplacez les valeurs instables derrière un harnais de test déterministe au lieu de les normaliser dans la prose. - Faites en sorte que les vérifications
securityréussissent sur la base vulnérable avant de traiter un échec corrigé comme nouvellement introduit. Une base de référence échouée laisse l'attribution non résolue. - Ne modifiez pas le correctif pendant la validation. Renvoyez les échecs à l'auteur du correctif et commencez une nouvelle exécution fraîchement épinglée.
Lecture du résultat
Le schéma result.json 2.0 contient une assessment avec status, findings, gaps et human_review_required. Le statut est complete lorsque toutes les vérifications requises ont produit des preuves utilisables, même si certaines vérifications ont trouvé des échecs. Le statut est incomplete lorsqu'il reste une lacune. Les constatations nomment l'ID de vérification, le type de vérification et l'attente échouée. Les lacunes nomment l'ID de vérification et la preuve manquante, avec un ID de vérification nul pour les problèmes au niveau de l'exécution tels que les échecs de nettoyage.
Les vérifications de suite s'exécutent d'abord sur la révision corrigée. Un échec terminé déclenche la même vérification sur la base de référence. Si la base de référence réussit, signalez l'échec après le correctif. Si les deux échouent, conservez les deux journaux et signalez que l'attribution n'est pas résolue. Ne supposez pas que des codes de sortie correspondants signifient le même échec.
L'exécuteur se termine avec le code 0 pour les vérifications complètes sans constatation, 1 pour les vérifications complètes avec constatation, 10 pour une validation incomplète et 64 pour des entrées invalides. Lisez l'artefact même après le code de sortie 10 : il peut contenir des constatations valides accompagnées de lacunes. Les preuves source ou de construction ne peuvent pas établir un comportement d'exécution. Indiquez le niveau de preuve à côté de tout résultat réussi.
Workflow dynamique Claude
Claude Code expose le workflow intégré sous le nom /post-patch-validation:validate-patch. Pour passer des entrées structurées via l'outil Workflow, utilisez le nom sans barre oblique initiale :
Workflow({
name: 'post-patch-validation:validate-patch',
args: {
finding: '<texte de constatation ou chemin local>',
baseRef: '<ref-vulnérable>',
patchRef: '<ref-corrigée>',
workdir: 'post-patch-validation',
},
})
Utilisez patchFile au lieu de patchRef le cas échéant. Le workflow utilise des lentilles de couverture fixes pour proposer des vérifications et un exécuteur fixe pour exécuter cette compétence. Les agents peuvent rédiger des artefacts de test. L'exécuteur Python enregistre les constatations et les lacunes, et les réviseurs signalent séparément les problèmes de couverture ou de preuve. NEEDS_REPAIR renvoie les échecs valides même lorsque d'autres vérifications ont laissé des lacunes. BLOCKED signifie que la preuve est incomplète sans un échec valide. Les vérifications réussies avancent vers READY_FOR_HUMAN_REVIEW uniquement après que les deux examens de preuve ont approuvé, sinon REVIEW_REQUIRED. Le workflow ne peut pas poser de questions après le lancement, alors transmettez toutes les entrées dès le départ.
Rationalisations à rejeter
| Rationalisation | Réponse requise |
|---|---|
| "Le correctif semble correct" | Spécifiez quelle assertion pourrait échouer sur la base vulnérable ; nous ne validons pas les correctifs en fonction de leur apparence." |
| "Les tests de la suite existante passent" | Les tests de la suite n'exercent pas le cas de sécurité signalé ; prouvez-le avec un exploit ou une variante qui échoue sur la base." |
| "Ce n'est pas le bon endroit pour corriger" | Proposez l'endroit ; validez-le après avoir implémenté le correctif à cet endroit." |
| "Un réviseur humain a déjà approuvé" | La relecture humaine est orthogonale à la preuve d'exécution ; validez d'abord, puis soumettez pour relecture." |
| "La vulnérabilité n'est pas exploitable" | Spécifiez l'invariant de sécurité violé et prouvez qu'il est rétabli par le correctif." |
| "Le test échoue pour une raison non liée" | Isolez l'assertion de sécurité ; si elle ne peut pas être isolée, signalez la lacune et la raison." |
| "Nous avons un autre système de validation" | Chaque système a des hypothèses de couverture différentes ; exécutez les deux et comparez les lacunes." |