· 6 min read ·

Firebase Browser Keys, Gemini, and the Security Contract That Silently Breaks

Source: hackernews

Firebase’s own documentation is unusually explicit on one point: the apiKey value inside your web app’s firebaseConfig snippet is not a secret. Google says it clearly in its official docs: including this key in version control is fine. The key identifies your Firebase project to Google’s backend, but access to data is controlled by Firebase Security Rules, which run server-side regardless of whether someone knows your key. For Authentication, Firestore, Realtime Database, and Cloud Storage, this framing is accurate.

One developer found where that guarantee stops holding. Their post on Google’s AI developer forum describes a €54,000 billing spike that materialized in 13 hours. An attacker harvested their Firebase browser key, confirmed it had no API restrictions configured, and used it to call the Gemini API at volume. The thread reached Hacker News and accumulated over 270 comments. The familiar argument followed: half the discussion attributed blame to Google’s design defaults, the other half to developer carelessness about key configuration. Both perspectives are supported by the facts.

The Security Model Firebase Keys Were Built For

The apiKey field in a firebaseConfig block serves a specific purpose: it tells Firebase’s SDK which project to route requests to. It is not an authentication credential in the conventional sense. When a user authenticates through Firebase Auth, they receive a Firebase ID token that is validated by Firebase Security Rules on every subsequent database or storage operation. The API key itself cannot bypass those rules.

This model works well for Firebase’s native services, and it is why Google’s documentation can say the key is safe to expose. The security perimeter runs around the data, not the key. Firebase Security Rules are your access control layer, not the key.

The problem is that this model is specific to Firebase services. The same API key can also authorize calls to other Google Cloud APIs, and those APIs have no equivalent of Firebase Security Rules. They use the key itself as the authorization credential.

What Changes When You Add Gemini

When you enable the Gemini API on a Firebase project in Google Cloud Console, nothing visually changes in your Firebase setup. The firebaseConfig object looks identical. The SDK initialization code is the same. But the same key that was safe to expose can now call a paid, per-token API that bills you directly. The key’s exposure model has not changed; the key’s authorization scope has silently expanded.

This is a variant of what security researchers call the confused deputy problem. The key was issued as a deputy for Firebase services under a model where the deputy’s power is constrained by Security Rules. Enabling Gemini on the same project grants that deputy new authority under a completely different security model, one where possession of the key is sufficient authorization. The tooling does not warn you. The documentation does not prominently flag this transition.

Google Cloud Console’s APIs & Services → Credentials section does allow restricting a key along two axes: application restrictions (which limit which domains, IP ranges, or apps can use the key) and API restrictions (which limit which services the key can call). A correctly configured Firebase browser key would restrict API access to only Firebase-specific APIs: the Identity Toolkit, Firestore API, Cloud Storage for Firebase. It would exclude the Generative Language API entirely.

The defaults, however, leave both restrictions set to “None.” A developer who follows Google’s Firebase quickstart, adds Gemini to their project, and never opens the Credentials panel lands in exactly the configuration that produced this incident.

The Billing Cap Problem

Google Cloud does not offer hard billing caps. Budget alerts can be configured at any threshold, but they send email notifications; they do not pause your account or reject API calls. AWS has the same architecture. The difference is that awareness of this gap is more established in the AWS community because the horror stories have been circulating longer.

Google does provide a mechanism to automatically respond to budget alerts: Cloud Billing budget notifications can publish to a Pub/Sub topic, which can trigger a Cloud Function that programmatically disables the billing account or disables specific APIs. This is not a built-in feature with a checkbox. It requires setting up the Pub/Sub trigger, writing and deploying a Cloud Function, and granting the function the necessary IAM permissions. For a solo developer or small team who has not previously encountered a billing incident, it is not a tool that comes to mind at project setup time.

In this incident, 13 hours passed before the developer noticed. At the token rates for capable Gemini models, Gemini 1.5 Pro charges roughly $3.50 per million input tokens and $10.50 per million output tokens at standard tiers, meaning sustained high-volume generation can compound quickly into five-figure bills. Whether Google will credit the bill is handled case-by-case through billing support. The community experience is that Google sometimes issues credits for first incidents involving key abuse, but there is no written policy guaranteeing this, and the outcome appears to depend on how the support ticket is handled.

The Architecture That Avoids This

The correct setup for a Firebase app that uses Gemini does not route Gemini requests through the browser key. Gemini calls belong on a server, behind an authentication layer that verifies the requesting user’s identity before forwarding the request. Cloud Functions for Firebase is the natural fit: the function receives a Firebase ID token from the client, verifies it, and makes the Gemini API call using a server-side credential that never touches the client.

// Cloud Function: only authenticated users can invoke Gemini
exports.generateText = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError(
      'unauthenticated',
      'Request requires authentication.'
    );
  }
  // Gemini call uses Application Default Credentials from the function environment
  const result = await generativeModel.generateContent(data.prompt);
  return { text: result.response.text() };
});

The Firebase browser key restriction should be configured in Google Cloud Console to allow only Firebase-specific APIs, combined with an HTTP referrer restriction scoped to your production domain. The credential that can call Gemini lives entirely in the server environment, ideally as a service account using Application Default Credentials rather than an API key string, which gives you fine-grained IAM control and avoids having a secret to rotate.

Firebase App Check adds another layer by cryptographically attesting that requests to your Firebase backend originate from your actual app rather than arbitrary clients. It does not help with exposed browser keys calling non-Firebase APIs directly, but it substantially raises the bar for Firebase-specific abuse and is worth enabling regardless.

For teams running multiple projects, keeping Firebase and paid AI APIs in separate Google Cloud projects provides the cleanest separation. The Firebase project’s browser key is physically incapable of calling APIs that are only enabled in the separate AI project. This costs some cross-project setup complexity but eliminates the category of risk entirely.

The Design Gap That Google Owns

The straightforward reading of this incident is that the developer should have configured API restrictions on their key. That is true. The more useful reading is that Google’s tooling created a configuration where the documented behavior, that the browser key is safe to expose, became false in response to a project-level change that the tooling encouraged and did not flag as security-relevant.

Firebase makes it easy to add Gemini to a project. The path to an unsafe configuration requires no unusual steps: create a Firebase project, copy the quickstart config, enable the Gemini API through the console, start building. The path to a safe configuration requires knowing to visit a separate console section, understanding which APIs the browser key should and should not be authorized for, and recognizing that the “safe to expose” documentation carries a conditional that was never stated in the sentence.

This is not the first time this pattern has surfaced. The Google Maps API has a long history of similar billing incidents from exposed browser keys. Developers have experienced the same failure mode with the Firebase Cloud Messaging API, where exposed keys were used to send mass push notifications. The structural cause is the same each time: a key designed for one security model gains authority over a billing surface governed by a completely different model, with no tooling intervention at the transition point.

Google should surface API restriction configuration directly in the Firebase console, with a targeted prompt when a paid Cloud API is added to a project that has a browser key with unrestricted access. A warning at that moment, before the first deployment, would close the gap where most of these incidents originate. The current state requires developer awareness that the existing quickstart flow does not prompt for, at a moment, project setup, when most developers are not thinking about the intersection of key scoping and billing exposure.

Was this interesting?