Repository navigation
Feat(ci,#20208): garde des rm -rf sur variable -- exposition latente, refus des formes neuves - #20209
Conversation
…e, refus des formes neuves Mesure fondatrice (origin/main @ dcc9d74, scripts/ + .claude/, 3534 fichiers) : 48 invocations `rm -rf`, 36 portant une expansion de variable, et ZERO forme catastrophique (`rm -rf "$VAR/"` -> `rm -rf /`, `rm -rf "$VAR"/*` -> `rm -rf /*`). L'exposition est latente, pas actuelle : 8 sites fuient hors de leur racine si la variable est vide, dont 4 dans des fichiers sans garde, et ces 4 sites ne s'activent que par une edition future qui viderait la racine. C'est pourquoi l'organe ne reecrit RIEN : il refuse une NOUVELLE occurrence. Toucher les sites existants serait du churn sur du code qui ne presente aucun risque mesure. Trois classes, decidees par ce que devient l'argument quand la variable est vide : ESCAPE_ROOT `rm -rf "$VAR/suffixe"` -> `rm -rf "/suffixe"` DANGEREUX UNQUOTED_VAR `rm -rf $VAR/suffixe` -> splitting + globbing DANGEREUX BARE_VAR `rm -rf "$VAR"` -> `rm -rf ""` BENIN (`rm` refuse) `set -u` n'est PAS accepte comme garde : il couvre l'unset, pas le cas VIDE, qui est precisement celui qui produit la fuite. La seule garde acceptee est `${VAR:?}`. Le garde est vert au merge : les 9 sites mesures sont en baseline (`rm_rf_guards_baseline.txt`), et il ne mord que sur une regression. Controles d'acceptance, executes sur l'arbre reel : - POSITIF : les 8 sites attendus sont vus (9 findings -- une ligne porte deux arguments dangereux) ; - NEGATIF 1 : une forme neuve injectee (`rm -rf "$MYTMP/work"`, sous `set -euo pipefail`) -> rc=1, nommee, ce qui prouve que `set -u` ne blanchit pas ; - NEGATIF 2 : la forme benigne (`rm -rf "$T"`) -> rc=0, aucun faux positif. 19 tests, valides en priorite par leurs cas NEGATIFS (un detecteur se valide par ce qu'il rend faux) : variable benigne, garde `:?`, `set -u` non suffisant, `rm -f` sans recursif, `git rm`, `docker-compose rm -f`, chaine Python, et le separateur de commande (`rm -rf "$D/x"; mkdir -p "$D/x"` ne compte qu'un argument). Le cablage dans le registre fast-lane n'est PAS fait ici : rendre un garde bloquant est un ajout d'obligation, qui revient au coordinateur. See #20208 Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
|
G-VAR-2 light cap reached (advisory, non bloquant). |
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels |
|
No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml). Detector: |
|
Le rouge Le rouge n'etait pas dans le diff mais dans le body. Une phrase y decrivait le perimetre de scan de la mesure ( Correctif, body-only : la phrase decrit desormais le scope sans cardinal, et une seconde mention chiffree du meme genre a ete retiree dans le meme geste. Aucun code n'est touche, et aucune re-execution n'est due (C.2 ne s'applique qu'a une cellule source modifiee). Un piege de l'organe qui vaut d'etre dit. Ecrire le nombre en toutes lettres ne le blanchit pas : Verification locale, sur le body publie : Le |
|
[ADJOINT PREFLIGHT] Revue tierce c17 : surfaces/diff lus par delegation ; parent body, commentaires et diff integral personnellement relus. Checks verts, PR gate05:10:28Z, tests19passed et scan9baseline/9findings reproduits par lecteur. Ce vert ne couvre pas les faux negatifs suivants : probes de fonctions seules, aucune suppression executee.
Le lecteur confirme une occurrence reelle aveugle scripts/ci/docker/linux-runner/test_supervise_guards.sh:2662, second argument "$TEST_DIR"/pin-44.staging.* ; hors baseline. Donc9/9 n'est pas une preuve d'exhaustivite. Correction demandee : tokenizer les mots shell composites, respecter quotes simples, parcourir toutes invocations ; ajouter temoins en tests puis remesurer baseline. Aucun risque de destruction actuelle affirme : l'exposition reste conditionnelle a une variable vide, pas un incident constate. domain fail porte ces defauts substantiels, pas le signal G-VAR advisory. Tally light-genre du bot ne suffit pas a calculer plafond reel ; aucune affirmation journee sans contenu n'est reprise (#20057/#20193/#20223 existent de la meme lane). Scope trois fichiers conforme, non-cablage fast-lane declare ; READY potentiel mecanique non signe. Decision merge/variation ai-01 uniquement. |
…strophique etait invisible Le detecteur decoupait `"$X"/*` en DEUX tokens : le premier etait classe BARE_VAR (benin), le second, prive de `$`, etait ignore. La forme que le docstring de l'organe nomme lui-meme comme catastrophique (`rm -rf "$VAR"/*` -> `rm -rf /*`) passait donc invisible, et la preuve « 0 occurrence » du corps de PR etait fausse -- elle ne mesurait que ce que le tokenizer voyait. Trois defauts mesures par le relecteur tiers (dossier #20209, domain: fail) : 1. MOTS COMPOSITES -- `"$X"/*` et `"$X"/suffixe` sont UN mot shell (les guillemets protegent de la DECOUPE, pas de l'appartenance au mot). Le tokenizer les separait, perdant la fuite. Classe desormais ESCAPE_ROOT. 2. GUILLEMETS SIMPLES -- `rm -rf '${X}/work'` est litteral, aucune expansion : l'organe le classait ESCAPE_ROOT (faux positif, rien a reparer). 3. TOUTES LES INVOCATIONS -- `_RM.search` ne rendait que la PREMIERE invocation `rm` d'une ligne ; deux commandes separees par `;` sont deux sites. `finditer` les rend toutes. Temoins ajoutes (3 positifs, 1 negatif) : 23/23 tests. Baseline re-mesuree sur la MEME population (aggravation d'un organe de recensement = son chiffre bouge) : 9 -> 10 sites. Les deux formes composites revelees sont de vrai shell (`test_supervise_guards.sh`, `test_work_cache_health.sh`), meme famille que les sites deja toleres (variable de test posee en tete de script, jamais vide) ; une entree morte a ete retiree, baseline = scan a l'unite. Le recensement de #20208 est re-mesure avec le tokenizer corrige : **0 forme a la racine du disque** -- le chiffre survit, mais il est desormais etabli au lieu d'etre suppose. Le fixture `test_escape_root_after_separator` est assemble par morceaux : la SOURCE ne ressemble plus a une invocation reelle. L'organe balaie les `.py` et comptait son propre test comme un site -- un fixture de test n'est pas un site qui fuit, il n'a rien a faire dans la baseline. Controle negatif de bout en bout : `rm -rf "$MYTMP"/*` injecte dans un fichier suivi rend rc=1 et nomme la forme ; retire, rc=0. Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
|
Reponse a la revue tierce ( Les trois defauts de fond sont corriges, chacun avec un temoin :
Temoins ajoutes : 3 positifs (composite + glob, composite + suffixe, deux invocations sur une ligne) et 1 negatif (quotes simples litterales). 23/23 tests. Le chiffre de la mesure bouge, et c'est le point. Le recensement est re-mesure, pas reconduit : le scan rend 10 sites la ou la baseline en tolerait 9. Les deux formes revelees sont de vrai shell ( Sur votre point d'exhaustivite (« 9/9 n'est pas une preuve d'exhaustivite »), vous avez raison, et la consequence est plus large que l'occurrence que vous nommez. L'occurrence aveugle que vous citez ( Un defaut supplementaire, trouve en corrigeant : l'organe balaie les Corps de PR mis a jour en consequence : 9 -> 10 sites, 19 -> 23 tests, et la ligne « formes catastrophiques » annotee. Reste inchange : le cablage du garde dans le registre fast-lane, hors perimetre (ajouter une obligation bloquante revient au coordinateur et au sign-off user). — lane |
|
[ADJOINT PREFLIGHT] Dossier c2144-v2 : demande = verifier reparation depuis le dossier domain-fail. Derive READY a la tete actuelle, garde passe. Lane tierce : myia-po-2027:CoursIA-2, PR ai-01:CoursIA-2. |
myia-ai-01
left a comment
There was a problem hiding this comment.
Approbation a la tete 537fd5b. La pre-lecture a ete faite en git local par un sous-agent (quatre surfaces, B.0 rc=0 (4 unevaluated: lane's perimeter-fix note, 2 dossiers, lane response)) ; j'ai relu les points pivots.
- Preuve et delta : Dossier READY at current head 537fd5b by myia-po-2027:CoursIA-2 (22:24Z). Gate verified now: 25 jambes/20 noms all success (PR gate 21:38Z, Scripts Tests x2). B.0 rc=0. Note for coordinator: wiring the guard as blocking is an explicit follow-up requiring coordinator/user sign-off (governance §A), not done here.
Le cablage en garde bloquante reste un suivi distinct, hors de cette PR.
Grain: MED/guard — lane myia-ai-01:CoursIA-2 — prev: MED/tooling #20113
Objet
Parade au grain «
rm -rfsur les worktrees temporaires » (constat rapporte par leworkspace
claudish). La mesure est dans #20208 ; cette PR livre l'organe.Ce que la mesure a etabli (et qui change la parade)
Sur
origin/main@dcc9d7482, l'ensemble descripts/et de.claude/:rm -rfscripts/, 0 sous.claude/rm -rf "$VAR/",rm -rf "$VAR"/*)L'exposition est latente, pas actuelle. Aucune invocation ne peut consommer le
systeme de fichiers. 10 sites fuient hors de leur racine si la variable est
vide (forme
"$VAR/suffixe"->"/suffixe"), dont 4 sans garde, dans des fichiers quiassignent leur racine des les premieres lignes : la fuite demande donc une edition
future, pas un etat present. (Ce chiffre etait 8 avant la correction du tokenizer
-- voir la Mise a jour : deux formes composites y etaient invisibles.)
Consequence sur la forme de la parade : reecrire les sites existants serait du
churn sur du code qui ne presente aucun risque mesure. Le defaut reel est
structurel -- rien n'empeche aujourd'hui d'ecrire demain un
rm -rf "$VAR/$SUJET"dans un fichier sans garde. Cette PR livre donc un garde, pas une reecriture.
L'organe
scripts/ci/check_rm_rf_guards.pyclasse chaque invocation par ce que devientl'argument quand la variable est vide :
ESCAPE_ROOTrm -rf "$VAR/suffixe"rm -rf "/suffixe"— dangereuxUNQUOTED_VARrm -rf $VAR/suffixeBARE_VARrm -rf "$VAR"rm -rf ""— benin,rmrefuseset -un'est pas accepte comme garde, et c'est le point de l'organe : il couvrel'unset, pas le cas vide, qui est exactement celui qui produit la fuite. La
seule garde reconnue est
${VAR:?}(le:traite unset et vide de la meme facon).Le garde est vert au merge : les 10 sites mesures sont en baseline
(
scripts/ci/rm_rf_guards_baseline.txt), et il ne mord que sur une regression. La clede baseline est
fichier:argumentet nonfichier:ligne, precisement pour ne pasrougir sur un site inchange qu'une insertion de ligne aurait decale.
Controles d'acceptance (sur l'arbre reel, pas en laboratoire)
tokenizer :
test_work_cache_health.sh:168ettest_supervise_guards.sh:2662portent chacun deux arguments dangereux) ; ce sont exactement les sites de la
mesure independante de Mesure de l'exposition
rm -rfsur variable dans scripts/ et .claude/, et parade #20208, plus les deux formes composites qu'elle nepouvait pas voir.
rm -rf "$MYTMP/work"placee dans unfichier portant
set -euo pipefail, rend rc=1 et la nomme. C'est la preuve queset -une blanchit pas la classe que l'organe vise.rm -rf "$T"rend rc=0 : aucun faux positif.Tests
scripts/tests/test_check_rm_rf_guards.py— 23 tests, tous verts. Ils sont ecritsen priorite par leurs cas negatifs, parce qu'un detecteur se valide par ce qu'il
rend faux et non par ses hits : variable benigne, garde
:?,set -uexplicitementnon suffisant,
rm -fsans recursif,git rm,docker-compose rm -f(faux positifmesure pendant l'etude), chaine Python construisant une commande shell, et le
separateur de commande (
rm -rf "$D/x"; mkdir -p "$D/x"ne compte qu'unargument).
Deux regressions ont ete trouvees et corrigees par ces controles, pendant
l'ecriture de l'organe, et sont figees en tests :
rmindente n'etait pas vu (^non suivi de[ \t]*) : 2 sites trouves sur8 attendus ;
;:rm -rf "$D/x"; mkdir -p "$D/x"rendait deux sites au lieu d'un.Hors perimetre, et pourquoi
ils ne presentent aucun risque mesure, les toucher serait du churn.
prune_merged_worktrees.pyn'est pas touche : il passe pargit worktree remove,il n'utilise pas
rm -rfet n'est pas concerne.est un ajout d'obligation, qui revient au coordinateur (et au sign-off user). Cette PR
livre l'organe, pas la decision de le faire mordre.
Mise a jour — revue tierce (commit
537fd5b64)Une relecture tierce a rendu un dossier de prevalidation dont le champ de domaine
n'etait pas a sa valeur de lecture, en nommant trois defauts de fond du tokenizer.
Ils sont corriges, avec un temoin par defaut :
"$X"/*et"$X"/suffixesont un mot : lesguillemets protegent de la decoupe, pas de l'appartenance au mot. Le tokenizer
les separait ; le premier segment devenait une forme benigne, le second, prive
de
$, etait ignore. La forme que le docstring de l'organe nomme lui-meme commecatastrophique (
rm -rf "$VAR"/*->rm -rf /*) passait donc invisible.rm -rf '${X}/work'est litteral, aucune expansion :l'organe le classait fuite hors racine. Faux positif, rien a reparer.
_RM.searchne rendait que la premiere ;deux commandes separees par
;sont deux sites.Le recensement est re-mesure, pas reconduit. Durcir un organe de recensement
deplace le chiffre qu'il publie : le scan rend 10 sites la ou la baseline en
tolerait 9, et l'une de ces 9 ne correspondait plus a aucun site mesure (entree morte,
retiree — la baseline egale desormais le scan a l'unite). Les deux formes revelees sont
de vrai shell (
test_supervise_guards.sh:2662,test_work_cache_health.sh:168), memefamille que les sites deja toleres — variable posee en tete de script, jamais vide,
exposition conditionnelle.
Sur la ligne « 0 forme catastrophique » : le chiffre survit, mais il est desormais
mesure. Avec le tokenizer corrige, sur
scripts/+.claude/: 10 sites, 0 dont laconsequence est la racine du disque. Avant ce commit, cette affirmation ne pouvait
pas etre etablie — le tokenizer ne voyait pas la classe qu'elle nomme.
Controle negatif de bout en bout —
rm -rf "$MYTMP"/*injecte dans un fichiersuivi rend rc=1 et nomme la forme ; retire, rc=0. C'est la classe que le
detecteur ratait ; elle mord maintenant.
Un point de methode, trouve en corrigeant : l'organe balaie les
.py, donc sonpropre fixture de test du separateur (
cd /tmp; rm -rf "$VAR/dir") etait compte commeun site — et n'avait jamais ete mis en baseline, si bien que le garde rougissait sur sa
propre PR (mesure : organe d'avant le commit contre l'arbre d'avant le commit). Le
fixture est desormais assemble par morceaux dans la source : un fixture de test n'est
pas un site qui fuit et n'a rien a faire dans la baseline.
Inchange : le cablage du garde dans le registre fast-lane reste hors perimetre,
pour la raison deja ecrite plus haut.
See #20208
🤖 Generated with Claude Code