Écrits
Comment j'ai corrigé cinq vulnérabilités d'API avec un seul prompt et Claude Fable 5
Derrière le tableau de bord de transparence VLOP tourne un petit service FastAPI — une API de requêtes structurées calquée sur la TikTok Research API, qui sert des données de transparence agrégées du DSA de l'UE. Plusieurs de ses endpoints sont délibérément publics : un constructeur de requêtes interactif, une boîte « demander » en langage naturel, un fil de synthèse. Les endpoints publics d'un service à l'échelle d'un projet perso sont précisément là où la dette de sécurité s'accumule en silence, parce que rien ne vous force à regarder.
J'ai donc demandé à Claude Code — propulsé par Fable 5, le premier modèle de la famille Claude 5 d'Anthropic — de regarder. Le prompt tenait en une ligne : « lance un contrôle de sécurité sur l'API et corrige toutes les vulnérabilités. » Ce qui est revenu, c'est une pull request avec cinq corrections distinctes, chacune assortie de tests de non-régression, plus une auto-revue de son propre diff.
Ce qu'il a trouvé
Une faille SSRF dans les callbacks de webhook. L'API permet à une requête de porter une callback_url qui reçoit un POST à la fin du job — une surface classique de falsification de requête côté serveur (SSRF). Le garde-fou existant bloquait les plages privées, loopback, link-local et de métadonnées cloud, mais une liste de blocage est la mauvaise forme pour ce problème : elle laissait passer des plages qui ne sont ni privées ni publiques, comme le NAT de niveau opérateur (100.64.0.0/10). Le correctif inverse le contrôle — une cible de callback doit désormais être routable globalement, faute de quoi elle est rejetée.
Injection de formule CSV. Les résultats de requête s'exportent en CSV, et le jeu de données comprend des champs en texte libre issus de rapports de transparence tiers. Une cellule commençant par =, +, - ou @ s'exécute comme une formule à l'ouverture du fichier dans Excel ou Sheets — autrement dit, une valeur figurant dans le rapport publié par quelqu'un d'autre pourrait exécuter du code sur la machine d'un chercheur. Les cellules de texte portant ces préfixes sont désormais neutralisées, tant côté serveur que dans l'export côté client du tableau de bord.
Corps de requête et complexité de requête sans borne. L'endpoint de requête public accepte l'intégralité du modèle de requête structurée sans authentification. Rien ne plafonnait la taille d'un corps de requête ni le nombre de conditions qu'une requête pouvait empiler. Les corps dépassant une limite sont désormais rejetés avec un 413 avant tout travail d'analyse, et le modèle de requête lui-même borne les valeurs par condition, les conditions par clause et les champs par requête.
Un canal auxiliaire temporel dans la vérification des clés d'API. Les clés étaient vérifiées par une recherche de dictionnaire, qui s'interrompt au premier caractère divergent — en principe, le temps de réponse laisse fuir les préfixes de clé. La comparaison se fait désormais en temps constant sur chaque clé configurée.
Plus divers renforcements — le genre de petits détails qu'un balayage par checklist attrape et qu'un mainteneur pressé laisse passer : des bornes sur les paramètres de pagination, ce genre de choses.
La partie qui m'a surpris
La pull request a déclenché une revue automatisée d'une seconde IA, Gemini Code Assist, qui a laissé deux suggestions. Fable 5 a évalué les deux au regard du code source réel avant d'agir — et sur l'une d'elles, il a trouvé le raisonnement du bot incomplet. Gemini a signalé que parser un en-tête Content-Length de longueur arbitraire fourni par un attaquant en un entier pouvait consommer du CPU. Vrai, mais mineur. Le problème plus fin, que Fable 5 a identifié et expliqué dans sa réponse, c'est que Python 3.11+ refuse de parser les entiers au-delà d'environ 4 300 chiffres et lève une exception à la place — de sorte qu'un en-tête pathologique aurait produit une erreur 500 plutôt que le 413 attendu. Il a aussi généralisé le correctif codé en dur de Gemini pour que la borne suive la limite configurée, et ajouté un test de non-régression envoyant un en-tête de 5 000 chiffres. Une IA relisant la revue d'une autre IA sur le code de la première IA, et ayant raison dans le désaccord, ne figurait pas sur ma grille de loto pour 2026.
Ce que j'en retire
Cela fait écho à ce que j'ai écrit sur les LLM dans le travail de conformité : les gains viennent des tâches structurées à sorties vérifiables. Un balayage de sécurité sur une petite base de code, c'est exactement cela — une checklist finie (surfaces d'injection, SSRF, canaux auxiliaires, épuisement de ressources) appliquée de façon exhaustive, où chaque trouvaille peut être confirmée en lisant le code et chaque correctif épinglé par un test. L'exhaustif-et-ennuyeux est là où les modèles battent désormais de façon fiable des humains fatigués.
Le jugement, lui, est resté le mien. Décider que l'endpoint de métriques peut rester sans authentification, que les clés de démo sont acceptables hors production, qu'un stockage de jobs en mémoire convient à un service de démo — ce sont des décisions de modèle de menace sur ce à quoi le service sert, et le modèle n'a pas prétendu le contraire. Le diff complet, tests inclus, est dans la pull request ; la suite compte 122 tests et est au vert.