Code Auditor
Un agent LangGraph qui traite un dépôt comme une entrée hostile
Code Auditor prend une URL Git ou un dossier local et part du principe du pire. Il recherche malwares et secrets avant que quoi que ce soit d’autre ne touche au code, le cartographie et l’analyse sans jamais l’exécuter sur l’hôte, construit une feuille de route classée par risque, puis corrige un élément à la fois dans une sandbox. Chaque modification attend l’approbation d’une personne.
- Type
- Projet personnel, 2026, conçu et réalisé de bout en bout
- Agent
- Machine à états LangGraph, 11 nœuds, checkpoints SQLite
- Isolation
- Conteneurs Docker sans réseau, avec plafonds de mémoire, de CPU et de processus
- Modèles
- Claude Sonnet pour la feuille de route et les correctifs, en sortie structurée dans les deux cas
- Tests
- 478 fonctions de test backend, 18 tests frontend
Prototype. Chaque étape est testée avec des doublures du modèle et des conteneurs, et les scanners avec les vrais outils. La boucle de correction n’a pas encore tourné de bout en bout avec un vrai modèle dans Docker.
Du dépôt à la feuille de route
La première moitié d’une exécution lit le dépôt et ne l’exécute jamais. Chaque étape est un nœud d’une machine à états LangGraph figée, et non une boucle d’agent libre : aucun discours ne peut donc contourner l’ordre des contrôles.
- Entrée ou sortie
- Appel LLM
- Code
- Contrôle
- Dépôt Une URL Git, clonée avec tout son historique dans un espace de travail jetable (timeout de 600 s). Un dossier local est copié sans suivre les liens symboliques.
- Scan préalable ClamAV, des règles YARA et TruffleHog tournent dans un conteneur sans réseau. Le contrôle est en échec fermé (fail closed) et se fie aux détections, pas à l’indicateur « passed » du scanner. Ici : aucune détection.
- Exploration Cartographie les modules, le graphe de dépendances, les points d’entrée et les tests en analysant le code avec ast. Rien n’est importé ni exécuté. Plafonds : 2 Mo par fichier, 20 000 fichiers.
- Diagnostic ruff, mypy et radon tournent dans un conteneur de 4 Go sans réseau ; chaque constat revient avec une sévérité.
- Audit de sécurité bandit, semgrep, osv-scanner et gitleaks, avec un score CVSS. Le niveau Critique est réservé aux outils de sécurité ; un constat Critique s’arrêterait ici pour attendre un humain.
- Plan Les constats sont condensés (150 listés un par un, le reste regroupé) et Claude Sonnet renvoie une feuille de route typée. Du code recalcule ensuite chaque sévérité au lieu de faire confiance au modèle.
- Feuille de route Des éléments avec sévérité, priorité, justification et fichiers concernés, prêts pour la boucle de correction. Sans modèle, ou en cas d’erreur du modèle, une feuille de route à base de règles est construite à la place.
Cette requête ne passe pas par ici. Les autres chemins sont listés plus bas.
Voilà toute la requête. Envie de voir tourner quelque chose de ce genre sur vos données ?
Sélectionnez une case du schéma, ou suivez la requête étape par étape.
Les autres chemins du graphe
- La moindre détection au scan préalable, ou l’absence de tout résultat : quarantaine, arrêt, et un rapport qui dit pourquoi.
- Un constat de sécurité Critique : l’exécution s’arrête à la validation humaine, avant toute planification.
- Aucun constat, ou une feuille de route vide : passage direct au rapport.
- Plafond de coût atteint pendant la planification : une feuille de route à base de règles, et l’exécution est marquée comme interrompue.
La boucle de correction, avec un humain dedans
La seconde moitié modifie le code, un élément de la feuille de route à la fois. Le modèle écrit des fichiers ; du code décide s’ils peuvent être écrits sans danger, une sandbox décide s’ils fonctionnent, et une personne décide s’ils restent.
- Entrée ou sortie
- Appel LLM
- Code
- Contrôle
- Élément à traiter L’élément le plus prioritaire. Le périmètre et le plafond de coût de 10 $ sont vérifiés avant tout appel au modèle.
- Action Jusqu’à 4 fichiers entiers partent vers le modèle, qui renvoie de nouveaux fichiers complets, jamais de diffs ni de commandes shell. Chaque fichier est validé, sauvegardé par snapshot puis écrit de façon atomique ; le diff est produit par difflib.
- Vérification pytest tourne dans une sandbox sans réseau, sur un montage en lecture seule de l’espace de travail. Un test échoue.
- Action Deuxième tentative. Chaque élément en a 3 en tout, la première comprise.
- Vérification Les tests passent. « Invérifiable » est distingué de « échoué » et n’est jamais retenté.
- Valid. humaine Le graphe se met en pause avec interrupt() jusqu’à ce qu’un relecteur approuve ou rejette. La décision doit aussi exister sous forme de ligne en base, et c’est la ligne qui fait foi.
- Rapport Après le dernier élément, un rapport Markdown produit par du code, pas par un modèle. « Corrigé » veut dire approuvé, vérifié et non annulé par rollback.
Cette requête ne passe pas par ici. Les autres chemins sont listés plus bas.
Voilà toute la requête. Envie de voir tourner quelque chose de ce genre sur vos données ?
Sélectionnez une case du schéma, ou suivez la requête étape par étape.
Les autres chemins du graphe
- Toujours en échec après 3 tentatives, ou plafond d’itérations atteint : rollback à l’octet près, puis validation humaine.
- Rejet sans commentaire : la modification est annulée. Rejet avec commentaire : retour à l’étape Action avec un nouveau budget de tentatives.
- Rien n’a été écrit sur le disque : la validation laisse l’exécution continuer sans s’arrêter.
Confinement
Le dépôt n’est pas digne de confiance, la sortie du modèle non plus. Les deux sont tenus en laisse courte.
-
Quatre types de conteneur
Scan préalable (2 Go), analyse (4 Go), sécurité (6 Go) et sandbox de test (2 Go), chacun sans réseau, avec 2 CPU, 512 processus et un timeout compris entre 10 et 30 minutes.
-
Écrire sur l’hôte, exécuter dans la boîte
Les modifications sont écrites dans l’espace de travail, sur l’hôte ; le code ne s’exécute jamais ailleurs que dans la sandbox, sur un montage en lecture seule. Les snapshots vivent hors du montage.
-
Des limites strictes par exécution
50 itérations, une heure de temps réel, un plafond de coût de 10 $ vérifié avant chaque appel au modèle, et une limite de récursion LangGraph de 200.
-
Des outils aux versions figées
ruff 0.9.10, mypy 1.15.0, radon 6.0.1, bandit 1.8.6, semgrep 1.172.0, gitleaks 8.24.0, osv-scanner 2.4.0, TruffleHog 3.88.0, pytest 8.3.4.
Ce qu’un vrai dépôt a montré
Une première exécution manuelle sur un vrai dépôt de 56 fichiers, sans Docker ni modèle, est consignée dans les notes du projet. 134 de ses 137 constats de sécurité étaient des B101 de bandit, une simple instruction assert, et aucun des 17 éléments de la feuille de route n’entrait dans la liste d’autorisation par défaut du correcteur.
Le bruit et le périmètre, et non la qualité du modèle : voilà ce qu’un outil de ce genre doit d’abord maîtriser.
Choix d’ingénierie
-
Une machine à états, pas un agent libre
Des nœuds fixes aux rôles étroits. Le modèle planifie et écrit des fichiers ; il ne choisit jamais le prochain contrôle à exécuter.
-
Échec fermé (fail closed)
Un résultat de scan préalable manquant vaut un échec. Ce sont les détections des scanners qui décident, pas leurs indicateurs de sortie.
-
La sévérité, dans le code
Le modèle peut suggérer une sévérité ; le code la recalcule d’après l’origine du constat. Le niveau Critique n’appartient qu’aux outils de sécurité.
-
Des fichiers entiers, pas des patchs
Le modèle renvoie des fichiers complets. Les diffs sont calculés par difflib, et une sortie dangereuse est refusée avant toute écriture.
-
Pas d’approbation sans trace
Un interrupt LangGraph met l’exécution en pause, et la reprise exige une ligne d’approbation en base. Pas de ligne, pas de reprise.
-
Un rapport sans modèle
Le rapport final est produit par du code à partir des événements enregistrés : il ne peut donc pas exagérer ce qui a été corrigé.
Technologies
- Agent
- LangGraph, checkpoints SQLite, SDK Anthropic (sortie structurée)
- Analyse
- ruff, mypy, radon, bandit, semgrep, osv-scanner, gitleaks
- Scan préalable
- ClamAV, YARA, TruffleHog
- Backend
- Python 3.12, FastAPI, WebSockets, SQLModel, Alembic, SDK Docker
- Frontend
- React 19, TypeScript, Vite, Tailwind CSS 4
- Qualité
- pytest, Vitest, tests de parseurs sur de vraies sorties d’outils capturées
Vous construisez quelque chose de similaire ?
Je détaille volontiers la conception, les compromis, ou ce que je changerais ensuite.