Rustine

Clé API exposée dans le JavaScript : la trouver et la révoquer

Une clé API écrite dans du JavaScript exécuté par le navigateur n'est pas « cachée » : elle est publique. N'importe qui peut la lire dans le bundle, les source maps ou l'historique git. La marche à suivre tient en trois étapes : retrouver la clé, la révoquer, puis la déplacer côté serveur.

Clé API exposée dans le JavaScript : la trouver et la révoquer
Sommaire
  1. Pourquoi ta clé API est publique dès qu'elle touche le navigateur
  2. Ce que tu risques si tu ne fais rien
  3. Où chercher les clés qui fuient
  4. 1. Ton propre bundle de production
  5. 2. Les source maps
  6. 3. L'historique git
  7. 4. Les variables d'environnement côté client
  8. Comment révoquer une clé exposée, service par service
  9. Stripe
  10. OpenAI
  11. Supabase
  12. Google Maps et les autres clés « publiques »
  13. Le correctif : déplacer la clé côté serveur
  14. La checklist de réaction en 5 étapes
  15. Le prompt à coller dans ton agent
  16. Questions fréquentes
  17. Peut-on « cacher » une clé API avec de l'obfuscation ?
  18. Une clé dans un fichier .env côté client est-elle en sécurité ?
  19. Que faire si ma clé a déjà été publiée sur GitHub ?
  20. Combien de temps faut-il pour réagir ?
  21. Les apps générées par Lovable ou Bolt sont-elles plus exposées ?
  22. Sources

Une clé s'échappe d'un fichier JavaScript dans les outils de développement du navigateur

Pourquoi ta clé API est publique dès qu'elle touche le navigateur

Quand ton app tourne, le navigateur télécharge ton JavaScript. Pour l'exécuter, il doit pouvoir le lire. Donc tout ce que tu mets dans ce code — une clé Stripe secrète, un token OpenAI, une clé service_role Supabase — part avec lui.

Il n'existe pas de « variable cachée » côté client. Les préfixes NEXT_PUBLIC_ (Next.js) ou VITE_ (Vite) existent justement pour désigner ce qui est volontairement public : ces variables sont injectées en clair dans le bundle au moment du build. Si tu y mets un secret, tu le publies.

L'obfuscation n'y change rien. Elle rend la lecture moins confortable, elle ne rend pas un secret inaccessible. Ce qui arrive dans le navigateur appartient au visiteur, point.

Ce que tu risques si tu ne fais rien

Une clé qui fuit n'est pas un détail cosmétique. Concrètement :

  • Une facture : une clé OpenAI ou Stripe est payée à la consommation. Une clé volée peut vider ton quota en quelques heures.
  • Une base ouverte : une clé service_role Supabase contourne la sécurité des lignes (RLS) et permet de lire, modifier ou supprimer toute ta base.
  • Une obligation de notifier : si des données clients partent, le RGPD t'impose d'en informer les personnes concernées et la CNIL dans certains cas.

Où chercher les clés qui fuient

Si tu soupçonnes une fuite, regarde dans cet ordre.

1. Ton propre bundle de production

Après un build, ouvre le dossier généré et cherche les motifs de tes fournisseurs. C'est ton propre code, tu as le droit de l'inspecter.

  • Next.js : grep -r "sk_live\|sk-\|service_role\|AIza" .next/static
  • Vite / SvelteKit : même idée dans dist/ ou .svelte-kit/output/

Tu y verras exactement ce qui est parti dans le navigateur.

2. Les source maps

Si ton build génère des fichiers .map (source maps), ils contiennent souvent le code source non minifié. Une clé effacée du fichier minifié peut y réapparaître. Vérifie que productionBrowserSourceMaps (Next.js) ou build.sourcemap (Vite) est désactivé en production, et que *.map est dans .gitignore s'il en reste.

3. L'historique git

Un secret supprimé aujourd'hui reste dans l'historique. git log -S "sk_live" ou git log -S "service_role" cherche une chaîne dans tout l'historique. Si tu trouves, considère la clé comme brûlée : révoque-la, ne te contente pas de la supprimer du repo. Et note qu'un commit publié sur un dépôt public ne disparaît pas des forks et des caches.

4. Les variables d'environnement côté client

Dans Next.js, tout ce qui commence par NEXT_PUBLIC_ est injecté dans le navigateur. Dans Vite, c'est VITE_. Une variable sans préfixe reste côté serveur ; avec le préfixe, elle part côté client. La confusion entre les deux est la cause n°1 des fuites chez les vibecodeurs.

Une loupe inspecte les fichiers du projet à la recherche d'une clé exposée

Comment révoquer une clé exposée, service par service

Révoque d'abord, corrige ensuite. Une clé publique ne se « récupère » pas : on la désactive et on en crée une neuve.

Stripe

Stripe distingue deux familles de clés. La clé publiée (pk_live_, pk_test_) est faite pour le navigateur : elle sert à tokeniser un paiement, elle n'autorise rien d'autre. La clé secrète (sk_live_, sk_test_) ne doit jamais quitter le serveur : elle signe des opérations au nom de ton compte.

Si une clé sk_ a atterri dans ton JavaScript : Dashboard Stripe → Développeurs → Clés API → révoque la clé, puis régénères-en une. Crée de préférence une clé restreinte (préfixe rk_) limitée à la seule opération dont ton backend a besoin.

OpenAI

Les clés OpenAI commencent par sk-. Elles facturent à la consommation. Platform OpenAI → API keys → révoque la clé exposée. Pense ensuite à plafonner le budget du projet et à limiter les permissions de la nouvelle clé.

Supabase

Supabase utilise deux clés : la clé anon (publique, faite pour le navigateur) et la clé service_role (qui contourne la RLS). Si service_role apparaît dans ton bundle, c'est une urgence. La différence entre les deux est détaillée ici : Clé anon vs service_role Supabase : laquelle exposer ?.

Pour révoquer : Dashboard Supabase → Settings → API Keys → régénère la clé service_role (et l'anon si besoin). Ensuite, la RLS redevient ta vraie frontière : Supabase RLS : sécuriser ton app codée avec l'IA.

Google Maps et les autres clés « publiques »

Certaines clés sont conçues pour le navigateur (Google Maps AIza..., Stripe pk_). Elles se protègent autrement : restriction par domaine référent, par API activée, et quotas. Une clé Maps sans restriction de site peut être réutilisée ailleurs et gonfler ta facture. Dans Google Cloud Console, restreins la clé au domaine de ton app.

Le correctif : déplacer la clé côté serveur

Une fois la clé révoquée, refais le chemin correctement. Le principe tient en une phrase : le navigateur ne parle jamais directement au fournisseur avec un secret, il passe par ton serveur.

Schéma : le navigateur passe par un serveur qui garde la clé API à l'abri

Concrètement, dans une app Next.js :

  1. Crée une route API (app/api/.../route.ts) ou une Server Action.
  2. Mets la clé dans .env sans préfixe NEXT_PUBLIC_.
  3. La route lit la clé côté serveur, appelle le fournisseur, et renvoie au navigateur uniquement le résultat.

Le navigateur reçoit une réponse, jamais la clé. C'est le même schéma quel que soit le framework : un endpoint serveur qui joue le proxy.

Pour Supabase, l'exception utile : la clé anon est faite pour rester dans le navigateur, à condition que la RLS soit bien configurée. La clé n'est pas le problème dans ce cas, c'est l'absence de policies. Et une clé n'est que la porte d'entrée : derrière, un IDOR laisse lire les données des autres si ton API ne vérifie pas à qui appartient chaque ressource.

La checklist de réaction en 5 étapes

  1. Repère la clé (bundle, source maps, historique git).
  2. Révoque-la chez le fournisseur.
  3. Régénère une clé avec le minimum de permissions.
  4. Déplace l'appel côté serveur (.env sans préfixe public).
  5. Vérifie le nouveau build : plus rien dans .next/static ou dist/.

Le prompt à coller dans ton agent

C'est la signature de Rustine : on ne s'arrête pas au constat. Colle ce prompt dans Cursor, Claude Code ou Lovable pour corriger ta fuite :

code
Tu vas m'aider à sortir les secrets du frontend de mon app.

1. Parcours tout le repo et liste, fichier par fichier, tout ce qui ressemble à un secret exécuté côté navigateur : clés commençant par sk_live_, sk_test_, sk-, service_role, AIza, ainsi que les variables NEXT_PUBLIC_ ou VITE_ qui contiennent un secret.
2. Signale-moi ce qui est public et sans risque (pk_live_, pk_test_, clé anon Supabase, NEXT_PUBLIC_ sans secret) — on n'y touche pas.
3. Pour chaque vrai secret trouvé dans le frontend : crée une route API serveur (ou une Server Action) qui fait l'appel à la place du navigateur, et déplace la clé dans .env SANS préfixe public.
4. Ajoute .env, .env.local et *.map à .gitignore.
5. Vérifie ensuite que : plus aucun secret n'apparaît dans .next/static ou dist/ après un nouveau build, et que la route API lit la clé uniquement côté serveur.

Avant de lancer l'agent, révoque les clés exposées (section précédente). Le prompt corrige le code, il n'invalide pas une clé déjà publique.

Questions fréquentes

Peut-on « cacher » une clé API avec de l'obfuscation ?

Non. L'obfuscation rend le code moins lisible, elle ne rend pas un secret inaccessible. Tout ce qui part dans le navigateur peut être extrait. La seule vraie protection, c'est de garder le secret côté serveur.

Une clé dans un fichier .env côté client est-elle en sécurité ?

Uniquement si le framework ne l'injecte pas dans le bundle. Dans Next.js, une variable NEXT_PUBLIC_ est publique par définition. Une variable sans préfixe reste côté serveur et ne doit jamais être lue dans un composant client.

Que faire si ma clé a déjà été publiée sur GitHub ?

Révoque-la immédiatement, puis nettoie l'historique. Active la détection de secrets de GitHub. Une clé révoquée est inoffensive, même si elle reste visible dans un ancien commit.

Combien de temps faut-il pour réagir ?

Quelques minutes suffisent pour révoquer. Plus tu attends, plus une clé facturée à la consommation (OpenAI, Stripe) peut te coûter cher. Révoque avant même d'avoir fini de corriger le code.

Les apps générées par Lovable ou Bolt sont-elles plus exposées ?

Souvent, oui : les assistants génèrent du code qui met un secret dans le frontend sans prévenir. Les failles typiques des apps Lovable recoupent exactement ce cas : clé service_role dans le navigateur, bucket public, RLS absente.

Sources


Une clé qui fuit n'est rarement qu'un symptôme : là où il y a un secret dans le bundle, il y a souvent des policies manquantes, un bucket ouvert ou une API trop bavarde derrière. Plutôt que de tout vérifier à la main, fais scanner ton site comme le ferait un inconnu. Rustine montre ce qui fuit avec la preuve, et te livre le correctif à coller dans ton agent — le serveur MCP peut même scanner et corriger à ta place. Scanner mon site avec Rustine.

Ton app est-elle touchée ? Regarde en une minute.Scanner mon site