15 août 2026

Clé Stripe sk_live_ exposée dans le JS : comment ça arrive avec Bolt, Lovable ou v0

C'est la faille la plus grave qu'on détecte, et statistiquement l'une des plus fréquentes sur les apps générées avec Bolt, Lovable, v0 ou Cursor : une clé secrète Stripe sk_live_ qui se retrouve dans le JavaScript envoyé au navigateur. Voici comment ça arrive, comment la trouver toi-même, et comment l'éviter.

Deux clés Stripe, deux usages très différents

Stripe donne toujours une paire de clés par environnement (test et live) :

  • pk_live_... (clé publique) — conçue pour être exposée côté client. Elle sert à initialiser Stripe.js et Stripe Elements dans le navigateur.
  • sk_live_... (clé secrète) — donne un accès total à l'API Stripe : créer des charges, déclencher des remboursements, lister tous les clients et leurs moyens de paiement, changer la configuration du compte. Elle ne doit jamais quitter ton serveur.

Le problème, c'est que les deux commencent par sk_ ou pk_, et un assistant IA qui génère un composant de paiement en une seule passe ne fait pas toujours la distinction — surtout quand on lui demande "ajoute Stripe" sans préciser l'architecture client/serveur.

Comment la clé secrète finit côté client

Trois scénarios reviennent tout le temps :

  • Un composant React appelle new Stripe(process.env.STRIPE_SECRET_KEY) directement dans un fichier qui s'exécute côté navigateur (pas une API route). Le bundler l'embarque tel quel dans le JS public.
  • La variable d'environnement est préfixée NEXT_PUBLIC_ (ou VITE_) par erreur — ce préfixe dit explicitement au bundler "inclus ça dans le bundle client", et il obéit sans distinguer une clé publique d'une clé secrète.
  • La clé est écrite en dur ("juste pour tester") dans un fichier .tsx pendant le prototypage, et n'est jamais retirée avant le déploiement.

Dans les trois cas, le résultat est identique : ouvre les DevTools, onglet Network ou Sources, et la clé est là, en clair, dans un fichier JS téléchargé par n'importe quel visiteur.

Comment la repérer toi-même en 30 secondes

Pas besoin d'outil : ouvre ton site, fais Ctrl+U (ou Cmd+Option+U) pour voir le source, puis dans les DevTools va dans l'onglet Network, filtre sur JS, et fais Ctrl+F dans chaque fichier chargé en cherchant sk_live_ ou sk_test_. C'est exactement ce que Vetora automatise avec cette regex sur le HTML et tous les scripts chargés :

const stripeLiveKeyRegex = /sk_live_[A-Za-z0-9]{20,}/;
const stripeTestKeyRegex = /sk_test_[A-Za-z0-9]{20,}/;

Une clé sk_test_ exposée est classée élevée plutôt que critique — elle ne donne accès qu'à l'environnement de test — mais elle mérite d'être corrigée quand même : elle indique souvent que la même erreur d'architecture existe côté production, avec sk_live_ cette fois.

Le correctif

Toute opération qui utilise la clé secrète doit passer par le serveur — une API route, une edge function, un webhook. Le client ne parle jamais directement à l'API Stripe avec sk_, il appelle ton propre backend, qui lui appelle Stripe :

// app/api/create-checkout/route.ts (côté serveur uniquement)
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const session = await stripe.checkout.sessions.create({ /* ... */ });
  return Response.json({ url: session.url });
}

Côté client, seule la clé publique apparaît, et c'est normal — c'est fait pour :

// composant client
import { loadStripe } from '@stripe/stripe-js';
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

Si tu découvres une clé sk_live_ déjà exposée : fais-la pivoter immédiatement dans le dashboard Stripe (Developers → API keys → Roll key). Une clé qui a été publique, même quelques heures, doit être considérée comme compromise.

Tu utilises Stripe sur une app générée avec Bolt, Lovable, v0 ou Cursor ? Scanne ton app pour vérifier qu'aucune clé secrète ne traîne dans ton JavaScript.