Dernier article de cette série sur l'audit d'un service d'authentification Go. Après avoir couvert les patterns de sécurité, l'infrastructure mTLS, l'architecture CQRS, et la méthodologie d'audit, il reste une question : comment on documente tout ça pour les agents IA qui toucheront au code après nous ?
Le CLAUDE.md du projet faisait 296 lignes. Après l'audit, il en fait 142. Moins 52%. Et l'agent code mieux qu'avant.
Le réflexe "tout documenter"
Le pattern naturel : chaque fois que l'agent fait une erreur, on ajoute une ligne au CLAUDE.md. "Ne pas oublier le dummy hash sur le login." "Le CSRF middleware doit être après le session-load." "Les CRL sont checkées à deux endroits."
Résultat : un fichier qui grossit monotoniquement, jamais nettoyé. Chaque ajout est légitime individuellement. Mais collectivement, le ratio signal/bruit baisse à chaque ligne.
Le CLAUDE.md est un budget d'attention. Chaque ligne consomme du contexte.
Si tu mets 296 lignes, l'agent accorde autant d'importance à "le layout est dans /cmd"
(qu'il peut déduire en 2 secondes avec tree) qu'à "le CRL Number doit être
monotone croissant" (qui est un invariant de sécurité critique non déductible du code).
Le test de suppression
Pour chaque ligne du CLAUDE.md, une seule question :
Si je supprime cette ligne et qu'un dev senior lit le code, est-ce qu'il manquerait quelque chose ?
Ce qui échoue au test (= supprimable) :
- Layout tree —
treele donne en 1 commande - Stack technique —
go.modle dit - Comment lancer les tests —
Makefileougo test ./... - Patterns standard de Go — un dev senior les connaît
- "Utiliser context.Context partout" — c'est la convention Go par défaut
Ce qui passe le test (= à garder) :
- Invariants — "event-XOR-error sur les command handlers, jamais les deux"
- Gotchas — "le CRL check se fait à deux endroits, handshake ET middleware"
- Décisions non-évidentes — "PBES2 + attribut KSP au lieu de PBES1 pour les .p12"
- Security choices — "dummy hash Argon2 recalculé au boot avec les params courants"
- Regression anchors — "ne pas retirer le check monotone sur crl.Number"
Les 5 catégories qui survivent
Après le nettoyage, le CLAUDE.md ne contient plus que 5 types d'information :
1. Invariants architecturaux
Les règles qui, si elles sont violées, cassent la cohérence du système :
## Invariants
- Command handler : event XOR error, jamais les deux
- Projector : tous les side-effects dans une seule TX, sauf logout (best-effort)
- Session : cert serial stampé à la création, vérifié à chaque requête
2. Gotchas non déductibles du code
## Gotchas
- CRL check : VerifyConnection (handshake) + middleware HTTP (request-time)
Les deux sont nécessaires. Ne pas retirer l'un sans l'autre.
- CSRF middleware : APRES session-load, AVANT refresh-user. L'ordre compte.
- Login timing : le dummy hash DOIT utiliser les mêmes params que le vrai hasher.
3. Décisions de sécurité
## Security decisions
- .p12 : PBES2/AES-256 + attribut MS KSP (OID 1.3.6.1.4.1.311.17.1)
PAS de fallback PBES1/3DES, même si "ça marche sur Windows".
- Lockout : même status code (401) pour locked, wrong creds, unknown user.
- Audit log : login failures sur unknown users = slog only, PAS de DB row.
4. Regression anchors
## Ne pas retirer
- Test TestP12HasKSPAttribute : vérifie la structure .p12 pour Windows
- Test TestDummyHashUsesCurrentParams : évite la régression sur param bump
- Check crl.Number monotone : protège contre le rollback attack
- Guard SNI == Host : protège contre le misdirected request
5. Frontières de confiance
## Trust boundaries
- api.internal : mTLS required (serviceCAPool)
- admin.internal : mTLS required (adminCAPool, different CA)
- app.internal : no client cert (web users)
Ce qu'on a supprimé
154 lignes supprimées. Voici ce qu'elles contenaient :
- 42 lignes de layout tree (
tree -L 2les remplace) - 28 lignes de "comment faire X" (le Makefile les couvre)
- 31 lignes de patterns Go standard ("utiliser errgroup", "toujours defer Close()")
- 18 lignes de stack/dépendances (lisibles dans go.mod)
- 22 lignes de conventions de naming (déductibles du code existant)
- 13 lignes de documentation de CI (lisibles dans le pipeline YAML)
Avant / après : la différence en pratique
Avant le nettoyage, l'agent citait des lignes du CLAUDE.md pour justifier ses choix. Le problème : il citait les mauvaises lignes. "D'après le CLAUDE.md, la structure du projet est /cmd/server/main.go" — oui, merci, ça ne m'aide pas à ne pas casser le CRL check.
Après le nettoyage : moins de citations verbatim, plus de respect effectif des invariants. L'agent ne dit plus "d'après le CLAUDE.md". Il applique les contraintes parce qu'elles sont les seules informations disponibles — pas noyées dans du bruit.
Le counter-intuitif : moins d'instructions → meilleure compliance.
CLAUDE.md vs AGENTS.md vs README
La séparation des concerns s'applique aussi à la documentation pour agents :
| Fichier | Audience | Contenu |
|---|---|---|
| CLAUDE.md | Agent IA | Ce qu'il doit savoir pour NE PAS casser les choses |
| AGENTS.md | Agent IA | Ce qu'il doit savoir pour BIEN faire les choses (style, workflow) |
| README | Humain | Ce qu'un humain doit savoir pour comprendre le projet |
Le overlap entre ces fichiers est de la dette documentaire. Si la même information est dans le CLAUDE.md et le README, elle sera mise à jour dans l'un et oubliée dans l'autre. Un seul point de vérité par information.
Conclusion
Un CLAUDE.md efficace ne dit pas à l'agent comment coder. Il lui dit ce qu'il ne peut pas déduire du code lui-même. Tout le reste est du bruit qui dilue le signal.
L'audit de sécurité a produit des invariants, des gotchas, et des regression anchors. Le CLAUDE.md est le bon endroit pour les documenter — à condition de ne documenter que ça. Le test de suppression est le filtre : si un dev senior déduit l'information en 5 secondes, elle n'a pas sa place dans le CLAUDE.md.
C'est le dernier article de cette série "auth2". En 9 articles, on est passé d'un bug de conteneur PKCS#12 à la méthodologie d'audit et à la documentation pour agents IA. Le fil conducteur : chaque pattern de sécurité est simple en surface. La complexité est dans les couplages implicites — entre le conteneur et le provider, entre le timing et les params, entre le handshake et le middleware. Rendre ces couplages explicites, c'est le job.