
Vibe coding et sécurité : j'ai cassé une app générée par IA
sans exploit
Vibe coding : front React solide, backend grand ouvert. Clé exposée dans le bundle, aucune Row Level Security, RPC sans autorisation. Récit d'une compromission totale par l'API.
Maxime J8 min de lectureMis à jour le
Du code vibe-codé en production ?
Un pentester lit le backend et cherche ce que l'IA oublie toujours : la policy manquante, la fonction sans rôle, la clé qui autorise trop.
Faire relire votre codeUn samedi soir, une idée, un prompt. Trois quarts d'heure plus tard, une plateforme complète tourne en ligne : comptes utilisateurs, connexion, classement, tableau de bord. Zéro ligne de code écrite à la main. C'est ça, le vibe coding : vous décrivez, l'IA construit, ça marche. Le lundi, je l'ai attaquée. En quatre minutes, sans écrire un seul exploit, j'avais tous les secrets de la base, mon score à deux milliards et le pouvoir de bannir n'importe qui. Voici comment, et pourquoi ça arrive à presque toutes les applications générées de cette façon.
Cadre : mes propres applications
Le front tient. On applaudit (trop vite)
Réflexe de pentester : on commence par là où tout le monde tape. Un champ de pseudo, un champ "pays", une bio. On y glisse les classiques du Top 10 des risques web, en visant le XSS stocké :
<script>alert(document.domain)</script>
<img src=x onerror=alert(1337)>
"><svg/onload=alert(document.cookie)>
javascript:alert(1)Rien. Chaque charge s'affiche telle quelle, en texte, sur le profil. On teste le DOM (fragments d'URL, location.hash), on cherche un dangerouslySetInnerHTML avec de la donnée utilisateur, une open redirect, un eval traînant : rien n'accroche. La raison est structurelle et plutôt rassurante : le front est en React, et React échappe automatiquement toute expression rendue dans du JSX. Votre <script> devient <script> avant même d'atteindre le DOM. Pour sortir du contexte HTML, il faudrait que le développeur ait explicitement désactivé cette protection. L'IA ne l'a pas fait. Bon point.
Ce n'est pas un hasard
{username} dans une balise". Ce qui est visible, testable au clic et omniprésent dans les tutoriels, l'IA le maîtrise.À ce stade, un audit superficiel signerait "RAS côté client" et passerait à autre chose. C'est le piège. La surface qui compte n'est pas celle qu'on regarde.
Puis on ouvre l'onglet Réseau
L'application est un single-page React qui parle à un backend hébergé (le fameux backend-as-a-service : base de données, authentification et API REST auto-générée, le tout clé en main). Le navigateur doit bien s'authentifier auprès de cette API. Donc la clé d'accès est forcément dans le bundle JavaScript. On l'y trouve en dix secondes :
# la clé "anon" et l'URL du projet, en clair dans le JS livré au navigateur
$ curl -s https://cible.exemple.app/assets/index-*.js | grep -oE 'https://[a-z0-9]+\.backend\.co|eyJ[A-Za-z0-9_-]{20,}'
https://xxxxxxxx.backend.co
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...REDACTED...role":"anon"...Ici, une confusion fréquente : cette clé est censée être publique. Elle n'est pas un secret, et l'exposer n'est pas la faille. La clé publique n'est qu'un ticket d'entrée vers l'API : ce qu'elle vous autorise à faire une fois entré dépend entièrement des règles côté serveur.
Pas de Row Level Security : la base répond à tout le monde
Sur ce type de backend, une table n'est protégée que si l'on active la Row Level Security (RLS) et qu'on écrit des policies : des règles SQL qui décident, ligne par ligne, qui a le droit de lire ou d'écrire quoi. Par défaut, sur beaucoup de projets, la RLS est désactivée. Sans elle, la clé publique donne un accès en lecture (et souvent en écriture) à toutes les lignes de toutes les tables exposées. Y compris la table des défis, avec leurs réponses :
# lire la table des challenges, colonne "flag" comprise, avec la clé publique
$ curl -s 'https://xxxxxxxx.backend.co/rest/v1/challenges?select=title,points,flag' \
-H "apikey: eyJ...REDACTED"
[
{"title":"Warmup", "points":50, "flag":"CTF{r3d4ct3d_0001}"},
{"title":"SQLi 101", "points":200, "flag":"CTF{r3d4ct3d_0002}"},
{"title":"Crypto boss","points":500, "flag":"CTF{r3d4ct3d_0003}"}
...
]Les douze réponses de la plateforme, extraites en une requête, sans en résoudre une seule. Ce n'est ni du XSS, ni de l'injection SQL : c'est du contrôle d'accès cassé (broken access control), la première catégorie (A01) de l'OWASP Top 10 2025. La donnée n'était protégée par aucune règle. L'IA a créé les tables, les a exposées via l'API, et n'a jamais écrit la moindre policy.
Les fonctions RPC sans videur
On monte d'un cran. L'IA avait généré des fonctions serveur (RPC) pour la logique métier : ajouter des points, bannir un tricheur. Le front les appelle. Mais rien ne vérifie qui les appelle ni pour qui. La signature est en clair dans le JavaScript ; il suffit de la rejouer :
# s'attribuer le score maximal d'un entier 32 bits signé (2 147 483 647)
$ curl -s 'https://xxxxxxxx.backend.co/rest/v1/rpc/add_points_to_user' \
-X POST -H "apikey: eyJ...REDACTED" -H 'content-type: application/json' \
-d '{"p_user_id":"<mon-uuid>","p_points":2147483647}'
# bannir n'importe quel compte : la fonction admin n'exige aucun rôle admin
$ curl -s 'https://xxxxxxxx.backend.co/rest/v1/rpc/admin_ban_user' \
-X POST -H "apikey: eyJ...REDACTED" -H 'content-type: application/json' \
-d '{"p_user_id":"<uuid-du-1er-au-classement>","p_reason":"nope"}'À partir de là, la plateforme m'appartient. Je mets mon score à INT_MAX, je passe tous les concurrents en négatif, je bannis le haut du classement, je réécris les pseudos. Le préfixe admin_ sur la fonction était le seul "contrôle" : un nom, pas une barrière.
admin_ban_user exécutable par n'importe qui. Le nom fait office de sécurité.Deuxième cas : un proxy qui n'a jamais demandé la clé
Même famille de faille, autre décor. Un petit service généré pour relayer des appels vers une API de modèle de langage (un LLM). Le genre d'outil qu'on assemble en vingt minutes pour "partager un accès", et qui devient un shadow AI que personne n'a inventorié. Sauf qu'il relaie les requêtes vers l'API coûteuse sans jamais vérifier d'authentification : une clé bidon est acceptée sans broncher.
# aucune clé valide requise : "Bearer peu-importe" passe
$ curl -s https://proxy.exemple.app/v1/messages \
-H 'authorization: Bearer nimportequoi' \
-H 'content-type: application/json' \
-d '{"model":"...","messages":[{"role":"user","content":"coucou"}]}'
{"id":"msg_...","role":"assistant","content":[{"type":"text","text":"Bonjour !"}]}C'est de l'authentification cassée à l'état pur (A07:2025, Authentication Failures) : un endpoint qui coûte de l'argent à chaque appel, exposé au monde entier, sans porte. N'importe qui trouvant l'URL consomme le quota (et la facture) du propriétaire. Là encore, l'IA avait bien construit le relais, la fonctionnalité demandée. Personne ne lui avait demandé le videur, alors il n'y en a pas.
Pourquoi l'IA code toujours comme ça
Le fil rouge des deux cas est le même, et il n'a rien d'un accident :
- L'IA optimise ce qui est prouvable au clic. "Fais un classement" produit un classement qui s'affiche. Que n'importe qui puisse le réécrire par l'API ne se voit pas dans le navigateur : rien, dans le prompt ni dans le rendu, ne pousse le modèle à s'en soucier.
- Le défaut non sécurisé passe inaperçu. Activer la RLS, écrire des policies, vérifier un rôle dans une fonction : ce sont des étapes invisibles, souvent désactivées par défaut. L'IA suit le chemin qui marche le plus vite, et le chemin par défaut mène à une base ouverte.
- L'autorisation ne s'apprend pas dans les tutoriels. Les millions d'exemples d'entraînement montrent "comment afficher des données", rarement "comment décider qui a le droit de les modifier". Le modèle reproduit ce qu'il a le plus vu.
- Le vibe coding retire le seul garde-fou : la relecture. Quand on ne lit pas le code généré (c'est tout le principe), personne ne remarque la policy manquante. La faille naît silencieuse et part en prod telle quelle.
L'IA sécurise ce qui se voit, et laisse béant ce qui compte. Le front, visible et testable, est propre. L'autorisation, les secrets, la logique d'accès, tout l'invisible, reste ouvert. Ce biais tient à la démarche elle-même, quel que soit le modèle.
Ce qu'un audit aurait vu en une passe
Aucune de ces failles n'est subtile. Un test d'accès basique les lève toutes : rejouer les appels d'API sans session, tenter une écriture sur la ligne d'un autre utilisateur, appeler une fonction admin_* en anonyme. C'est le pain quotidien d'un contrôle d'accès sérieux, et c'est le genre de vérification qu'on automatise mal quand on se fie au seul rendu. Sur la frontière entre ce qu'une machine détecte et ce qu'un humain doit trancher, voyez pentest automatisé vs pentest humain, et pour la manière dont l'IA change le tri du bruit en analyse de code, le comparatif SAST, DAST, IAST à l'heure de l'IA.
Le vibe coding n'est pas le problème ; l'absence de revue de code sécurité, si. Générer vite est une force, à condition qu'une passe de contrôle systématique traque ce que l'IA laisse passer : la table exposée sans policy, la fonction serveur qu'aucun rôle ne garde. C'est ce que fait notre plateforme AppSec pilotée par IA : repérer, dans le code généré, l'endroit précis où l'IA a oublié le videur. Quand l'application tourne déjà en production et qu'on veut une certitude plutôt qu'une alerte, cette passe se fait à la main : c'est le principe d'un audit de code source, où l'on lit les policies, on suit les rôles et on rejoue les appels d'API avec le schéma de la base sous les yeux.
À retenir
- Une app générée par IA a souvent un front solide (React échappe le XSS) et un backend grand ouvert : ne jugez jamais sa sécurité au navigateur.
- La clé publique d'un backend-as-a-service n'est pas un secret ; ce qui protège vos données, c'est la Row Level Security et ses policies, trop souvent désactivées par défaut.
- Une fonction nommée
admin_*mais exécutable par un anonyme n'a de contrôle d'accès que le nom : le broken access control reste la faille n°1. - Un relais d'API sans authentification, c'est votre facture ouverte au monde : l'authentification cassée guette tout service assemblé à la va-vite.
- L'IA optimise le visible ; l'autorisation est invisible. Sans relecture de sécurité, le vibe coding envoie ce biais directement en production.
Vous avez du code généré par IA quelque part en production ? Il y a de bonnes chances que le videur manque à un endroit précis. On sait où regarder : parlez-en avec un humain via la page contact, ou découvrez notre approche de l'AppSec native IA.
Questions fréquentes
Qu'est-ce que le vibe coding ?
Le vibe coding consiste à décrire une application en langage naturel et à laisser une IA générer le code, souvent sans le relire. On obtient très vite une application fonctionnelle : comptes, authentification, tableau de bord. Le revers, c'est que le développeur ne lit pas ce que l'IA produit, donc personne ne remarque les décisions de sécurité qu'elle a omises.
Le code généré par IA est-il sécurisé ?
Pas par défaut. L'IA soigne ce qui se voit au navigateur, comme l'échappement du XSS en React, mais laisse souvent ouvert ce qui est invisible : le contrôle d'accès, l'autorisation côté serveur, les secrets. Le front peut être solide et le backend grand ouvert. Sans relecture de sécurité, ce biais part directement en production.
Une clé d'API publique dans le bundle est-elle une faille ?
Non en soi. La clé publique d'un backend-as-a-service est un ticket d'entrée, pas un secret : elle est censée être visible. Ce qui protège vos données, ce sont les règles côté serveur (Row Level Security et policies). La faille apparaît quand ces règles sont absentes : la clé donne alors accès à toutes les lignes de toutes les tables exposées.
Qu'est-ce que la Row Level Security ?
La Row Level Security (RLS) protège une table ligne par ligne à l'aide de policies, des règles décidant qui peut lire ou écrire quoi. Sur beaucoup de projets, elle est désactivée par défaut. Sans RLS, une API auto-générée expose chaque table telle quelle, et la clé publique suffit à tout lire et souvent tout modifier.
Comment sécuriser une application vibe-codée ?
Il faut la passe de relecture de sécurité que le vibe coding supprime. Concrètement : activer la RLS et écrire les policies, vérifier un rôle dans chaque fonction serveur, rejouer les appels d'API sans session pour tester le contrôle d'accès. Un audit de code source ou un pentest whitebox lève ces failles en une passe, car elles ne sont pas subtiles.
Quelles failles trouve-t-on dans une app générée par IA ?
Le contrôle d'accès cassé domine : tables sans policy, fonctions administratives exécutables par un anonyme, relais d'API sans authentification. Ce sont les catégories A01 (Broken Access Control) et A07 (Authentication Failures) de l'OWASP Top 10. L'IA reproduit ce qu'elle a le plus vu dans les tutoriels, qui montrent comment afficher des données, rarement comment décider qui a le droit de les modifier.
Veille sécurité
La suite, une fois par mois
Ce qui bouge vraiment sur la surface d'attaque externe, NIS2 et la sécurité applicative, écrit par le pentester qui signe ces articles. Un envoi par mois, désinscription en un clic.
Votre adresse ne sert qu'à cet envoi. Voir la politique de confidentialité.
Le dossier sécurité applicative
Guide de tête
SAST vs DAST vs IAST : comparatif, et ce que l'IA change
SAST, DAST, IAST : trois façons de chasser les failles applicatives, à trois moments du cycle. Forces, angles morts, faux positifs, et ce que l'analyse par IA change au tri du bruit.
Dans le même dossier
appsec
Prompt injection : ce n'est pas un jailbreak, et ça ne se corrige pasnouveau
Le jailbreak vise l'alignement du modèle, la prompt injection vise l'application. Structurelle par nature, l'injection indirecte détourne un agent via une page ou un PDF. Les défenses qui tiennent.
appsec
Checkmarx : prix, réalité du SAST d'entreprise et alternativesnouveau
Checkmarx prix, périmètre de Checkmarx One et alternatives : ce que dit vraiment la licence, le coût de tuning que personne ne budgète, et quoi regarder selon le besoin.
appsec
Snyk : prix, unité de facturation et alternatives (guide d'achat)nouveau
Snyk prix, plans publiés et pièges de facturation : le développeur contributeur, les tests mensuels plafonnés, le bruit transitif, et les alternatives par cas d'usage.