Firebase's 'Safe to Expose' API Key Has a Blind Spot, and Gemini Lives in It
Source: hackernews
A developer posted to the Google AI Developer Forum earlier this month describing a billing nightmare: €54,000 in Gemini API charges accrued in 13 hours, traced back to a Firebase browser key with no API restrictions. The key had been exposed in client-side JavaScript, as Firebase keys routinely are, and something had found it and started hammering the Gemini API.
This is not a story about a careless developer leaving a secret somewhere public. The Firebase browser key is supposed to be in client-side code. That is the design. The problem is a gap between what Firebase promises about that design and what actually happens when you enable Gemini on the same Google Cloud project.
What the Firebase API Key Actually Is
When you initialize the Firebase SDK in a web app, you pass a config object that looks something like this:
const firebaseConfig = {
apiKey: "AIzaSyD...",
authDomain: "my-app.firebaseapp.com",
projectId: "my-app",
storageBucket: "my-app.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abc123"
};
The apiKey field here is not a secret. Firebase’s official documentation is explicit on this point: the key is a project identifier, not an authentication credential. Access to Firebase services (Firestore, Realtime Database, Cloud Storage) is controlled by Firebase Security Rules, which evaluate authentication state and data conditions on every request. A publicly visible key combined with properly configured Security Rules is the intended and safe model for Firebase client applications.
This is a sound design for the services Firebase was originally built around. The key tells Google’s backend which project you belong to; the rules determine whether you can actually do anything with that project’s data. Thousands of production Firebase apps operate exactly this way.
Where the story changes is at the boundary between Firebase and Google Cloud Platform.
Firebase Projects Are GCP Projects
Firebase is not a separate infrastructure platform. Every Firebase project is, at the GCP level, a standard Google Cloud project. When Firebase creates your project, GCP automatically provisions several API keys: a browser key, an Android key, an iOS key, and a server key. These are all standard GCP API keys, and by default, none of them have API restrictions applied. That means each key is valid for any API enabled on the project.
For a Firebase-only project, this does not matter much. The APIs enabled are Firebase-specific: Cloud Firestore API, Firebase Authentication, Cloud Storage for Firebase, and so on. The browser key floating around in client JavaScript can technically authenticate against all of them, but Security Rules make exploitation meaningless for a well-configured project.
The situation changes when you enable the Gemini API (via generativelanguage.googleapis.com) or Vertex AI on that same project. GCP API keys are project-scoped. No API restrictions on the browser key means it is now a valid credential for Gemini. A caller who extracts the key from your client bundle can make requests like:
curl -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent?key=AIzaSyD..." \
-H "Content-Type: application/json" \
-d '{"contents": [{"parts": [{"text": "Generate a very long essay about..."}]}]}'
And those requests will succeed, bill the project, and nothing in Firebase Security Rules will intervene, because Security Rules do not apply to GCP APIs. There is no Firestore-equivalent rules layer in front of Gemini. The two systems have no shared access control.
This is the architectural mismatch. Firebase teaches developers that its API key is safe in public code. That teaching is correct for Firebase services. But the key is also a GCP credential, and its safety in that context depends entirely on API restrictions that Firebase never configures by default and, until recently, barely mentioned in the context of AI APIs.
How Quickly €54k Happens
At Gemini 1.5 Pro pricing of roughly $3.50 per million input tokens, reaching €54k in 13 hours requires sustained high-volume automated calls, likely using large context windows to maximize token consumption per request. An attacker has no reason to be conservative. The key costs them nothing; the bill goes to someone else. Automated scanners that crawl GitHub, npm packages, and bundled JavaScript specifically looking for Google API keys are well-documented. Once a key is found and verified to be unrestricted and connected to a billing account, the economics of abuse are straightforward.
Google’s billing system compounds the problem. GCP budget alerts can notify you when spending crosses a threshold, but by default they do not stop anything. An alert fires, an email arrives, and billing continues. Programmatic responses via Pub/Sub and Cloud Functions can disable billing automatically, but that setup requires deliberate work that most developers, especially those spinning up a Firebase project to experiment with Gemini, have not done. The gap between alert and action is where the damage accumulates.
The Fix Is Straightforward, But the Default Is Wrong
The correct mitigation is to add API restrictions to the Firebase browser key via the Google Cloud Console credentials page. Editing the auto-created browser key and restricting it to only Firebase-necessary APIs (Firebase Installations API, Identity Toolkit API, Cloud Firestore API, Firebase Cloud Messaging API, and similar) removes Gemini from its scope entirely, even if Gemini is enabled on the project.
Beyond key restrictions, the right architectural pattern for client applications that use Gemini is to never call the Gemini API from the client at all. A backend service (Cloud Functions, Cloud Run, or your own server) should intermediate all AI calls. The backend holds server-side credentials, controls rate limiting, and can enforce per-user quotas. The client calls your backend; your backend calls Gemini. This is how production AI applications are structured regardless of Firebase.
// This pattern is risky in client-side code:
const genAI = new GoogleGenerativeAI(firebaseConfig.apiKey);
// This is what should happen instead:
// Client calls your backend:
const response = await fetch('/api/generate', {
method: 'POST',
body: JSON.stringify({ prompt: userInput })
});
// Backend (e.g. Cloud Function) uses a server-side key or service account
Additionally, adding application restrictions to the browser key (limiting it to specific HTTP referrer domains) reduces the usable scope even further, since a key restricted to my-app.firebaseapp.com cannot be used by an attacker’s script running elsewhere.
A Documentation Gap That Google Owns
Firebase’s documentation has long carried the message that the client-side API key is not sensitive. The Firebase docs on API keys state this explicitly. What the documentation has not done well is explain that this safety guarantee is scoped narrowly to Firebase-native services, and that the moment you enable a paid GCP API on the same project, the key’s risk profile changes.
As Firebase increasingly markets itself as a platform for building AI-powered applications, with dedicated documentation for Gemini in Firebase apps and SDK integrations, the number of developers who will hit this exact pattern is going to grow. Quickstart tutorials that show initializing the Gemini client with the Firebase API key, even in ostensibly educational contexts, normalize something that should not be done in production.
The issue is not that Firebase’s security model is broken. It is that Firebase and GCP share infrastructure in ways that are not obvious from the Firebase-facing documentation, and the assumptions embedded in one part of that documentation do not transfer cleanly to the other part. The €54k incident, and the Hacker News discussion it generated, are the predictable result of that gap.
Google could meaningfully reduce this class of incident by doing a few things: generating Gemini-specific API keys that are separate from the Firebase browser key by default, adding a warning in the Cloud Console when a project’s browser key has no API restrictions and Gemini is enabled, and updating Firebase’s AI integration documentation to make the key restriction step non-optional. None of those changes are technically difficult. They are defaults and documentation, not architecture.
Until then, the practical advice for anyone building a Firebase project that touches any paid GCP API is to treat the auto-created browser key as a liability, not a convenience. Restrict it to the Firebase APIs you actually use, create a separate key or service account for anything that has a per-token billing model, and configure a budget with programmatic remediation rather than just email alerts. These are not complex steps, but they are invisible to a developer who learned that Firebase API keys are safe to expose and assumed that lesson applied everywhere.