Skip to content

Le caviardage repérait et RECONSTRUISAIT — il repère et découpe - #21

Merged
Palenza merged 2 commits into
travailfrom
raptor/caviardage
Aug 2, 2026
Merged

Le caviardage repérait et RECONSTRUISAIT — il repère et découpe#21
Palenza merged 2 commits into
travailfrom
raptor/caviardage

Conversation

@Palenza

@Palenza Palenza commented Aug 2, 2026

Copy link
Copy Markdown
Owner

La seule trouvaille critique de l'audit, réparée par la forme et par l'endroit.

Avant, sur le dépôt lui-même (aucun secret dedans) : 800 fichiers altérés sur 15 255, 459 lignes perdues. Un agent recevait du code dont les numéros de ligne ne collaient plus au disque.

La forme — on rend une liste de bornes, le hors-bornes est recopié à l'octet près. Corruption impossible par construction. $& inerte, chevauchements fusionnés, idempotent.

L'endroit — ce garde était écrit pour les journaux. Read/Grep/Glob sont désormais scannés mais jamais réécrits : caviarder un fichier local ne protège de rien et abîme la matière.

Deux secrets qui sortaient en clair, trouvés par le banc : R2_SECRET_ACCESS_KEY (la liste ignorait access_key et ne lisait que 2 segments de suffixe) et client_secret (ma règle « identifiant nu » rejetait 32 caractères de hasard).

⚠️ J'ai introduit cette seconde régression moi-même en réparant la première. D'où le banc à trois sens livré en test permanent : dépôt entier comme corpus sans secret (zéro ligne perdue = invariant), 13 secrets réels, idempotence. Il a attrapé la dernière corruption — un bloc PEM écrasé en une ligne.

Après : 26 fichiers sur 2 828, 0 ligne perdue, 13/13 secrets masqués, idempotent.

Portes : check 0, typecheck 0, suite 2796 (+25).

Enzo added 2 commits August 3, 2026 02:45
La seule trouvaille CRITIQUE de l'audit adversarial, réparée par la forme et
par l'endroit, pas par une expression régulière de plus.

MESURÉ AVANT, sur le dépôt lui-même — un corpus qui ne contient AUCUN secret :
800 fichiers sur 15 255 altérés, 459 lignes PERDUES, 9 509 modifiées. Trois
exemples suffisent : `id-token: none` → `id-token: ***` (il masquait le mot
« none »), `token: ${{ secrets.X }}` → YAML cassé, et `Authorization:` en fin
de ligne qui avalait le premier mot de la ligne suivante. Un agent recevait du
code dont les numéros de ligne ne collaient plus au disque.

LA FORME. On ne rend plus du texte reconstruit, on rend une LISTE DE BORNES
(spansSecrets.ts) ; le découpage recopie le hors-bornes à l'octet près. Trois
pièges se referment d'un coup : un remplacement passé en chaîne laissait `$&`
se ré-injecter (il est maintenant inerte, c'est un appel de fonction) ; deux
motifs qui se chevauchaient masquaient deux fois (les bornes fusionnent) ;
l'opération est idempotente. La corruption devient impossible PAR
CONSTRUCTION, pas par vigilance.

LE NOM NE DÉCIDE PLUS SEUL (valeurPlausible.ts). Une clé nommée `token` dont
la valeur est `none`, `${{ secrets.X }}` ou `mcpSession.authorizationHeader`
n'est pas un secret. Sauf sous un nom d'ENVIRONNEMENT en majuscules, où la
valeur EST le secret quelle que soit sa forme — c'est ce qui tranche le
conflit réel entre `ANTHROPIC_API_KEY="mots-avec-tirets"` (à masquer) et
`const secret = "mots-avec-tirets"` (à laisser).

L'ENDROIT, et c'est la moitié la plus importante. Ce garde a été écrit pour
les JOURNAUX ; on l'avait branché sur TOUT ce qu'un agent reçoit, y compris la
lecture de ses propres fichiers. Or caviarder un fichier local ne protège de
RIEN — le secret est déjà sur la machine — et abîme la matière de travail.
`Read`, `Grep`, `Glob` sont désormais SCANNÉS (l'alerte de contenu tiers
reste) mais jamais RÉÉCRITS.

DEUX SECRETS QUI SORTAIENT EN CLAIR, trouvés par le banc :
- `R2_SECRET_ACCESS_KEY` — la liste des noms connaissait `api_key` mais ni
  `access_key`, ni `secret_key`, et ne regardait que 2 segments de suffixe
  quand il en faut 3. C'est la famille des clés d'objet, donc celles du
  data-lake. Onze noms composés ajoutés, suffixe porté à 3.
- `client_secret: "aB3dEf9h…"` — ma propre règle « un identifiant nu n'est pas
  un secret » le rejetait : 32 caractères de hasard sont alphanumériques, donc
  indiscernables d'un nom de variable POUR UNE RÈGLE DE FORME. Le discriminant
  est la longueur, pas la forme.

⚠️ ET J'AI INTRODUIT CETTE RÉGRESSION MOI-MÊME. En réparant les faux positifs,
j'ai fait passer deux vrais secrets au travers sans m'en apercevoir. Une
relecture ne pouvait pas l'attraper : le caviardage doit être juste dans DEUX
directions opposées, et améliorer l'une dégrade souvent l'autre.

D'où LE BANC À TROIS SENS, livré comme test permanent
(bancCaviardage.test.ts) : le dépôt entier comme corpus sans secret (zéro
ligne perdue est un INVARIANT, pas un fil-piège), 13 secrets réels dont chacun
doit tomber, et l'idempotence. Il a immédiatement attrapé la dernière
corruption — un bloc PEM écrasé en une ligne, qui décalait le fichier. Les
clés privées sont maintenant masquées LIGNE PAR LIGNE : secret parti, compte
de lignes intact.

Après : 26 fichiers altérés sur 2 828, zéro ligne perdue, 13/13 secrets
masqués, idempotent. Le reliquat est composé de fixtures de test qui imitent
de vraies clés — les masquer est le comportement juste.

Portes : vp check 0, typecheck 0, suite complète verte — 2796 (+25).
Trouvaille de l'audit adversarial, mesurée sur les vraies réponses d'agent :
le plafond de 20 était atteint en HUIT HEURES, puis l'état restait saturé en
permanence — 3 389 promesses évincées sur 27 jours, sans une trace.

L'ironie est totale. Les nouvelles promesses entrent EN TÊTE et
`slice(0, 20)` garde les vingt premières : ce sont donc les plus ANCIENNES
qui tombent. Or cette fonctionnalité existe pour rattraper la promesse qu'on a
OUBLIÉE, et la plus ancienne est exactement celle-là. Elle jetait sa raison
d'être.

LA FORME ÉTAIT JUSTE, L'ENDROIT NON. Le commentaire d'origine disait le vrai
motif : « au-delà, ce n'est plus un rappel, c'est du bruit qu'on cesse de
lire ». C'est un argument d'AFFICHAGE, appliqué au STOCKAGE. Et l'affichage a
été réparé ailleurs depuis — le panneau est replié par défaut, borné en
hauteur et défilant. Le plafond ne protégeait donc plus rien : il ne restait
que le broyeur.

Plafond porté à 200, aligné sur celui des promesses barrées.

Et surtout : ce qui est jeté se COMPTE désormais (`evincees`). Un plafond plus
haut ne fait que retarder la panne ; seul un compteur la rend visible. Zéro en
usage normal — s'il monte, on le saura au lieu de chercher une promesse qui
n'existe plus.

⚠️ Le golden codait « point 27 » en dur, dérivé de l'ancien plafond : il
tombait au premier changement de constante alors que le comportement, lui,
était juste. Il se dérive maintenant du plafond, et il vérifie EN PLUS le
compteur d'éviction — il prouve plus qu'avant.

Portes : check 0, typecheck 0, suite complète verte — 2796.
@Palenza
Palenza merged commit f253bf9 into travail Aug 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant