· 6 min read ·

The €54k Firebase Key Mistake That Was Always Waiting to Happen

Source: hackernews

When someone posted to the Google AI Developer Forum about a €54,000 billing spike that hit in 13 hours, the response on Hacker News was the usual mix of sympathy and exasperation. The sympathy is warranted. The exasperation, while understandable, misses the more interesting question: how does Google’s own tooling make this failure mode so easy to stumble into?

The short version of what happened: a Firebase browser API key with no API restrictions was used to call the Gemini API at scale, presumably by someone who scraped it from the client-side JavaScript. No alerts fired in time. No hard cap stopped it. Thirteen hours later, five digits of euros were gone.

This is not a story about a careless developer. It is a story about two design philosophies that were never meant to meet, and what happens when they do.

Firebase Keys Were Built to Be Public

Firebase has a security model that is genuinely unusual. When you initialize a Firebase app in the browser, you embed a configuration object that includes an API key:

const firebaseConfig = {
  apiKey: "AIzaSy...",
  authDomain: "your-app.firebaseapp.com",
  projectId: "your-app",
  storageBucket: "your-app.appspot.com",
  messagingSenderId: "...",
  appId: "..."
};

This is not a mistake. This is the intended usage. The Firebase documentation explicitly states that unlike typical API keys, Firebase API keys are not used to authorize access to backend resources. Instead, Firebase relies on Firebase Security Rules to control read/write access to Firestore and Realtime Database. The browser key is a project identifier, not a credential.

This made Firebase revolutionary when it launched. Direct client-to-database connections with declarative security rules, no backend required. The philosophy worked because the rules engine sat between the key and the data.

The problem is that this philosophy only holds for Firebase-native services. A Firestore read gate-kept by Security Rules is safe with a public key. A Gemini API call has no Security Rules. It has billing.

The Gap Between Firebase Rules and API Key Restrictions

When you create a Firebase project, Google Cloud automatically creates an unrestricted API key. “Unrestricted” here means two things simultaneously. First, no HTTP referrer restrictions, so the key works from any domain. Second, no API restrictions, so it can authenticate requests to any Google Cloud API that accepts API keys.

Firebase Security Rules protect your Firestore data. They say nothing about what APIs a holder of your browser key can call.

Gemini, like Maps, Places, and a dozen other Google Cloud APIs, accepts API key authentication. When Gemini was introduced and Firebase projects started using the Vertex AI SDK or the Gemini API directly from the client, the unrestricted browser key suddenly became a credential for a billing-critical API with no middleware in between.

The attack pattern is mechanical: scan GitHub and public web for AIzaSy prefixed strings (Firebase API keys all start with this), test which ones are unrestricted, hammer the most expensive endpoint you can find. The entire pipeline can be automated. The economics favor the attacker because the cost of running the scan is near zero; the cost of a successful hit falls on someone else.

Why Gemini Makes This Worse Than the Maps API Era

Firebase key abuse is not new. Developers have been getting surprise bills from unrestricted keys being used to hammer the Maps API, the Translation API, and the Places API for years. But Gemini shifts the economics significantly.

Gemini 1.5 Pro prices output at $10.50 per million tokens for long context requests. A well-designed abuse script does not need to be subtle. It can request maximum context, generate maximum output, and run in parallel across as many threads as rate limits allow. With a 2 million token context window and outputs to match, each request can consume tens of dollars. Scale that across hundreds of concurrent requests over 13 hours and you arrive at five-figure bills without any sophisticated technique.

The Maps API had a natural ceiling: most abusive calls were geocoding or directions requests, which are cheap individually. Gemini has no such ceiling. Long context multimodal requests are priced accordingly, and an attacker can always request more tokens.

What Billing Protection Actually Looks Like

Google Cloud has billing budgets. You can create a budget, set a threshold, and receive an email when you cross it. What you cannot do is set a hard cap that actually stops charges. The documentation is clear about this: budget alerts notify you; they do not stop usage.

This is a deliberate product decision, not an oversight. Cloud providers argue that hard caps would cause service disruptions, and for production workloads serving real users, that argument has merit. But for developers in the early stages of building with AI APIs, a hard cap option would be valuable. AWS offers spending limits for certain services and Quota Management that can throttle rather than bill infinitely. Stripe has test-mode spending limits. The pattern exists; Google just has not applied it here.

Azure OpenAI Service allows you to set token-per-minute limits per deployment. If you cap a deployment at 100K tokens per minute and someone abuses your key, they hit the rate limit, not your credit card.

The Mitigation Checklist You Should Run Right Now

If you have a Firebase project, open the Google Cloud Console API Keys page and look at every key. Any key labeled “Browser key (auto created by Firebase)” deserves attention.

For each such key:

Apply API restrictions. Click the key, go to API restrictions, and select “Restrict key.” Add only the specific APIs your application uses. If your app uses Firebase Authentication and Firestore but not Gemini, the key should not be able to call Gemini.

Apply HTTP referrer restrictions. Under “Application restrictions,” set “HTTP referrers” and add your actual domains. Pattern matching like https://your-app.com/* is sufficient. This will not stop a determined attacker who can spoof referrer headers, but it eliminates drive-by scripts.

Set a billing budget with a low threshold. Even if it cannot stop charges, an alert at $50 gives you time to react. Set one at $10, one at $100, and one at 90% of your expected monthly spend.

Move AI API calls to your backend. This is the actual fix. Client-side calls to generative AI APIs are architecturally awkward regardless of security concerns: you lose control over rate limiting, input validation, prompt injection defense, and cost attribution. A thin backend proxy that accepts user requests and makes the Gemini call server-side, authenticated with a service account, removes the attack surface entirely.

The backend proxy approach also lets you implement per-user rate limiting, log requests for debugging, and cache responses where appropriate. These are things you will want anyway.

// Instead of this from the browser:
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); // key exposed

// Call your own backend:
const response = await fetch('/api/generate', {
  method: 'POST',
  body: JSON.stringify({ prompt: userInput }),
  headers: { 'Content-Type': 'application/json' }
});

The backend holds the service account credentials. The client never sees an API key.

The Design Decision Google Needs to Revisit

The deeper issue is that Google created an on-ramp for AI development that routes through Firebase, and Firebase’s key model was not designed with billing-critical AI APIs in mind.

The Firebase Genkit project and the Vertex AI in Firebase SDK are both positioned as easy ways to add AI to Firebase apps. The documentation and quickstarts walk you through using these SDKs from the client. The security implications of doing so with an unrestricted Firebase browser key are mentioned, but not prominently. A developer who follows the happy path can ship a live app with an exploitable configuration before they ever read the security section.

What Google could do: make unrestricted keys impossible for Firebase projects by default. When a new Firebase project is created, the auto-generated browser key should automatically restrict to Firebase-native APIs only. If a developer wants to call Gemini from the client, they should have to opt into that restriction explicitly, and the console should warn them about the billing exposure when they do.

Defaulting to secure is harder to get right and occasionally inconvenient for developers. But the alternative is a steady stream of forum posts from people who lost thousands of euros before breakfast.

The person who posted that thread probably will not get a refund. Google’s standard position on billing from compromised credentials is that the account holder is responsible. Some users have reported one-time courtesy credits for incidents like this, but there is no policy guaranteeing it. Five figures in unlucky charges and a support ticket is a brutal introduction to cloud billing.

The tooling could prevent most of this. Right now, it does not.

Was this interesting?