The Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Hardcoded API Credentials in Client-Side JavaScript |
| CWE | CWE-798 – Use of Hard-coded Credentials |
| Language | JavaScript (Node.js) |
| Risk | Unauthorized access to Firebase Realtime Database and Yandex Translation services |
| Root Cause | Firebase config object in common.js contained literal API key and sender ID strings committed to source control |
| Fix | Replace all hardcoded string values with process.env.* references and add .env to .gitignore |
Frequently Asked Questions
What is a hardcoded API key vulnerability?
A hardcoded API key vulnerability occurs when secret credentials are embedded as string literals in source code instead of being loaded from secure environment variables or secret managers, making them visible to anyone who can read the code or inspect network traffic.
How do you prevent hardcoded secrets in JavaScript?
Use environment variables (process.env.KEY_NAME) loaded at runtime, add .env files to .gitignore, use secret scanning tools in CI/CD, and never commit credentials to version control.
Is restricting repository access enough?
No. Client-side JavaScript is delivered to every browser, so even a private repository does not protect secrets once the app is deployed. The credentials are visible in DevTools regardless of repository visibility.
Can static analysis detect hardcoded API keys?
Yes. Tools like Semgrep, GitLeaks, TruffleHog, and GitHub's secret scanning can identify hardcoded credential patterns in source code and flag them before they reach production.
Introduction
The javascripts/common.js file is the shared configuration entry point for the live-transcript application — the first JavaScript loaded, the one that initializes Firebase, and therefore the one that every authenticated session depends on. It is also, as of this fix, the file that was broadcasting its own Firebase API key to every visitor who knew where to look.
The vulnerable config object at line 1 of common.js read like a credentials cheat sheet:
var config = {
apiKey: "AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ",
authDomain: "live-transcript.firebaseapp.com",
databaseURL: "https://live-transcript.firebaseio.com",
projectId: "live-transcript",
storageBucket: "",
messagingSenderId: "1024703297861"
};
Six fields. Every one of them hardcoded. The API key, the Realtime Database URL, the messaging sender ID — all committed to source control and shipped verbatim to every browser that loaded the page.
The Vulnerability Explained
What Made This Code Dangerous
Firebase's client SDK is designed to be initialized with a config object. The pattern itself is not wrong — what is wrong is treating the values inside that object as constants safe to embed in source code.
The specific field that carries the most risk is apiKey: "AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ". This key authenticates requests to the Firebase project live-transcript. Combined with the databaseURL: "https://live-transcript.firebaseio.com", an attacker has everything needed to interact with the Realtime Database directly — no need to ever visit the application UI.
The messagingSenderId: "1024703297861" adds another surface: an attacker can use it to send unauthorized push notifications through Firebase Cloud Messaging to any device subscribed to this project.
How the Attack Works — Step by Step
This is a two-step exploit that requires zero special tooling:
Step 1 — Key extraction. An attacker opens the application in any browser, presses F12 to open DevTools, navigates to the Sources or Network tab, and finds common.js. The apiKey string AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ is sitting there in plaintext. This takes under 30 seconds.
Step 2 — Unauthorized API calls. The attacker copies the key and databaseURL, then writes a few lines of JavaScript in their own environment:
// Attacker's script — no access to the real application required
const firebaseConfig = {
apiKey: "AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ",
databaseURL: "https://live-transcript.firebaseio.com",
projectId: "live-transcript"
};
firebase.initializeApp(firebaseConfig);
const db = firebase.database();
// Read all data if rules allow unauthenticated reads
db.ref('/').once('value').then(snapshot => console.log(snapshot.val()));
If Firebase Security Rules are not perfectly configured (a common oversight), this script can read or write the entire database. Even if rules are tight, the attacker can exhaust Firebase's free-tier quota, causing denial of service for legitimate users and unexpected billing charges.
Why Client-Side JavaScript Is Especially Risky
Unlike server-side code, JavaScript delivered to the browser is intentionally public. Minification and bundling do not protect secrets — automated tools can extract string literals from minified bundles in seconds. There is no such thing as a "hidden" value in a JavaScript file that runs in a browser.
This is why the OWASP Top 10 (A02:2021 – Cryptographic Failures) specifically calls out the storage of secrets in locations accessible to unauthorized parties, and why CWE-798 (Use of Hard-coded Credentials) exists as a distinct weakness category.
The Fix
What Changed and Why
The fix touched two files: javascripts/common.js and .gitignore. Both changes are necessary — one removes the existing exposure, the other prevents it from recurring.
javascripts/common.js — Replace Literals with Environment Variables
Before:
var config = {
apiKey: "AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ",
authDomain: "live-transcript.firebaseapp.com",
databaseURL: "https://live-transcript.firebaseio.com",
projectId: "live-transcript",
storageBucket: "",
messagingSenderId: "1024703297861"
};
After:
var config = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID
};
Every hardcoded string is replaced with a process.env.* reference. At build time, a bundler like Webpack or Parcel (used in this project, given the dist/ and .cache/ entries in .gitignore) will substitute these references with values from the environment — values that exist only on the build server, never in the repository.
The actual credential string AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ no longer appears anywhere in the codebase. An attacker reading the source sees only process.env.FIREBASE_API_KEY — a reference to a variable, not the variable's value.
.gitignore — Prevent Future Credential Commits
Before:
node_modules
npm-debug.log
Archive.zip
dist/
.cache/
After:
node_modules
npm-debug.log
Archive.zip
dist/
.cache/
.env
Adding .env to .gitignore ensures that when developers create a local .env file to hold the actual credentials during development, Git will never stage or commit that file. This is the second line of defense: even if a developer accidentally runs git add ., the .env file containing the real keys will be excluded.
Prevention & Best Practices
1. Establish a Secrets Hygiene Baseline
- Never use string literals for credentials in any file that is committed to version control. If you find yourself typing
apiKey: "AIza...", stop and useprocess.env.VARIABLE_NAMEinstead. - Create a
.env.examplefile with placeholder values (e.g.,FIREBASE_API_KEY=your_key_here) so developers know what variables are needed without exposing real values. - Add
.envto.gitignorebefore your first commit, not after you realize you need it.
2. Rotate Compromised Keys Immediately
If a key has been committed to a public or shared repository, assume it is compromised regardless of how quickly you remove it. Git history preserves deleted commits. Rotate the key in the Firebase Console immediately, then apply the environment variable fix.
3. Use Secret Scanning in CI/CD
Add automated secret detection to your pipeline:
# Using GitLeaks in a pre-commit hook or CI step
gitleaks detect --source . --verbose
# Using TruffleHog
trufflehog git file://. --only-verified
GitHub's built-in secret scanning will alert you if a known credential pattern (including Firebase API keys) is pushed to a repository.
4. Apply Least-Privilege Firebase Security Rules
Even with the key rotated and secrets moved to environment variables, ensure your Firebase Security Rules restrict access appropriately. A Firebase API key that reaches the client (which it must, for the SDK to work) should be paired with rules that enforce authentication:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
This means a leaked key alone is not sufficient for an attacker to read or write data — they would also need valid user credentials.
5. Reference Security Standards
- OWASP A02:2021 – Cryptographic Failures: covers secrets stored in accessible locations
- CWE-798 – Use of Hard-coded Credentials
- CWE-312 – Cleartext Storage of Sensitive Information
- OWASP Secrets Management Cheat Sheet: comprehensive guidance on handling secrets across environments
Key Takeaways
- The specific key
AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQwas visible to every visitor — hardcoded at line 1 ofcommon.js, the first file loaded by the application. - Client-side JavaScript has no secrets — minification and bundling do not hide string literals from anyone who opens DevTools.
- Both
common.jsand.gitignoreneeded to change — removing the existing exposure without adding.gitignoreprotection would leave the door open for the same mistake to recur. - Firebase's
messagingSenderIdis also sensitive — it enables unauthorized push notification campaigns, not just database access. process.env.*references in a bundled app are substituted at build time, meaning the final bundle contains the resolved value — ensure your build environment itself is secured and that build artifacts are not publicly accessible.
How Orbis AppSec Detected This
- Source: Hardcoded string literal
"AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ"assigned directly to theapiKeyproperty in theconfigobject atjavascripts/common.js:2 - Sink: The
configobject is passed tofirebase.initializeApp(config), making the credential an active authentication token for all Firebase SDK calls - Missing control: No environment variable indirection; no
.envexclusion in.gitignore; credential value committed to version control in plaintext - CWE: CWE-798 – Use of Hard-coded Credentials
- Fix: All six hardcoded config values in
common.jswere replaced withprocess.env.*references, and.envwas added to.gitignoreto prevent future credential commits
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
Hardcoded credentials in client-side JavaScript are one of the highest-signal, lowest-effort vulnerabilities an attacker can exploit. The live-transcript application had a Firebase API key sitting at line 2 of its most-loaded JavaScript file — a credential that could be extracted in under a minute by anyone with a browser.
The fix is elegant in its simplicity: six string literals replaced with six process.env.* references, and one line added to .gitignore. No architectural changes, no new dependencies, no behavior differences for legitimate users. But the security improvement is substantial — the credential no longer exists anywhere in the repository or the shipped bundle.
The broader lesson is about defaults. When you initialize a Firebase project, the Console hands you a config object pre-filled with your credentials and invites you to paste it into your code. That workflow is convenient, but it normalizes a dangerous pattern. Treat every value in that config object as a secret from day one, and your .env file as the only place those secrets should ever live.
References
- CWE-798: Use of Hard-coded Credentials
- CWE-312: Cleartext Storage of Sensitive Information
- OWASP Secrets Management Cheat Sheet
- OWASP Top 10 A02:2021 – Cryptographic Failures
- Firebase Security Rules Documentation
- Node.js process.env Documentation
- Semgrep rules for hardcoded secrets
- fix: the application exposes hardcoded api keys in c... in common.js