How Hardcoded Firebase API Keys Happen in JavaScript Service Workers and How to Fix Them
The Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Hardcoded Firebase API Keys / Credential Exposure |
| CWE | CWE-312 · CWE-798 |
| Language | JavaScript |
| Risk | Push notification abuse, quota exhaustion, unauthorized Firebase access |
| Root Cause | Live and legacy Firebase config values hardcoded in a publicly served service worker |
| Fix | Removed stale credentials, added security guidance enforcing API restrictions and Security Rules |
Summary
A production Firebase service worker file (static/firebase-messaging-sw.js) contained hardcoded API keys and project configuration directly in publicly accessible JavaScript — including a second, previously commented-out set of credentials from an older project. While Firebase web config values are technically public identifiers by design, pairing them with unrestricted Firebase projects or absent Security Rules turns them into an open door for push notification abuse, quota exhaustion, and unauthorized data access.
Introduction
The static/firebase-messaging-sw.js file is the backbone of push notification delivery in this application. It initializes Firebase, registers a messaging service worker, and handles incoming messages in the browser background. But buried inside the firebaseConfig object at line 6, the file held something it shouldn't: not one, but two sets of Firebase project credentials — one active, one commented out from what appears to be a previous project (asseto-push-notification).
The active configuration looked like this:
const firebaseConfig = {
apiKey: 'AIzaSyBiUQoc2jSnM8Et_908_Jcj75RTz1IGgco',
appId: '1:373301044674:android:e331be3913f69c4518225b',
messagingSenderId: '373301044674',
// ...
};
And the commented-out legacy credentials were sitting right above it:
// apiKey: "AIzaSyBdBIbK2e1JDbZGKKXU6yFwL1ze0jprq6Y",
// authDomain: "asseto-push-notification.firebaseapp.com",
// projectId: "asseto-push-notification",
// storageBucket: "asseto-push-notification.firebasestorage.app",
// messagingSenderId: "600702361358",
// appId: "1:600702361358:web:58cb3c2ee78e9b1280415f",
// measurementId: "G-R0J26HTEVL"
This pattern — live credentials alongside rotated-but-not-deleted legacy credentials — is one of the most common and underappreciated security hygiene failures in frontend development.
The Vulnerability Explained
Why Firebase API Keys Are "Public by Design" (and Why That Doesn't Mean "Safe")
Google's Firebase documentation explicitly states that the apiKey in a web configuration is a public identifier, not a secret. It's used to identify your Firebase project to Google's servers, not to authenticate privileged access. This is fundamentally different from, say, an AWS secret key or a database password.
However, this design contract comes with a critical obligation that is easy to overlook: the API key is only safe if your Firebase project is properly locked down. Specifically:
- Firebase Security Rules must be configured to deny unauthorized reads/writes to Firestore, Realtime Database, and Cloud Storage.
- The API key must be restricted in Google Cloud Console — limited to specific HTTP referrers (your domain) and specific APIs (only Firebase-related ones).
- Firebase App Check should be enabled to ensure only your legitimate app can call Firebase services.
Without these controls, anyone who extracts the apiKey, appId, and messagingSenderId from this publicly served JavaScript file can:
- Send arbitrary push notifications to your users via the Firebase Cloud Messaging (FCM) API using the
messagingSenderId - Exhaust your Firebase quota, causing service disruption or unexpected billing
- Access or modify data in Firestore/Realtime Database if Security Rules are permissive
- Abuse Firebase Authentication to create accounts or enumerate users if auth is misconfigured
The Commented-Out Credentials Are Still a Problem
The legacy asseto-push-notification project credentials weren't just a cosmetic issue. Comments in JavaScript are not stripped before the file is served to clients. Every browser that downloads firebase-messaging-sw.js receives the full text, including:
// apiKey: "AIzaSyBdBIbK2e1JDbZGKKXU6yFwL1ze0jprq6Y",
// authDomain: "asseto-push-notification.firebaseapp.com",
These values are:
- Indexed by search engines if the file is publicly crawlable
- Captured in version control history and potentially exposed in public repositories
- Visible to anyone who opens DevTools in a browser
If the asseto-push-notification project still exists and hasn't had its Security Rules hardened or its API key restricted, those commented-out credentials represent a live attack surface against a different Firebase project.
Concrete Attack Scenario
An attacker discovers firebase-messaging-sw.js through a routine scan of the application's JavaScript assets. They extract messagingSenderId: '373301044674' and the apiKey. Using the Firebase Admin SDK or a direct HTTP call to the FCM v1 API, they craft a push notification payload and send it to all subscribed devices. With no App Check enforcement and an unrestricted API key, the request succeeds. Users receive fraudulent push notifications appearing to come from the legitimate application — a perfect vector for phishing.
The Fix
The pull request made two targeted changes to static/firebase-messaging-sw.js:
1. Removed the Commented-Out Legacy Credentials
Before:
const firebaseConfig = {
// apiKey: "AIzaSyBdBIbK2e1JDbZGKKXU6yFwL1ze0jprq6Y",
// authDomain: "asseto-push-notification.firebaseapp.com",
// projectId: "asseto-push-notification",
// storageBucket: "asseto-push-notification.firebasestorage.app",
// messagingSenderId: "600702361358",
// appId: "1:600702361358:web:58cb3c2ee78e9b1280415f",
// measurementId: "G-R0J26HTEVL"
apiKey: 'AIzaSyBiUQoc2jSnM8Et_908_Jcj75RTz1IGgco',
appId: '1:373301044674:android:e331be3913f69c4518225b',
messagingSenderId: '373301044674',
After:
// NOTE: Firebase web config values are public identifiers by design (see
// https://firebase.google.com/docs/projects/api-keys). They must never be paired
// with an unrestricted Firebase project - restrict this API key (HTTP referrer /
// API restrictions in Google Cloud Console) and enforce Firebase Security Rules /
// App Check server-side so possession of these values alone grants no access.
const firebaseConfig = {
apiKey: 'AIzaSyBiUQoc2jSnM8Et_908_Jcj75RTz1IGgco',
appId: '1:373301044674:android:e331be3913f69c4518225b',
messagingSenderId: '373301044674',
The seven commented-out lines containing the asseto-push-notification project credentials are completely removed, eliminating the legacy exposure surface.
2. Added Inline Security Documentation
The fix adds a multi-line comment above firebaseConfig that does something important: it encodes the security contract directly in the code. Future developers reading this file now see an explicit reminder that:
- Firebase API keys are public identifiers by design (with a link to the official docs)
- The API key must be restricted in Google Cloud Console
- Firebase Security Rules and App Check must be enforced server-side
- Possession of these values alone should grant no access
This is defensive documentation — it prevents the next developer from assuming "it's fine, these are meant to be public" without understanding the full obligation that comes with that design.
What This Fix Does NOT Do
It's worth being explicit: this fix does not rotate the active apiKey. The AIzaSyBiUQoc2jSnM8Et_908_Jcj75RTz1IGgco value remains in the file. The fix correctly recognizes that rotating a Firebase web API key without simultaneously hardening the project's Security Rules and key restrictions would provide no real security improvement — the new key would be just as exposed.
The real fix is the combination of:
1. Removing legacy credentials ✅ (done in this PR)
2. Adding documentation of the security contract ✅ (done in this PR)
3. Restricting the API key in Google Cloud Console ⚠️ (must be done manually)
4. Enforcing Firebase Security Rules ⚠️ (must be verified/enforced separately)
5. Enabling Firebase App Check ⚠️ (recommended additional step)
Prevention & Best Practices
Restrict Your Firebase API Key Immediately
In Google Cloud Console → APIs & Services → Credentials, find your Firebase API key and apply:
- Application restrictions: HTTP referrers — add only your production domain(s) (e.g.,
https://yourdomain.com/*) - API restrictions: Restrict to only the Firebase APIs your app actually uses (Firebase Installations API, FCM, etc.)
This ensures that even if someone extracts the key from your JavaScript, requests from unauthorized origins will be rejected by Google's infrastructure.
Write Strict Firebase Security Rules
A default Firebase project often ships with open rules:
// DANGEROUS default - never use in production
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true; // ← anyone can read/write everything
}
}
}
Replace these with rules that verify authentication and authorization:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
Enable Firebase App Check
Firebase App Check ensures that only your legitimate app instances can access your Firebase backend. Even if an attacker extracts your config values, App Check attestation (using reCAPTCHA v3 for web) blocks unauthorized clients.
Never Leave Commented-Out Credentials in Source Code
Treat commented-out credentials the same as live credentials. If you're rotating keys or migrating projects:
- Delete the old credentials from the file entirely
- Rotate the old key in the Firebase/Google Cloud Console
- Verify the old key is revoked before merging
Use a pre-commit hook or CI check with a tool like Gitleaks or Trufflehog to catch credential patterns before they reach your repository.
Use Environment Variables for Build-Time Injection
While Firebase web config is designed to be public, a defense-in-depth approach injects these values at build time rather than hardcoding them:
// In your build process (e.g., webpack, Vite)
const firebaseConfig = {
apiKey: process.env.VITE_FIREBASE_API_KEY,
appId: process.env.VITE_FIREBASE_APP_ID,
messagingSenderId: process.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
};
This doesn't prevent the values from appearing in the built output, but it:
- Keeps credentials out of version control
- Makes it easier to use different configs per environment
- Reduces the risk of accidentally committing the wrong project's credentials
OWASP and CWE References
This vulnerability maps to:
- CWE-312: Cleartext Storage of Sensitive Information
- CWE-798: Use of Hard-coded Credentials
- OWASP A02:2021 – Cryptographic Failures (storing/exposing credentials without protection)
- OWASP A05:2021 – Security Misconfiguration (Firebase project without proper restrictions)
Key Takeaways
- Commented-out credentials in
firebase-messaging-sw.jsare still served to every client — JavaScript comments are not stripped before delivery, making them fully visible in DevTools and to web crawlers. - Firebase's "public by design" API key model is only safe when paired with key restrictions and Security Rules — the
apiKeyinfirebaseConfigis not inherently safe; it requires active configuration in Google Cloud Console to be safe. - The
messagingSenderId: '373301044674'value enables FCM abuse — with an unrestricted key and no App Check, this value alone is enough to attempt unauthorized push notification delivery. - Legacy project credentials (
asseto-push-notification) must be rotated, not just commented out — if that Firebase project still exists, those seven commented-out lines represent a live attack surface against a different application. - Inline security documentation prevents regression — the added comment block ensures future developers understand the security contract before modifying the Firebase configuration.
How Orbis AppSec Detected This
- Source: The
firebaseConfigobject instatic/firebase-messaging-sw.jsline 6, where Firebase project credentials are assigned as string literals directly in the JavaScript source - Sink: The publicly served service worker file itself — any client downloading
firebase-messaging-sw.jsreceives the full credential set, including commented-out legacy values - Missing control: No API key restrictions in Google Cloud Console, no documented requirement for Firebase Security Rules enforcement, and no removal of legacy
asseto-push-notificationcredentials that remained in commented form - CWE: CWE-312 (Cleartext Storage of Sensitive Information), CWE-798 (Use of Hard-coded Credentials)
- Fix: Removed the seven commented-out legacy credential lines and added an inline comment block documenting the Firebase public-key security contract and required hardening steps
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
The static/firebase-messaging-sw.js vulnerability is a textbook example of how a "safe by design" feature becomes unsafe through incomplete implementation. Firebase web API keys are public identifiers — but only when the surrounding security controls (key restrictions, Security Rules, App Check) are in place. When they're not, those public identifiers become the keys to your Firebase kingdom.
The fix here is surgical and correct: remove the legacy credentials that had no business being in the file, and add documentation that makes the security contract explicit for every developer who touches this code in the future. But the work doesn't stop at the PR merge — the API key restriction and Security Rules enforcement must happen in the Firebase and Google Cloud consoles to complete the security picture.
If your codebase uses Firebase, take ten minutes today to audit your firebaseConfig usage: Are your API keys restricted? Are your Security Rules tight? Is App Check enabled? The answers to those three questions determine whether your Firebase configuration is a harmless public identifier or an open invitation.
References
- CWE-312: Cleartext Storage of Sensitive Information
- CWE-798: Use of Hard-coded Credentials
- OWASP Secrets Management Cheat Sheet
- Firebase Documentation: API Keys for Firebase
- Firebase Security Rules Reference
- Firebase App Check Documentation
- Google Cloud: Restrict API Keys
- Semgrep rules for hardcoded secrets
- fix: fix security issue in firebase-messaging-sw.js