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.

Un dépôt à travers l’audit
Dépôt URL Git ou dossier Scan préalable malwares et secrets Quarantaine arrêt de l’exécution Rapport motif de l’arrêt Exploration carte ast, sans import Diagnostic ruff, mypy, radon Audit de sécurité bandit, semgrep, osv Valid. humaine constats critiques Plan classement par risque Feuille de route éléments par priorité Dépôt URL Git ou dossier Scan préalable malwares et secrets Quarantaine arrêt de l’exécution Rapport motif de l’arrêt Exploration carte ast, sans import Diagnostic ruff, mypy, radon Audit de sécurité bandit, semgrep, osv Valid. humaine constats critiques Plan classement par risque Feuille de route éléments par priorité
  • Entrée ou sortie
  • Appel LLM
  • Code
  • Contrôle
  1. 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.
  2. 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.
  3. 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.
  4. Diagnostic ruff, mypy et radon tournent dans un conteneur de 4 Go sans réseau ; chaque constat revient avec une sévérité.
  5. 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.
  6. 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.
  7. 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.

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.

Un élément de la feuille de route dans la boucle de correction
Élément à traiter suivant par priorité Action fichiers entiers Vérification pytest, lecture seule Valid. humaine approuver ou rejeter Rollback copies à l’octet près Rapport Markdown, sans modèle Élément à traiter suivant par priorité Action fichiers entiers Vérification pytest, lecture seule Valid. humaine approuver ou rejeter Rollback copies à l’octet près Rapport Markdown, sans modèle
  • Entrée ou sortie
  • Appel LLM
  • Code
  • Contrôle
  1. É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.
  2. 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.
  3. Vérification pytest tourne dans une sandbox sans réseau, sur un montage en lecture seule de l’espace de travail. Un test échoue.
  4. Action Deuxième tentative. Chaque élément en a 3 en tout, la première comprise.
  5. Vérification Les tests passent. « Invérifiable » est distingué de « échoué » et n’est jamais retenté.
  6. 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.
  7. 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.

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.