Bases de la sécurité
Variables d’environnement Vite exposées : le préfixe dit publier
Variables d’environnement Vite exposées : votre appli a fait ce que le préfixe demandait. VITE_ et NEXT_PUBLIC_ veulent dire publier, et l’IA ignorait le prix.

En bref
- Les variables d’environnement Vite exposées dans votre appli sont là parce que le préfixe l’a demandé. Une variable nommée VITE_ ou NEXT_PUBLIC_ est copiée dans le JavaScript que chaque visiteur télécharge, et la garder dans un fichier .env n’y change rien.
- Le préfixe est la bonne étiquette pour une adresse ou une clé publiable, et la mauvaise pour tout ce qui dépense de l’argent ou ignore les règles de votre base de données. Le build ne fait pas la différence, et l’IA qui a écrit la ligne non plus.
- Sur 30 998 applis vibe-codées en ligne que nous avons scannées, 1 332 livraient quelque chose qui ressemble à une clé. 1 142 d’entre elles étaient des clés d’API Google, qui ont en général besoin d’une restriction et pas d’une rotation. 52 livraient une clé qui dépense de l’argent ou lit tout.
Quelqu’un a ouvert votre appli Lovable, appuyé sur F12, et trouvé dans le code
une valeur que vous êtes certain d’avoir mise dans un fichier .env. Ou vous
avez demandé une fonctionnalité au builder, il a écrit une ligne commençant par
VITE_, et quelque chose que vous avez lu depuis dit que ce préfixe est par
là que les clés fuient. Le fichier a .env dans son nom et tous les tutoriels
disent de ne jamais le partager. Des variables d’environnement Vite exposées à
chaque visiteur, cela ressemble à un bug de Vite.
Voici la partie que guide après guide explique de travers : rien n’a échoué
à cacher cette valeur. VITE_ et NEXT_PUBLIC_ sont une instruction pour
votre outil de build, et l’instruction est de mettre la valeur dans l’appli
que chaque visiteur télécharge. L’outil a lu l’étiquette et a fait ce qu’elle
disait.
Un build emballe votre appli dans une boîte, et chaque visiteur reçoit une copie de la boîte. Le préfixe est une étiquette sur une valeur qui dit : emballe celle-ci aussi. Le reste de cet article porte sur les valeurs pour lesquelles cette étiquette est juste, sur la raison pour laquelle l’IA la colle sur les mauvaises, et sur la façon de voir ce qu’il y a dans votre propre boîte.
Les variables d’environnement Vite sont-elles exposées aux visiteurs ?
Celles qui commencent par VITE_, oui, et c’est à cela que sert le préfixe.
Vite, l’outil de build derrière la plupart des applis Lovable et Bolt, lit
votre fichier .env à chaque build. Une variable nommée VITE_SUPABASE_URL
est copiée dans le bundle, le fichier JavaScript compressé que chaque visiteur
télécharge, où votre code la lit sous la forme
import.meta.env.VITE_SUPABASE_URL. Une variable nommée DB_PASSWORD, sans
préfixe, arrive vide dans le navigateur. La documentation de Vite elle-même
dit que les valeurs préfixées sont intégrées à votre code source au moment du
build et ne doivent pas contenir de clés d’API.
Next.js, avec lequel v0 construit, a la même règle sous un autre nom :
NEXT_PUBLIC_. Sa documentation décrit la valeur comme inlined, une chaîne
figée écrite dans le bundle du navigateur au moment du build. Expo utilise
EXPO_PUBLIC_ et prévient avec les mêmes mots. Les anciens projets Create
React App utilisent REACT_APP_. Chaque préfixe dit la même chose à son outil
de build : celle-ci va dans la boîte.
Pourquoi un fichier .env semble privé et ne l’est pas
Parce que l’habitude de garder une clé dans un fichier .env vient des
serveurs, où elle fonctionne.
Sur un serveur, le fichier et le code qui le lit sont sur une machine que vous
contrôlez. Un visiteur reçoit une réponse de cette machine et ne voit jamais
le fichier. Sortir la clé du code et la mettre dans un fichier que le serveur
lit au démarrage est une bonne pratique là-bas, et c’est là qu’a été écrit
chaque tutoriel qui dit « mettez-la dans .env ».
Votre fichier .env a deux lecteurs, et les tutoriels parlent d’un seul. Le
premier, c’est quiconque peut voir votre projet : un collaborateur, un dépôt
GitHub public, la vue des fichiers du builder lui-même. Une entrée dans
.gitignore, qui est une liste de fichiers que git laisse de côté, tient
.env à l’écart de ce lecteur. Le second lecteur est le build, qui ouvre le
fichier à chaque publication et en copie tout ce qui porte le préfixe.
.gitignore ne lui dit rien.
Donc « mon .env est dans gitignore » est vrai, et répond à une autre
question. Le fichier est resté hors de votre dépôt. Les valeurs préfixées sont
quand même entrées dans l’appli, parce que c’est la route que le préfixe
ouvre, et dans un projet Lovable, Bolt ou v0, la plus grande partie du code
que vous avez modifié tourne dans le navigateur, où il n’y a aucun serveur
derrière lequel le fichier pourrait rester.
Pourquoi l’IA a pris le préfixe
Parce que c’est ainsi qu’on fait fonctionner une valeur dans du code de navigateur, et que le modèle n’a aucune idée de ce que la valeur coûte.
Vous avez demandé une carte, ou un chat qui répond aux questions sur votre
produit. Le code que le builder a écrit pour cela tourne dans le navigateur du
visiteur, et du code de navigateur qui lit process.env.OPENAI_API_KEY ne
reçoit rien du tout. Le moyen de faire arriver la valeur, c’est le préfixe. Le
builder renomme la variable VITE_OPENAI_API_KEY, la fonctionnalité marche
dans l’aperçu, et aucune erreur n’est levée nulle part, parce que du côté de
l’outil de build rien ne s’est mal passé.
Le préfixe ne juge pas. C’est la même instruction pour une clé Google Maps, qui est censée être publique une fois restreinte, et pour une clé secrète Stripe, qui peut rembourser chacun de vos clients. Une personne qui saurait que la seconde débite votre carte s’arrêterait. Le modèle sait que la fonctionnalité ne marchait pas tant que la ligne n’était pas là, et qu’ensuite elle a marché.
C’est pour cela que cela apparaît sur des applis dont les propriétaires ont
fait tout ce qu’on leur a dit. La valeur a été sortie du code, gardée dans
.env, le fichier a été mis dans gitignore, et l’appli la livre quand même,
parce que la seule étape qui la publie ressemble à l’étape qui la fait
marcher.
Quelles valeurs vont derrière le préfixe
Une adresse et une clé publiable. Tout ce qui dépense de l’argent ou ignore les règles de votre base de données, non.
| Valeur | Derrière VITE_ ou NEXT_PUBLIC_ ? | Pourquoi |
|---|---|---|
VITE_SUPABASE_URL | À sa place | Une adresse. Elle dit à quel projet votre appli parle, et rien d’autre. |
VITE_SUPABASE_PUBLISHABLE_KEY, ou VITE_SUPABASE_ANON_KEY sur un projet plus ancien | À sa place | Conçue pour le navigateur. Chaque requête qu’elle fait reste filtrée par Row Level Security, les règles sur chaque table qui décident ligne par ligne qui peut lire quoi. |
Une clé Stripe pk_live_ | À sa place | Construit des formulaires de paiement. Ne peut ni débiter, ni rembourser, ni lire les clients. |
| Une clé Google Maps | À sa place, une fois restreinte | Publique par conception. Une restriction par référent dans Google Cloud est ce qui empêche un inconnu de vous facturer avec. |
| Une clé OpenAI ou Anthropic | Jamais | Il n’existe pas de variante publiable. Qui la détient dépense votre argent. |
Une clé Supabase service_role ou sb_secret_ | Jamais | Contourne Row Level Security et lit chaque ligne de chaque table. |
Une clé Stripe sk_live_ | Jamais | Débits, remboursements, virements et chaque fiche client. |
| Une clé d’accès AWS | Jamais | Tout ce que ce compte peut faire, depuis n’importe où. |
Si votre appli a VITE_SUPABASE_URL et une clé publiable à côté, c’est la
paire que Supabase a prévue pour un navigateur, et notre scan la marque comme
étant à sa place. L’adresse et la clé publiable sont la raison d’être du
préfixe.
Le test pour tout le reste, c’est de savoir si cela vous gênerait de voir la valeur imprimée sur votre page d’accueil. Le préfixe la met un clic plus loin que cela, dans un fichier au lieu de la page, et quiconque veut le fichier l’a. Distinguer les deux familles, par le préfixe et, sur une ancienne clé Supabase, par le rôle qu’elle contient, est un article à part.
Ce que 30 998 applis livraient derrière le préfixe
Surtout des clés d’API Google. 52 applis livraient une clé qui dépense de l’argent ou lit tout.
En août 2026, nous avons lancé
les neuf mêmes vérifications sur
30 998 applis vibe-codées en ligne. 1 332 d’entre elles, soit 4 %, livraient
quelque chose qui ressemble à une clé dans le code que chaque visiteur
télécharge. 1 142 de celles-ci étaient des clés d’API Google, qui ont en
général besoin d’une
restriction définie dans Google Cloud
et pas d’une rotation. 204 livraient une valeur d’allure aléatoire à côté d’un
nom comme secret ou password, qui peut être un vrai identifiant ou non.
Les coûteuses étaient rares. 33 applis livraient une clé OpenAI, 9 une clé
d’accès AWS, 5 une clé Anthropic, 3 une clé secrète Stripe et 3 une clé
service_role Supabase : 52 applis en tout, puisque l’une d’elles en portait
deux. La valeur derrière le préfixe est donc en général une clé Google, et le
remède pour cela est un réglage. Le cas rare est celui où se trouvent les
dégâts, et
ce que coûte une clé OpenAI qui a fui
est l’article pour celui-là.
Comment vérifier ce que votre propre appli livre
Ouvrez l’appli en ligne, appuyez sur F12, et cherchez la valeur dans chaque fichier chargé.
- Ouvrez votre appli publiée dans un navigateur, à sa vraie adresse. L’aperçu du builder est un autre build et peut avoir une version de retard.
- Appuyez sur F12 pour ouvrir les outils de développement, puis choisissez l’onglet Sources.
- Appuyez sur Ctrl+Maj+F, ou Cmd+Option+F sur un Mac. Cela ouvre une recherche dans chaque fichier que la page a chargé.
- Collez les dix premiers caractères environ de la valeur qui vous inquiète, et ne la collez nulle part ailleurs.
Un résultat veut dire que la valeur est dans la boîte que reçoit chaque
visiteur. Cherchez la valeur plutôt que le nom : le build remplace en général
import.meta.env.VITE_OPENAI_API_KEY par la valeur elle-même, si bien qu’une
recherche de VITE_ peut revenir vide alors que chaque valeur qu’il y avait
derrière est là.
Si vous préférez ne pas parcourir votre appli valeur par valeur, notre scan gratuit lit votre site en ligne depuis l’extérieur, les neuf vérifications, en 20 secondes environ et sans compte. Il nomme chaque clé trouvée par son type, dit lesquelles ont leur place dans un navigateur, et écrit « Impossible à vérifier » pour tout ce à quoi il n’a pas pu répondre plutôt qu’une coche : scannez votre appli.
Où va un vrai secret à la place
Sur une machine depuis laquelle vos visiteurs ne téléchargent jamais rien. Dans un projet Supabase, c’est une Edge Function, un petit morceau de code serveur que Supabase exécute pour vous ; dans une appli Next.js, c’est une route serveur ; dans une appli Replit, c’est la moitié serveur.
La forme est la même partout. Votre code de navigateur demande à votre fonction de faire le travail. La fonction détient la clé, passe l’appel à OpenAI ou Stripe, et renvoie la réponse. La clé reste sur la machine et le visiteur reçoit un résultat. L’article sur OpenAI le dessine comme un objet qui se déplace d’une case vers la droite, et le changement se résume à cela.
Deux choses vous disent que le builder a fait ce que vous avez demandé. La
variable a perdu son préfixe, donc c’est OPENAI_API_KEY, et elle vit dans
les secrets propres à la fonction, définis dans le tableau de bord Supabase
sous Edge Functions, sans rien dans le .env de l’appli. Et le fichier qui la
lit se trouve sous supabase/functions/ ou app/api/, quelque part que le
build n’emballe jamais, au lieu de sous src/.
Un ordre compte, et il est facile de l’inverser. Si une clé secrète a déjà été livrée derrière un préfixe, la déplacer ne fait rien pour les copies déjà téléchargées. Renouvelez-la d’abord chez le fournisseur, puis déplacez le travail. Renouveler d’abord ou fermer la fuite d’abord dépend de si la copie est déjà publique, et dès qu’elle a été dans un bundle publié, elle l’est.
Pour un projet Replit, la même séparation a son propre nom, Secrets, et ce que l’outil Secrets couvre et ce qu’il ne couvre pas est un article à part.
Quoi faire maintenant
Que faire
- Cherchez dans votre projet
VITE_,NEXT_PUBLIC_,EXPO_PUBLIC_etREACT_APP_. Chaque occurrence est une valeur que votre build publie exprès. Décidez pour chacune si elle peut être publique. - Gardez l’adresse et la clé publiable.
VITE_SUPABASE_URLetVITE_SUPABASE_PUBLISHABLE_KEY, ou la cléanonsur un projet plus ancien, sont la paire pour laquelle le préfixe existe. - Une clé secrète derrière un préfixe est d’abord renouvelée chez le fournisseur, puis déplacée. Supprimer la ligne ne rappelle pas une copie déjà téléchargée.
- Déplacez le travail qui avait besoin de la clé dans une Edge Function ou une route serveur, avec la clé dans les secrets de cette fonction et sans préfixe sur son nom.
- Restreignez une clé Google par référent dans Google Cloud. Celle-là a besoin d’un réglage et garde sa valeur.
- Après la prochaine publication, cherchez dans l’appli en ligne chaque valeur que vous avez déplacée.
Chaque publication emballe une nouvelle boîte. La prochaine fonctionnalité que vous demandez est une nouvelle occasion pour une valeur de recevoir le préfixe, et rien entre le builder et internet ne lit le bundle en sortie. Un scan du mois dernier a lu le bundle du mois dernier.
Reeve Monitor lit le bundle pour vous. Il relance les neuf vérifications toutes les heures sur trois applis au plus, vous prévient quand un résultat change, surveille la disponibilité toutes les 60 secondes, et envoie un rapport mensuel. Une clé qui atteint le bundle avec la publication de mardi figure dans le re-scan de cette heure-là, que vous ayez pensé à regarder ou non. Il coûte €12 par mois au tarif de base, avec sept jours gratuits avant de vous facturer ; la page des tarifs est parfois en dessous du chiffre donné ici et jamais au-dessus.
Si vous préférez traiter cela sous forme de liste, la checklist de sécurité en 10 minutes couvre ce point et les autres choses qu’il vaut la peine de désactiver dans une appli fraîchement lancée.
FAQ
Les fichiers .env sont-ils secrets ?
Pour votre dépôt, oui, si le fichier est listé dans .gitignore. Pour vos visiteurs, non. Le build lit .env à chaque publication et copie chaque valeur portant le préfixe VITE_ ou NEXT_PUBLIC_ dans le JavaScript que livre votre appli. Le fichier lui-même ne quitte jamais votre machine ; les valeurs que vous avez étiquetées pour le navigateur, si.
Peut-on exposer VITE_SUPABASE_ANON_KEY sans risque ?
Elle est faite pour être là. La clé anon, appelée clé publiable sur un projet récent, est conçue pour vivre dans un navigateur. Elle dit seulement à quel projet appartient une requête, et chaque requête qu’elle fait est filtrée par vos règles Row Level Security. Cela tient exactement tant que ces règles sont activées et correctes, ce qui est une vérification à part.
NEXT_PUBLIC_ est-il différent de VITE_ ?
Même règle, autre outil de build. Next.js écrit la valeur de toute variable NEXT_PUBLIC_ dans le bundle du navigateur comme une chaîne figée au moment du build, et une variable sans le préfixe arrive vide dans le code du navigateur. Expo fait pareil avec EXPO_PUBLIC_, et les anciens projets Create React App avec REACT_APP_. Quel que soit l’outil qui a construit votre appli, le préfixe veut dire publier.
Comment vérifier ce qu’il y a dans mon bundle ?
Ouvrez l’appli en ligne, appuyez sur F12, choisissez Sources et appuyez sur Ctrl+Maj+F (Cmd+Option+F sur un Mac) pour chercher dans chaque fichier que la page a chargé. Collez les premiers caractères de la valeur. Cherchez la valeur plutôt que le nom de la variable, parce que le build remplace en général le nom par la valeur, si bien que VITE_ peut être absent alors que la clé est là. Notre scan gratuit fait la même lecture depuis l’extérieur en 20 secondes environ.
Où doit vivre une clé secrète dans une appli Lovable ou Bolt ?
Dans une Supabase Edge Function, avec la clé définie dans les secrets de cette fonction dans le tableau de bord Supabase et sans préfixe VITE_ sur son nom. Votre code du navigateur appelle la fonction, la fonction appelle le fournisseur avec la clé, et la clé n’atteint jamais un visiteur. Si la clé a déjà été livrée, renouvelez-la chez le fournisseur avant de la déplacer.
.gitignore protège-t-il mes clés ?
Il garde le fichier .env hors de git, donc personne qui lit votre dépôt ne le voit. Il n’a aucun effet sur le build, qui lit le fichier directement et publie chaque valeur préfixée. Un .env ignoré par git avec VITE_OPENAI_API_KEY dedans livre quand même cette clé à chaque visiteur.