J’ai livré la première version publique de GeminiOmni un mercredi après-midi. C’était une page d’accueil épurée avec un seul outil fonctionnel — une version précoce de l’éditeur d’images Nano Banana 2 — et un bouton « Essayez gratuitement, sans inscription ».
Le site est en ligne à 14h42, heure de Berlin. À 15h05, mon tableau de bord de facturation Google Cloud hurlait.
Ce billet est le détail complet de ce qui a mal tourné, ce que ça a coûté, et ce que j’ai changé avant la fin de la même journée de travail. Si vous construisez quoi que ce soit qui touche à une API IA payante, prenez ça comme une leçon gratuite — la plupart des gens n’écrivent pas sur les parties embarrassantes.
La séquence réelle
Voici la chronologie d’après mon journal d’incidents (oui, j’en tiens un — je le recommande) :
- 14h42 Déploiement réussi. Site en ligne sur geminiomni-ai.com. J’ai posté une courte démo sur une petite instance Mastodon et suis allé me faire un café.
- 14h58 De retour, une notification Slack de Google Cloud : « Votre facture a dépassé 5 $ pour le cycle en cours. » Inquiétant, car le cycle n’avait que seize minutes.
- 15h05 Connecté à la console API Gemini. J’ai vu 412 requêtes de génération d’images en 20 minutes, depuis 38 adresses IP distinctes réparties sur trois continents.
- 15h08 Site mis hors ligne. Clé API désactivée. Généré une nouvelle clé que je n’allais pas encore utiliser.
- 15h42 Commencé à réécrire l’architecture.
- 18h30 Livré la v2 avec le correctif. Site remis en ligne.
- 23h00 Facture totale pour les quatre heures de mésaventure : 84,30 $.
Pour contexte, j’avais budgété 200 $ pour tout le premier mois de dépenses API. J’en ai brûlé 42 % en dix-sept minutes à cause d’une erreur que j’aurais dû savoir éviter.
Ce que j’avais fait
La première version de l’éditeur d’images était câblée comme suit : une page Next.js avec un composant React qui prenait le prompt et l’image uploadée de l’utilisateur, puis appelait directement l’API Google Gemini Image depuis le navigateur avec le client @google/genai et une clé API intégrée dans NEXT_PUBLIC_GEMINI_API_KEY.
Oui. J’ai mis une clé API payante dans NEXT_PUBLIC_*. Que Next.js intègre docilement dans le JavaScript client. Qui est visible par quiconque ouvre les outils de développement.
Pour ma défense — et cette défense n’est pas très solide — le prototype original était une expérience locale privée. J’avais câblé la clé comme variable d’environnement côté client parce que j’itérerais rapidement et ne voulais pas perdre de temps à configurer une route API. Quand j’ai préparé le déploiement public, j’ai modifié environ quarante choses et l’emplacement de la clé n’en faisait pas partie.
L’erreur n’est pas propre à moi. Une demi-douzaine de personnes que je respecte ont fait des variantes de ça en 2024-2025. C’est l’incident de coût IA évitable le plus courant que j’aie vu.
Comment ça a été aspiré aussi vite
Le détail qui m’a surpris, c’est la rapidité avec laquelle les requêtes ont afflué. J’avais posté sur une instance Mastodon d’environ 600 abonnés, dont aucun n’aurait fait ça par malveillance. Le trafic devait venir d’ailleurs.
Voici ce que j’ai déduit des journaux de requêtes :
- En moins de cinq minutes après la mise en ligne, un crawler automatisé a scanné le site, extrait la clé
NEXT_PUBLIC_GEMINI_API_KEYdu JavaScript, et l’a publiée sur un canal d’échange de clés dont j’ignorais l’existence. - En dix minutes, cette clé était utilisée par environ 38 adresses IP différentes pour générer des images sans rapport avec mon produit. La plupart des prompts que j’ai échantillonnés dans les journaux étaient génériques (« une voiture de sport rouge », « fille anime dans une forêt »). Quelques-uns testaient les limites du modèle avec des prompts adversariaux.
- À la quinzième minute, quelqu’un exécutait une boucle serrée générant des images aussi vite que Google le permettait.
C’est la partie que je veux faire comprendre aux développeurs indépendants : il existe des crawlers dont le seul travail est de scanner les nouveaux sites à la recherche de clés API. Ils surveillent les émissions récentes de certificats SSL, visitent les nouveaux domaines, analysent les bundles JavaScript et publient les clés découvertes en quelques minutes. Vous n’avez pas besoin d’être célèbre. Vous n’avez pas besoin d’être sur un subreddit populaire. Il suffit d’être en ligne avec une clé divulguée.
L’architecture que j’ai livrée cet après-midi-là
Le correctif était structurel, pas cosmétique. J’ai réécrit le flux de données pour qu’aucune clé API ne soit jamais envoyée au navigateur, et l’architecture est maintenant celle que nous utilisons aujourd’hui :
Navigateur (aucune clé, jamais)
↓ POST /api/ai/generate { prompt, imageFile }
Route API Next.js (côté serveur)
↓ envConfigs.gemini_api_key (conservée sur le serveur)
API Google GeminiTrois changements concrets :
1. Déplacer la clé API dans une variable d’environnement réservée au serveur. Renommé NEXT_PUBLIC_GEMINI_API_KEY en GEMINI_API_KEY. Next.js refuse par défaut d’envoyer les variables d’environnement réservées au serveur vers le client. Précaution supplémentaire : j’ai ajouté une vérification au démarrage qui fait planter l’appli si une variable d’environnement contenant « API_KEY » ou « SECRET » commence par NEXT_PUBLIC_.
2. Créer un proxy /api/ai/*. Chaque appel de modèle passe par un unique point de terminaison côté serveur. Le navigateur envoie le prompt et les fichiers téléchargés par l’utilisateur ; le serveur attache la clé API, effectue l’appel effectif à l’API Gemini et renvoie uniquement le résultat. Ça semble évident, mais c’est tout le correctif.
3. Ajouter une limite de débit par IP sur le proxy. Même avec la clé sécurisée, je ne voulais pas qu’un utilisateur inonde les générations du niveau gratuit pour épuiser les crédits. Le proxy limite à 10 requêtes par IP par minute sur le niveau gratuit et 60 par minute sur Pro+. Cela n’aurait pas empêché l’incident initial — les attaquants changeaient d’IP — mais cela rend la prochaine classe d’abus beaucoup plus difficile.
La modification totale du code était d’environ 180 lignes ajoutées et 90 supprimées. Il m’a fallu 2 heures 48 minutes de « site hors ligne » à « site de nouveau en ligne avec la nouvelle architecture ». Pas génial, mais pas catastrophique.
Ce que je ferais différemment la prochaine fois
La réponse honnête est : ne jamais livrer une appli IA publique avec un appel API côté client vers un service payant. Ni pour « juste un prototype ». Ni pour « tests MVP ». Ni pour « une démo rapide à des amis ». Le modèle de menace, ce ne sont pas les utilisateurs malveillants. C’est l’infrastructure automatisée qui existe déjà exactement pour ce scénario.
Quelques leçons supplémentaires issues de l’incident :
Vérifier le bundle avant le déploiement. Avant de livrer, exécutez grep -r "NEXT_PUBLIC_" .next/static/ et lisez ce qu’il contient. Si quoi que ce soit ressemble à un identifiant, corrigez-le avant de mettre en ligne. J’ai maintenant ceci comme hook git pré-déploiement.
Définir un plafond quotidien strict. Google Cloud permet de plafonner votre API Gemini à un montant quotidien en dollars. J’ai réglé le mien à 50 $ pour le premier mois et 200 $ ensuite. Si le plafond est atteint, l’API renvoie des erreurs et je reçois un email — bien mieux que de laisser la facture monter à 5 000 $ en une seule mauvaise nuit.
Utiliser le modèle le moins coûteux qui fonctionne. Je dépensais 0,151 $ par édition 4K Nano Banana 2 sur le niveau gratuit. Même avant l’attaque, mon économie unitaire était fragile. J’achemine maintenant les générations du niveau gratuit via Nano Banana 2K à 0,045 $ par édition et je garde le 4K pour Pro+. La réduction de coût par 3,4 aurait transformé la facture de 84 $ en environ 25 $.
Limiter la longueur du prompt. L’un des abuseurs envoyait des prompts massifs avec des données d’image intégrées, facturés par jeton par Gemini. Le proxy limite désormais la taille du prompt entrant à 50 Ko. Un utilisateur légitime n’a jamais dépassé cette limite ; un abuseur la rencontre dès la première requête.
Les preuves
Pour ceux qui veulent vérifier que je n’invente rien : ma console API Gemini pour le 28 avril montre 411 requêtes de 14h42 à 15h08, avec le rapport de dépenses indiquant 83,96 $ de frais de génération d’images et 0,34 $ en jetons d’entrée. J’ai envoyé un email au support Google Cloud ce soir-là et ils ont refusé le remboursement — logique, la clé était bien la mienne et j’avais autorisé son utilisation. Ils ont suggéré exactement l’architecture que j’avais déjà livrée.
Le point essentiel, écrit assez gros pour que je puisse le lire depuis l’autre bout de la pièce :
Les clés API IA sont des identifiants porteurs. Elles vont sur le serveur. Elles ne vont nulle part ailleurs.
Si vous ne retenez qu’une chose de ce billet, retenez celle-ci.
À lire ensuite
- L’architecture complète du proxy côté serveur de GeminiOmni
- Pourquoi Veo 3.1 Fast bat Sora pour le travail vidéo indépendant
- Tarifs — comment j’ai choisi 19 $ / 79 $
— Lena
