Clés API dans les builds frontend : comment détecter et corriger les fuites
Les clés API dans les builds frontend peuvent exposer vos données. Apprenez à détecter les secrets inclus, à les déplacer côté serveur et à effectuer une rotation en toute sécurité.

Ce que signifie une fuite de secrets via un build frontend
Un build frontend est l'ensemble des fichiers que vos utilisateurs téléchargent pour exécuter votre app dans un navigateur. Il inclut généralement du JavaScript bundlé (souvent un gros fichier), du CSS, des images et d'autres assets statiques. Les outils de build prennent votre code source et le packagent pour que le navigateur le charge rapidement.
Une fuite se produit quand quelque chose que vous vouliez garder privé est intégré dans ces fichiers téléchargeables. S'il est envoyé au navigateur, n'importe qui peut le voir en affichant la source, en ouvrant DevTools ou en téléchargeant le bundle et en le cherchant.
Les secrets sont tout ce qui permet à quelqu'un d'agir au nom de votre appli ou d'accéder à des systèmes protégés. Exemples courants : clés API et tokens de service (Stripe, OpenAI, fournisseurs de cartes, services mail), chaînes de connexion à des bases de données, secrets de signature JWT, clés privées de chiffrement, URLs de webhooks privés, endpoints admin et URLs internes contournant l'auth.
C'est pourquoi les clés API dans les builds frontend doivent être traitées comme publiques. La minification et l'obfuscation n'aident pas. Si le navigateur doit recevoir la valeur pour l'utiliser, un attaquant peut aussi la recevoir.
L'impact est souvent simple : quelqu'un copie votre clé et fait grimper vos factures, vide vos quotas de requêtes, ou utilise votre compte pour spammer un fournisseur. Si le secret ouvre l'accès à des données, cela devient une vraie fuite (dossiers clients, fichiers, actions admin). Les coûts et dommages commencent souvent modestement puis montent vite, surtout si la clé a des permissions larges.
Manières courantes dont les secrets se retrouvent dans le code client
La plupart des fuites ne sont pas des « hacks ». Elles viennent de choix normaux de build et de configuration qui traitent par erreur des secrets comme des réglages publics.
L'injection à la compilation transforme .env en code livré
Des outils comme Vite, Next.js et Create React App peuvent remplacer des variables d'environnement au moment du build. Si un secret est référencé dans du code client, le bundler le remplace souvent par une chaîne littérale. Une valeur qui vivait sagement dans un fichier .env local peut se retrouver intégrée dans le JavaScript de production.
Le piège courant est de se dire « c’est dans une var d'env, donc c’est sûr ». Ce n'est sûr que si elle est utilisée côté serveur et jamais incluse dans les bundles clients.
Les préfixes « publics » sont pour des valeurs publiques
Les frameworks utilisent des préfixes pour marquer les variables qui peuvent être exposées au navigateur (par exemple : VITE_, NEXT_PUBLIC, REACT_APP). Ils conviennent pour des réglages non sensibles comme un feature flag ou un identifiant d'analytics public.
Ce ne sont pas des emplacements pour tout ce qui peut engager des coûts, accéder à des données privées ou donner des permissions élevées : clés API tierces pouvant générer des frais, tokens admin, clés de signature, URLs de base de données, identifiants de service et secrets de webhooks.
Si vous mettez un secret derrière un préfixe public, vous demandez explicitement au système de build de le publier.
Chaînes codées en dur et fichiers de config « utiles »
Les secrets se glissent aussi via des constantes, objets de config et fichiers JSON (par exemple config.ts, firebaseConfig ou settings.json). Ils peuvent ressembler à une config innocente pendant la revue.
C'est particulièrement fréquent dans des prototypes générés par l'IA : des outils collent souvent un exemple fonctionnel avec une clé inline pour que la démo « fonctionne tout de suite ». Si ce code est publié, la clé devient publique.
Les sourcemaps facilitent la découverte
Les sourcemaps ne créent pas la fuite, mais elles peuvent grandement faciliter la recherche. Si les .map sont publiques, un attaquant peut trouver des tokens, endpoints et chaînes ressemblant à des secrets dans le code lisible, plutôt que de scanner des bundles minifiés.
Si vous suspectez une exposition, supposez que la clé est déjà compromise. Planifiez de la déplacer côté serveur et de la faire tourner.
Moyens rapides pour repérer des env vars et clés API exposées
Vous pouvez souvent confirmer une fuite en quelques minutes sans toucher au code. Commencez par ce que le navigateur a déjà.
-
Surveillez les requêtes réseau dans DevTools. Ouvrez l'onglet Network et parcourez les flux qui appellent des services tiers (login, paiement, recherche, upload). Ouvrez une requête et regardez Headers et Preview/Response. Les fuites apparaissent souvent en query params (comme
?api_key=), dans un headerAuthorization: Bearer ...ou dans un corps JSON contenant un token. -
Cherchez ce que vous expédiez réellement. Dans votre dossier de sortie build (souvent
dist/oubuild/), faites une recherche de texte sur les fichiers.jspour des motifs commesk-,api_key,apikey,secret,token,Bearer,BEGIN PRIVATE KEY, ainsi que vos fournisseurs et URLs (Stripe, OpenAI, Twilio, Supabase, etc.). -
Ouvrez le bundle compilé et scannez les chaînes suspectes. Cherchez des identifiants codés en dur, des endpoints complets ou des blobs de config contenant des clés. Un signal courant est une config runtime exposée sur
window(par exemplewindow.__CONFIG__ouwindow.env) qui inclut plus que des IDs publics. -
Vérifiez si les sourcemaps sont accessibles en production. Des fichiers
*.mappublics révèlent les noms de variables et structures d'origine, ce qui accélère la réutilisation.
Un test pratique : si vous pouvez copier un token depuis DevTools et rejouer la même requête depuis un autre profil de navigateur ou un script, ce n'est pas une valeur sûre côté client.
Planifiez le nettoyage avant de changer le code
Une fois la fuite confirmée, ne commencez pas à supprimer des variables ou à faire tourner toutes les clés en même temps. Un peu de plan empêchera des pannes, des logins cassés et des erreurs 401 confuses.
Commencez par lister chaque système externe avec lequel votre app communique et où résident les identifiants aujourd'hui : paiements, mail, stockage de fichiers, analytics, API IA, etc. Incluez staging, preview et dev local. Les fuites arrivent souvent dans les builds preview parce qu'ils paraissent jetables.
Ensuite, séparez ce qui peut être appelé depuis le navigateur de ce qui doit rester côté serveur. Tout ce qui peut dépenser de l'argent, lire des données privées, écrire en base ou contourner les limites doit être inaccessible depuis le client. Si un service propose une clé publishable prévue pour les navigateurs, traitez-la différemment d'une clé secrète.
Créez un inventaire simple à partager en équipe :
- Nom de la clé tel qu'utilisé dans le code (par exemple
STRIPE_SECRET_KEY) - Service et compte/projet auquel elle appartient
- Environnements (dev, staging, prod)
- Où elle apparaît (repo, CI, réglages d'hébergement, fichiers buildés)
- Niveau de risque (faible pour IDs publics, élevé pour secrets)
Décidez ensuite ce qu'il faut faire immédiatement et ce qui peut attendre après changements de code. Faites tourner tout de suite si la clé exposée peut écrire, a de larges scopes ou peut vider des crédits. Différez si la révocation cassera la production avant d'avoir une alternative côté serveur prête.
Prévoyez une courte fenêtre de gel (même 30 à 60 minutes) où personne ne merge, ne déploie ni ne modifie les vars d'env pendant que vous coordonnez. Désignez un responsable pour chaque service et convenez de l'ordre : publier les changements côté serveur d'abord, faire la rotation ensuite, puis redéployer et vérifier.
Étapes pas à pas : auditer, retirer et retester votre build
Traitez cela comme un incident : trouvez ce qui fuit, arrêtez la fuite, puis prouvez qu'elle est résolue.
1) Auditez ce qui existe (repo et sortie build)
Cherchez dans votre code source et vos assets compilés (les fichiers finaux que les utilisateurs téléchargent). Ne vous arrêtez pas au dépôt. Les fichiers buildés peuvent cacher des secrets dans des bundles minifiés.
Un workflow simple :
- Scannez les motifs ressemblant à des secrets : tokens longs, préfixes fournisseurs (par exemple
sk_,xoxb-) et chaînesBearer. - Vérifiez l'utilisation des env :
process.env,import.meta.envet toutes les variables publiques du framework. - Cherchez dans le JS buildé des chaînes évidentes : votre domaine API, préfixes de clés ou blobs JSON contenant des identifiants.
- Confirmez où ça apparaît : dans la source, dans la config, ou seulement après l'étape de build.
- Notez chaque correspondance et où elle a été trouvée avant de modifier quoi que ce soit.
2) Confirmez ce qui est réellement sensible
Toutes les valeurs qui ressemblent à des clés ne sont pas forcément dangereuses. Certains services fournissent des clés publishables pour les navigateurs, d'autres donnent un accès complet au compte.
Posez-vous deux questions :
- Que peut faire ce credential ?
- Un attaquant peut-il l'utiliser depuis sa machine ?
Si la réponse est oui et oui, traitez-la comme sensible.
3) Retirez-la du client, remplacez par un appel serveur
Supprimez le secret du code client et de toute variable d'environnement à l'étape de build qui finit dans le navigateur. Remplacez l'appel client->fournisseur par un endpoint serveur qui utilise le secret côté serveur. Le frontend doit appeler votre endpoint, pas le fournisseur directement.
4) Retestez avec une clé fraîche (d'abord non-production)
Créez une nouvelle clé, testez bout en bout en staging ou preview, puis basculez uniquement en production. Rebuild et rescannez la sortie pour confirmer que le secret a bien disparu.
Déplacer les secrets côté serveur sans casser l'app
La solution la plus sûre n'est pas de « mieux cacher dans le JavaScript ». C'est de ne plus envoyer du tout le secret au navigateur.
Le modèle de base : un proxy côté serveur
Faites en sorte que le client appelle votre endpoint backend (ou une fonction serverless). Cet endpoint appelle l'API tierce et ajoute le secret côté serveur. Le navigateur ne voit jamais la clé.
Approche simple qui conserve le comportement UI :
- Remplacez l'appel direct du frontend vers l'API tierce par un appel vers votre endpoint.
- Côté serveur, lisez la clé depuis des variables d'environnement accessibles uniquement au serveur.
- Retournez seulement les données dont l'UI a besoin, pas la réponse complète upstream.
Évitez de construire un proxy ouvert. Validez les entrées côté serveur : n'autorisez que les routes nécessaires, vérifiez les champs requis et bloquez les URLs ou headers inattendus. Si votre endpoint accepte une « URL cible » venant du client, vous avez recréé la fuite sous une autre forme.
Préférez des tokens de courte durée quand c'est possible
Si un tiers le permet, générez des tokens éphémères côté serveur et envoyez seulement le token au client. Des tokens qui expirent en quelques minutes sont plus sûrs que des clés longues intégrées dans un bundle.
Associez cela à des contrôles serveurs simples comme le rate limiting et la journalisation des requêtes pour que les abus remontent vite (pics soudains, échecs répétés, payloads inhabituels).
Limitez le code client à des identifiants publics (comme un ID de projet public) et des flags non sensibles (useSandbox: true). Tout ce qui permet l'accès ou peut créer des frais appartient au serveur.
Faire tourner les clés en toute sécurité avec un minimum d'interruption
Faire tourner une clé exposée n'est pas juste « générer une nouvelle et coller ». Si vous révoquez trop tôt, les vrais utilisateurs auront des erreurs. Si vous laissez l'ancienne active trop longtemps, la fuite reste exploitable. Visez un chevauchement court et contrôlé.
La méthode la plus sûre est la rotation en deux phases : introduire la nouvelle clé d'abord, confirmer que le trafic l'utilise, puis révoquer l'ancienne.
Séquence de rotation à faible risque
Faites ceci en surveillant vos logs :
- Créez une nouvelle clé dans le dashboard du fournisseur. Nommez-la clairement (par exemple :
prod-2026-01-rotation). - Ajoutez la nouvelle clé dans un store de secrets côté serveur (pas dans le build frontend). Laissez l'ancienne active pour l'instant.
- Déployez une version qui utilise la nouvelle clé.
- Vérifiez l'utilisation dans les logs du fournisseur et vos logs applicatifs.
- Révoquez l'ancienne après un court chevauchement (souvent 15 à 60 minutes, plus si vos déploiements sont lents ou si vous avez des jobs longue durée).
Mettez un rappel pour révoquer l'ancienne clé. Les équipes oublient souvent en plein incident.
N'oubliez pas les « appelants cachés »
Avant toute révocation, assurez-vous d'avoir mis à jour :
- Jobs en arrière-plan et tâches planifiées
- Webhooks et intégrations tierces qui appellent votre API
- Applications mobiles (elles peuvent prendre du temps à se mettre à jour)
- Environnements de staging qui partagent accidentellement des clés prod
- Configs de test local encore sur l'ancienne clé
Après le déploiement, surveillez de près les taux d'erreur et d'échec d'authentification. Si quelque chose casse, revenez en arrière rapidement ou prolongez le chevauchement pendant que vous localisez l'appelant manquant.
Erreurs courantes qui maintiennent la fuite
Si vous voyez une valeur dans le navigateur (View Source, DevTools, requêtes réseau, JS bundlé), un attaquant peut aussi la voir.
- La minification et l'obfuscation ne protègent pas. Une clé embarquée dans un bundle reste une clé, même si elle est morcelée ou renommée.
- Les tokens longue durée dans
localStorageousessionStoragesont faciles à voler via XSS. Préférez des sessions côté serveur (cookies HTTP-only) et gardez les vrais identifiants sur le serveur. - Les endpoints de debug et routes admin restent parfois ouverts. Tout ce qui contourne l'auth ou déverse des données en test peut devenir un chemin vers la prod.
- La journalisation côté client peut divulguer des secrets. Évitez de logger intégralement headers, tokens ou dumps de config dans le navigateur.
- CORS n'est pas une frontière de sécurité. Il limite seulement quels navigateurs peuvent lire les réponses, pas les appels directs depuis des scripts, serveurs ou outils comme curl.
Checklist rapide avant déploiement
Utilisez ceci juste avant de pousser en prod.
5 vérifications qui évitent la plupart des fuites
- Confirmez que le bundle client est propre : cherchez dans la sortie build (pas seulement dans la source) des motifs de clés (
sk_,AIza,Bearer,-----BEGIN, ou le nom d'un fournisseur). Vérifiez aussi que vous n'utilisez pas de préfixes publics (NEXT_PUBLIC_,VITE_) pour des éléments privés. - Considérez les sourcemaps comme sensibles : si vous les gardez en production, restreignez l'accès. Désactivez-les si vous n'en avez pas besoin.
- Placez les secrets derrière une frontière serveur : tout appel nécessitant un secret doit passer par votre serveur (ou une fonction serverless). Le navigateur n'envoie que l'intention et reçoit des résultats sûrs.
- Verrouillez les endpoints serveur : validez les entrées, vérifiez l'auth quand il le faut et appliquez des limites de débit.
- Faites tourner les clés et surveillez les abus : créez de nouvelles clés d'abord, déployez, puis révoquez les anciennes. Surveillez tout pic de trafic ou dépenses anormales.
Exemple simple : si votre frontend appelle directement une API de cartographie ou d'IA, cette clé sera copiée et réutilisée par des inconnus. Au lieu de cela, le navigateur appelle votre endpoint backend, et le backend appelle le fournisseur avec la clé. Dans le navigateur, il n'y a rien de précieux à voler.
Exemple : un prototype généré par l'IA qui a embarqué une vraie clé API
Un fondateur crée rapidement un MVP dans Lovable. Il déploie le jour même. L'app appelle une API payante (un LLM, cartes, email ou analytics) et la clé est définie en variable d'environnement. Le souci, c'est que l'outil de build copie cette valeur dans le bundle navigateur, donc la clé se retrouve dans le JavaScript livré.
Un curieux ouvre DevTools, cherche « key » ou le nom du fournisseur et la trouve dans un fichier minifié. Il la copie et lance des requêtes depuis son script. En quelques heures, l'usage monte en flèche, les coûts augmentent, et le dashboard du fournisseur montre du trafic qui ne correspond pas aux vrais utilisateurs.
La correction est simple, mais l'ordre compte :
- Déplacez l'appel API derrière une route serveur (ou serverless) pour que le navigateur ne voie jamais le secret.
- Ajoutez des protections de base sur cette route : vérifications d'auth, limites de débit et validation des entrées.
- Faites tourner la clé exposée et révoquez l'ancienne après un bref chevauchement.
- Passez en revue les logs du fournisseur pour IP suspectes, endpoints inhabituels et trafic hors heures normales.
- Ajoutez des alertes et des limites de dépenses pour être averti avant que les coûts n'explosent.
Après la modification, l'UI déclenche toujours la même fonctionnalité ; elle appelle juste votre endpoint backend au lieu du fournisseur. Le backend lit la clé depuis des variables d'environnement côté serveur et parle au tiers au nom de l'utilisateur. Dans le navigateur, il n'y a rien de valeur à voler.
Étapes suivantes si votre codebase est en pagaille ou générée par l'IA
Si votre app a été générée avec v0, Cursor, Replit, Bolt ou Lovable, supposez que la même clé peut être copiée à plusieurs endroits : un .env, un helper de config, un log « temporaire » et le bundle client. C'est pour ça que les correctifs rapides échouent : vous supprimez une copie, mais une autre est toujours publiée.
Concentrez-vous sur toutes les voies que la valeur peut prendre, pas seulement l'évidence. Cela inclut l'injection à la compilation, les objets de config bundlés, les fonctions serverless et les flux d'auth qui font transiter des tokens via le navigateur.
Approche pratique quand le repo est difficile à faire confiance :
- Identifiez les secrets les plus critiques (paiement, mail, base de données, APIs admin).
- Cherchez dans le dépôt des motifs de clés et des noms d'env, puis vérifiez aussi la sortie build.
- Tracez comment les requêtes sont faites : navigateur -> fournisseur direct, ou via votre serveur ?
- Décidez du nouvel emplacement côté serveur pour chaque secret (route API, service backend, proxy).
- Planifiez la rotation des clés après le changement de code pour éviter des pannes surprises.
Si vous avez hérité d'un prototype IA cassé et avez besoin d'une intervention rapide et axée sécurité, FixMyMess (fixmymess.ai) propose un diagnostic et une remédiation pour des problèmes comme les secrets bundlés, l'auth cassée et les patterns de requêtes non sûrs. Ils offrent un audit gratuit pour cartographier ce qui est exposé avant de commencer la rotation des clés.
Questions Fréquentes
Qu'est-ce qui compte comme « secret » dans un build frontend ?
Si le navigateur a besoin de la valeur pour fonctionner, elle est pratiquement publique. Tout ce qui peut dépenser de l'argent, lire des données privées, écrire dans votre base ou signer/vérifier des tokens doit être considéré comme secret et rester côté serveur.
La minification ou l'obfuscation protègent-elles les clés API dans les bundles JavaScript ?
Non. La minification change seulement l'apparence du code, pas son contenu. N'importe qui peut télécharger votre bundle et rechercher des chaînes, donc une clé intégrée dans JavaScript reste exposée.
Les variables d'environnement `NEXT_PUBLIC_`, `VITE_` et `REACT_APP_` sont-elles sûres pour les secrets ?
Ces préfixes sont explicitement pensés pour des valeurs qui peuvent être exposées au navigateur. Servez-vous-en uniquement pour des réglages non sensibles comme des IDs publics ou des flags d'interface, et jamais pour des clés secrètes, tokens de service ou URLs de base de données.
Comment savoir rapidement si une clé fuit sans modifier le code ?
Ouvrez votre site, lancez DevTools et inspectez les requêtes réseau pour des query params, headers ou corps contenant des tokens. Ensuite, cherchez dans les fichiers compilés des motifs comme "Bearer", "api_key", des noms de fournisseurs ou des préfixes connus.
Pourquoi les sourcemaps publiques aggravent-elles les fuites de secrets ?
Les sourcemaps ne créent pas la fuite, mais ils la facilitent fortement en révélant le code lisible et les noms de variables. Si vous les laissez en production, limitez leur accès ou désactivez-les si vous n'en avez pas besoin.
Que dois-je faire en premier après avoir trouvé une clé exposée ?
Considérez-la comme compromise et stoppez la fuite avant d'ergoter. Notez où vous l'avez trouvée, identifiez ce qu'elle permet de faire, et planifiez le déplacement côté serveur puis la rotation sans casser la production.
Quelle est la façon la plus sûre de sortir un secret du frontend sans casser les fonctionnalités ?
Remplacez l'appel direct du navigateur vers le fournisseur par un endpoint backend (ou une fonction serverless) qui appelle le fournisseur avec des variables d'environnement accessibles uniquement côté serveur. Le frontend envoie l'intention utilisateur et reçoit seulement les données nécessaires, pas la clé.
Est-il acceptable de stocker des tokens dans localStorage ou sessionStorage ?
C'est risqué : toute faille XSS peut voler ces tokens et les rejouer. Préférez des sessions gérées par le serveur et des identifiants à courte durée de vie quand c'est possible, et évitez de stocker des secrets longue durée dans le stockage du navigateur.
Comment faire tourner une clé exposée sans provoquer de downtime ?
Adoptez une rotation en deux étapes : déployez d'abord le support pour la nouvelle clé, vérifiez que le trafic l'utilise, puis révoquez l'ancienne après une courte période de chevauchement. Révoquer trop tôt casse les utilisateurs ; attendre trop longtemps laisse l'attaquant agir.
Les prototypes générés par l'IA fuient-ils plus souvent des secrets, et FixMyMess peut-il aider ?
Oui. Le code généré par l'IA intègre souvent des clés pour que l'exemple fonctionne immédiatement, et ces valeurs peuvent être copiées à plusieurs endroits. Si le dépôt vous semble peu fiable, FixMyMess propose un audit gratuit du code, trace les chemins d'exposition et livre une remédiation axée sécurité — souvent en 48–72 heures, avec un plan de rebuild en cas d'urgence.