Firebase’s public key security doctrine has a straightforward premise: the apiKey in your firebaseConfig is not a secret. Google says so explicitly in its documentation. The key identifies your project; Firebase Security Rules control your data. Paste the key on GitHub, ship it in your client bundle, commit it to a public repo. It does not matter.
That model is coherent for Firebase-specific services. It falls apart the moment Gemini enters the picture.
A developer posted to Google’s AI developer forum describing a €54,000 billing spike that accumulated in roughly thirteen hours. The cause was a Firebase browser key with no API restrictions, exposed in client-side code, used by someone outside the developer’s application to call the Gemini API directly. The thread generated significant discussion, with hundreds of comments that circle the same underlying tension: Firebase’s security model is internally consistent but does not generalize to the broader GCP API surface it sits on top of.
The Two-Layer Problem
Firebase browser keys are not Firebase-only constructs. They are Google Cloud API keys, created automatically when you set up a Firebase project, named something like “Browser key (auto created by Firebase).” Like any GCP API key, they can call any Google API that is enabled on the project, unless you explicitly restrict them.
Firebase Security Rules protect Firestore collections, Realtime Database paths, and Cloud Storage buckets. They do not protect the Gemini API. There is no equivalent of a Firestore rule that says “only let authenticated users call models/gemini-pro.” The Gemini API takes a key, validates it, and bills the associated project. That is the entirety of the access control model when you call it with an API key from the browser.
The gap is that enabling the Gemini API on a Firebase project and using the browser key to call it from client-side JavaScript bypasses every security primitive Firebase has built. The key is not a Firebase key at that point; it is a general GCP credential that happens to also identify your Firebase project.
How Key Scanning Works in Practice
Automated tools scan for API keys continuously. Some target GitHub repositories via the search API. Others scan npm packages, JavaScript bundles served from production domains, browser extensions, and mobile app binaries. Tools like truffleHog and gitleaks maintain patterns for hundreds of credential types, including Google API keys.
A Google API key begins with AIzaSy followed by 33 alphanumeric characters. That prefix is well-known and trivially regex-matched. Once found, an automated system can probe the key against a list of enabled APIs to determine what it can call. If Gemini responds with a 200, the key is worth exploiting.
The attack in this incident ran at roughly €4,000 per hour. At that rate, a thirteen-hour window before the developer noticed was long enough to produce a five-figure bill. The math is not unusual for Gemini pricing; running large context windows or high-volume inference against the API without any rate limiting can produce billing rates that would have seemed implausible for most cloud services a few years ago.
The Architecture That Creates This Exposure
Google’s own quickstart documentation for integrating Gemini into web apps has, at various points, shown patterns like this:
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.REACT_APP_GEMINI_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
Where REACT_APP_GEMINI_KEY is an environment variable that gets bundled into the client at build time. In a Create React App or Vite project, any variable prefixed for client access is compiled into the JavaScript bundle. Anyone who opens DevTools and searches the bundle source finds the key in seconds. The process.env syntax provides no protection; it is a build-time substitution, not runtime secret management.
The Firebase-specific version of this mistake is using the apiKey from firebaseConfig directly in Gemini API calls. Firebase’s documentation encourages developers to view this key as safe to expose. The encouragement is accurate for Firebase Auth, Firestore, and Storage, and dangerously misleading when developers extend the same key to Gemini calls.
The Correct Architecture
Any API with per-call billing costs and no application-layer access control should not be called directly from browser code. The correct pattern interposes a backend:
Browser → Firebase Cloud Function (or Cloud Run) → Gemini API
The Cloud Function handles authentication, enforces rate limits per user, logs usage, and holds the Gemini API key as a secret. The client never sees the key. Firebase Cloud Functions integrate with Firebase Auth, so you can require a valid Firebase ID token before proxying any Gemini request.
A minimal Cloud Function for this purpose looks something like:
const { onCall } = require("firebase-functions/v2/https");
const { GoogleGenerativeAI } = require("@google/generative-ai");
const { defineSecret } = require("firebase-functions/params");
const geminiKey = defineSecret("GEMINI_API_KEY");
exports.generateContent = onCall(
{ secrets: [geminiKey] },
async (request) => {
if (!request.auth) {
throw new Error("Unauthenticated");
}
const genAI = new GoogleGenerativeAI(geminiKey.value());
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
const result = await model.generateContent(request.data.prompt);
return { text: result.response.text() };
}
);
The key is stored in Secret Manager, injected at runtime, and never appears in any client-side bundle. The request.auth check ensures only authenticated Firebase users can trigger Gemini calls. Per-user rate limiting can be layered on top using Firestore or a token bucket stored in Redis.
API Key Restrictions Are Not Optional
If you already have an exposed browser key, the Google Cloud Console credentials page is where to start. Under APIs & Services > Credentials, editing the browser key reveals two sections that most Firebase tutorials skip entirely.
Application restrictions limit which origins can use the key. Setting HTTP referrer restrictions to your production domain (https://yourapp.com/*) means requests from other origins return a 403. This is not foolproof; the Referer header can be spoofed in some contexts, particularly from server-side code. It is effective against browser-based abuse and most automated scanning.
API restrictions are more robust. Selecting “Restrict key” and listing only Firebase Auth, Firestore, and the other client-side Firebase APIs your app uses means the key literally cannot call the Gemini API, regardless of origin. If you do not need Gemini on the browser key, the restriction removes the attack surface entirely. Google’s API key best practices documentation covers this, but the guidance appears after most developers have already shipped.
These two restrictions together mean an exposed browser key can only do what it was designed to do. Setting them should be a required step in any Firebase project checklist, not an optional hardening measure.
The Billing Cap Problem Has No Clean Answer
The part of this incident that generates the most frustration is that Google Cloud does not offer a hard spending cap. You can set budget alerts under Billing > Budgets & alerts and configure them to notify you via email or Pub/Sub when you hit a threshold. What you cannot do is tell Google to stop charging your account when you hit €100.
Google’s rationale is that a hard cap could cause outages for production applications. The implicit assumption is that you always want availability over cost control. For a production service with real users, this is arguably defensible. For a developer’s side project or a startup’s early-stage app, it is the wrong default.
AWS and Azure have largely similar policies for their core services. The difference is that Gemini’s pricing for token-heavy workloads can produce extremely high rates in short windows, while the billing alert delay (email delivery time, human response time, time to diagnose and revoke the key) can stretch to many hours. The combination is uniquely dangerous.
The programmatic workaround is to connect a budget alert to a Pub/Sub topic and deploy a Cloud Function that disables billing on the project when the threshold is hit. Google documents this pattern. It requires knowing the pattern exists, implementing it correctly, and accepting that disabling billing will also take down your legitimate services. For most developers, this is not a realistic first line of defense.
The more practical mitigation is per-API quota limits. Under APIs & Services > Quotas, you can set a daily request limit for the Gemini API. A daily cap of 1,000 requests limits your maximum exposure to the cost of 1,000 Gemini calls, regardless of how many an attacker attempts. This requires navigating GCP’s quota interface, which is not intuitive, but it is the closest thing to a spending cap that actually stops charges rather than notifying you about them.
The Pattern Is Not New
This class of incident predates Gemini by a decade. Google Maps launched pay-per-use pricing and developers immediately started exposing Maps JavaScript API keys without referrer restrictions; automated scanners found them; the keys got used for geocoding and routing at scale. Google added warnings in the Cloud Console when keys have no restrictions, but the warnings appear after key creation and are easy to dismiss.
Gemini changes the economics. Maps API abuse typically accumulates over days or weeks. Gemini’s token-based pricing, combined with the ability to send large context windows in each request, allows attackers to generate significant charges in hours. The €54,000 figure represents a rate that would have been difficult to reach with most previous Google API abuse vectors.
Firebase’s integration with Gemini accelerates the risk because it gives developers a natural reason to put a Gemini-capable key in their client-side code, in an ecosystem where the prevailing guidance is that browser keys are safe to expose. The guidance is not wrong for the services it was written for. It was written before Firebase became the entry point to Gemini for millions of developers.
What the Incident Demonstrates
The developer in this case made a mistake with a specific technical form: no API restrictions on the browser key, and a paid API being called through that key from the client. But the conditions that made the mistake plausible were built into the documentation, the quickstart patterns, and a billing system that alerts rather than halts.
Resolving this cleanly requires either restricting what Firebase browser keys can access by default (a breaking change for anyone calling non-Firebase GCP APIs from the client intentionally), or treating Gemini as a fundamentally different class of API that requires explicit backend mediation. The second option is the right architectural answer regardless of key restrictions, because it gives you a place to enforce authentication, rate limiting, and cost controls that Firebase Security Rules cannot provide for inference workloads.
Until Google makes one of those changes, the practical steps are to restrict browser keys to the APIs they actually need, set daily quotas on any API with significant per-call costs, and route Gemini through a backend that holds credentials in Secret Manager. Firebase’s “public keys are safe” doctrine applies to the services that built it. Gemini was built somewhere else, and it behaves accordingly.