Logo ConstructiCat
CodeBust.
Parcourir la section ▾

Angle mort de sécurité.

Les assistants IA produisent du code fonctionnel qui omet silencieusement les contrôles de sécurité qu'un humain ajouterait par habitude — validation des entrées, autorisation, encodage des sorties, gestion des secrets — parce que « ça compile et ça renvoie 200 » ressemble à du travail terminé.

##Signes et symptômes

Un relecteur repère un angle mort de sécurité lorsque le code fonctionne sur le chemin heureux mais que les contrôles qu'un humain soucieux de sécurité ajoute par réflexe sont tout simplement absents. Le modèle démontre la fonctionnalité, pas la défense. Signes révélateurs :

  • Requêtes / commandes construites par concaténation de chaînes au lieu d'être paramétrées (points d'injection SQL / commande / LDAP).
  • L'entrée utilisateur va vers la sortie sans encodage (XSS réfléchi/stocké) ou vers fs/child_process/fetch sans liste d'autorisation (traversée de répertoire, SSRF).
  • L'endpoint authentifie mais n'autorise jamais — il vérifie qui vous êtes, jamais si vous avez le droit de toucher cet enregistrement (IDOR / contrôle d'accès au niveau objet rompu).
  • Secrets codés en dur — clés d'API, jetons, mots de passe de base de données inscrits en ligne « pour que ça tourne ».
  • Contrôles absents par omission : pas de jeton CSRF, pas d'en-têtes de sécurité, pas de limitation de débit, cryptographie faible/héritée (MD5, SHA-1, Math.random() pour les jetons).
  • Théâtre de commentaires : un commentaire // validate input ou // TODO: add auth se trouve là où le vrai contrôle devrait être. OX Security a trouvé des commentaires en ligne excessifs ("Comments Everywhere") dans 90 à 100 % du code généré par IA qu'il a étudié ; dans cette odeur, un tel commentaire tient souvent lieu de la logique manquante.
// Handler Express généré par IA — a l'air complet, livre quatre failles
app.get("/api/orders/:id", authMiddleware, async (req, res) => {
  const apiKey = "sk_live_8f3b2a91c7d44e0f";            // 1. secret codé en dur
  const { rows: [order] } = await db.query(
    `SELECT * FROM orders WHERE id = ${req.params.id}`   // 2. injection SQL
  );
  res.send(`<h1>Order for ${order.customerName}</h1>`);  // 3. XSS réfléchi
  // 4. IDOR : authMiddleware prouve la connexion, ne vérifie jamais order.userId === req.user.id
});

Quatre vulnérabilités distinctes, zéro test en échec — l'odeur est ce qui n'est pas là.

##Causes du problème

Pourquoi les modèles le produisent

  • L'objectif d'entraînement est la vraisemblance, pas la sécurité. La prédiction du prochain token optimise pour la manière la plus courante d'écrire du code, et les corpus publics regorgent d'extraits de tutoriels aux requêtes concaténées et sans autorisation. L'article d'OX Security sur la sécurité du vibe coding le dit sans détour : "La concaténation de chaînes est ce qui apparaît le plus fréquemment dans les données d'entraînement." Dans le benchmark SusVibes de Carnegie Mellon, portant sur 200 tâches réelles de développement de fonctionnalités, SWE-Agent avec Claude 4 Sonnet a produit des solutions fonctionnellement correctes dans environ 61 % des cas, mais sûres dans environ 10,5 % seulement.
  • "Terminé" est défini par le chemin nominal. Un LLM optimise pour que la requête visible réussisse. Les contrôles de sécurité ne produisent aucun signal positif lors d'une exécution rapide — un contrôle absent ne fait échouer aucun test et ne casse aucune démo —, ils sont donc abandonnés.
  • Aucun contexte du dépôt. Le modèle ne voit pas votre helper authorize(), votre schéma de validation ni votre gestionnaire de secrets, alors il inline une clé littérale ou saute la vérification plutôt que de réutiliser un contrôle existant.
  • Complaisance + vélocité. Il renvoie exactement ce que vous avez demandé ("ajoute un endpoint de commande"), pas le modèle de menace qui l'entoure, et les goulots d'étranglement comme la revue et la modélisation des menaces sont précisément ce que le développement à la vitesse de l'IA supprime. La conclusion centrale d'OX Security est que le code généré par l'IA ne contient pas plus de vulnérabilités par ligne que le code écrit par des humains ; ce qui envoie du code vulnérable en production, c'est la vélocité, car "une revue de code digne de ce nom ne peut tout simplement pas suivre la nouvelle cadence de production". OX met aussi en garde contre les applications "non sécurisées par bêtise" (insecure by dumbness) : construites avec l'IA et déployées par des utilisateurs non techniques qui n'ont pas l'expertise sécurité pour voir ce qui manque.
  • Obsolescence due à la date de coupure d'entraînement. Les modèles recourent à des schémas de cryptographie et de bibliothèques qui étaient acceptables il y a des années (MD5, SHA-1, options TLS dépréciées, anciens flux d'authentification).

Pourquoi c'est nuisible

  • Exploitabilité directe. Le 2025 GenAI Code Security Report de Veracode (80 tâches de programmation, plus de 100 LLM) a relevé des failles de sécurité dans 45 % des cas, et les modèles ne se sont pas protégés contre le cross-site scripting (CWE-80) dans 86 % des cas. CodeRabbit a trouvé des vulnérabilités XSS 2,74 fois plus souvent dans les pull requests coécrites avec l'IA que dans celles écrites uniquement par des humains ; Tenzai a constaté qu'aucune des 15 applications construites par cinq agents de code n'avait de protection CSRF ni ne définissait d'en-têtes de sécurité ; Escape.tech a trouvé plus de 400 secrets exposés dans plus de 5 600 applications issues du vibe coding.
  • Invisible aux tests comme aux lecteurs. Un contrôle d'autorisation manquant n'a aucun test en échec ni aucun diff sur lequel pointer, il passe donc la revue sans encombre — et le volume de sortie de l'IA fait que la revue ne peut pas monter en charge pour l'attraper.
  • Dette technique cumulative. L'analyse 2025 de GitClear portant sur 211 millions de lignes modifiées a montré que les lignes associées au refactoring ("déplacées") sont passées de 25 % des lignes modifiées en 2021 à moins de 10 % en 2024, tandis que les lignes copiées-collées passaient de 8,3 % à 12,3 % et dépassaient pour la première fois les lignes déplacées en 2024 ; les blocs de cinq lignes dupliquées ou plus ont été multipliés par huit environ en 2024. Le même motif non sécurisé est cloné dans toute la base de code, ce qui multiplie le rayon d'impact de chaque angle mort.

##Traitement

Tactiques de revue et de prompting

  • Nommez le modèle de menace dans le prompt. "Écris cet endpoint en supposant que l'id est contrôlé par un attaquant ; impose une autorisation au niveau objet, paramètre toutes les requêtes, encode toutes les sorties et lis les secrets depuis process.env." Spécifier l'adversaire inverse le comportement par défaut.
  • Forcez la réutilisation des contrôles existants. "Utilise notre helper authorize(user, 'order', id) et le validateur orderSchema — n'écris pas de nouvelle validation." Cela contre directement la cause de l'absence de contexte du dépôt (et l'odeur connexe de Code dupliqué).
  • Faites exécuter les outils par le modèle. Exigez qu'il lance semgrep --config p/owasp-top-ten, gitleaks detect, npm audit et votre configuration eslint-plugin-security, puis qu'il corrige ce qu'ils signalent avant de se déclarer terminé.
  • Demandez explicitement les négatifs. "Liste les préoccupations d'authentification, d'autorisation, de validation des entrées, d'encodage des sorties et de gestion des secrets pour ce handler, et montre où chacune est appliquée." Un blanc dans cette liste est le bug.
  • Modélisez la menace du diff, pas de la ligne. Les failles dangereuses sont des omissions ; relisez donc en demandant "quel contrôle devrait être ici et ne l'est pas ?" plutôt qu'en cherchant de mauvaises lignes.

Le refactoring — paramétrer la requête (tue l'injection), encoder la sortie (tue le XSS), ajouter un contrôle d'autorisation au niveau objet (tue l'IDOR) et déplacer le secret en ligne vers la configuration :

// APRÈS
app.get("/api/orders/:id",
  authMiddleware,
  validate(orderParamsSchema),                    // validation des entrées
  async (req, res) => {
    const { rows: [order] } = await db.query(
      "SELECT * FROM orders WHERE id = $1", [req.params.id]   // paramétré
    );
    if (!order || order.userId !== req.user.id) {            // autorisation au niveau objet
      return res.sendStatus(404);
    }
    res.json({ customerName: order.customerName });          // sortie structurée, encodée automatiquement
  }
);
// secret : process.env.STRIPE_API_KEY, chargé une fois au démarrage, jamais inline

Là où l'odeur s'est propagée par clonage, corrigez-la une fois et appliquez Extraire une méthode au contrôle (requireOrderOwnership, escapeHtml) afin que chaque site d'appel partage une seule implémentation auditée plutôt que N copies.

##Détecté par