Back to Blog
high SEVERITY9 min read

How Hardcoded API Keys happen in JavaScript and how to fix it

A critical security vulnerability was discovered in `javascripts/common.js` where Firebase API keys, auth domains, and sender IDs were hardcoded directly in client-side JavaScript. Any user who opened browser DevTools or viewed page source could extract these credentials and make unauthorized calls to the Firebase Realtime Database and Yandex Translation services. The fix moves all sensitive configuration values to environment variables, ensuring secrets never reach the client bundle.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

This vulnerability (CWE-798: Use of Hard-coded Credentials) involves Firebase API keys and configuration values embedded as string literals directly in `javascripts/common.js`. Any visitor to the application could extract these credentials from the browser's DevTools Network tab or page source, then use them to make unauthorized Firebase Realtime Database or Yandex Translation API calls. The fix replaces every hardcoded value with `process.env.*` environment variable references and adds `.env` to `.gitignore` so secrets are never committed to version control.

Vulnerability at a Glance

cweCWE-798
fixReplace all hardcoded string values with process.env.* references and add .env to .gitignore
riskUnauthorized access to Firebase Realtime Database and Yandex Translation services
languageJavaScript (Node.js)
root causeFirebase config object in common.js contained literal API key and sender ID strings committed to source control
vulnerabilityHardcoded API Credentials in Client-Side JavaScript

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 use process.env.VARIABLE_NAME instead.
  • Create a .env.example file with placeholder values (e.g., FIREBASE_API_KEY=your_key_here) so developers know what variables are needed without exposing real values.
  • Add .env to .gitignore before 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 AIzaSyD591s8Of9vqduC2ZAakt9mMQRin4wCFSQ was visible to every visitor — hardcoded at line 1 of common.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.js and .gitignore needed to change — removing the existing exposure without adding .gitignore protection would leave the door open for the same mistake to recur.
  • Firebase's messagingSenderId is 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 the apiKey property in the config object at javascripts/common.js:2
  • Sink: The config object is passed to firebase.initializeApp(config), making the credential an active authentication token for all Firebase SDK calls
  • Missing control: No environment variable indirection; no .env exclusion 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.js were replaced with process.env.* references, and .env was added to .gitignore to 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

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.

What CWE is hardcoded API key exposure?

CWE-798 (Use of Hard-coded Credentials) covers secrets baked into source code. CWE-312 (Cleartext Storage of Sensitive Information) may also apply when those secrets are committed to a repository.

Is restricting repository access enough to prevent hardcoded secret exposure?

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.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #83

Related Articles

critical

How Plaintext Secret Storage Happens in Cloudflare Workers (wrangler.toml) and How to Fix It

A critical misconfiguration in `platforms/m365/wrangler.toml` left developers one copy-paste away from committing live API keys directly into git history. The fix adds an explicit warning comment blocking the `[vars]` anti-pattern and adds `.dev.vars` to `.gitignore`, ensuring secrets flow through Cloudflare's encrypted `wrangler secret` mechanism instead of plaintext config. This matters because git history is permanent — a key committed even once can be extracted long after it's "deleted."

critical

How Hardcoded API Keys Happen in JavaScript and How to Fix Them

A critical security vulnerability was discovered in `src/js/init.js` where a Bugsnag API key was hardcoded directly into client-side JavaScript, making it visible to anyone who inspects the page source or JavaScript bundle. The fix replaces the hardcoded string with an environment variable reference (`import.meta.env.VITE_BUGSNAG_API_KEY`), ensuring the key is injected at build time rather than baked into the shipped code. This pattern is one of the most common — and most avoidable — secrets exp

critical

How Plaintext Credential Storage Happens in Node.js Config Files and How to Fix It

A critical vulnerability in `config.js` allowed OAuth tokens and user IDs to silently fall back to empty strings when environment variables were unset, enabling credential bypass and potential hardcoded secret exposure. The fix removes the `|| ""` fallback pattern, ensuring credentials are either properly set or explicitly `undefined`, and updates downstream checks to use truthy evaluation instead of empty-string comparison. This change closes a subtle but dangerous gap that could have allowed A

critical

How Hardcoded API Keys happen in JavaScript plugins and how to fix them

A critical hardcoded API key was discovered in `plugins/ocr.js` at line 21, where the OCR integration used a plaintext fallback credential `'K81241004488957'` whenever the `OCR_API_KEY` environment variable was absent. This exposed a live API key to anyone with repository access, enabling unauthorized use of the OCR service. The fix removes the hardcoded fallback entirely and fails fast with a clear error message when the required environment variable is not configured.

high

How Information Disclosure happens in Go dependency management and how to fix it

CVE-2026-42151 is a high-severity information disclosure vulnerability in the Prometheus monitoring library (github.com/prometheus/prometheus) that exposed Azure OAuth client secrets through the Prometheus configuration API endpoint. Applications depending on versions prior to v0.311.3 were at risk of leaking sensitive Azure credentials to anyone with access to the config API. The fix involves upgrading the dependency in go.mod from v0.310.0 to v0.311.3.

high

How Dependabot Missing Cooldown happens in GitHub Actions and how to fix it

A high-severity misconfiguration in `.github/dependabot.yml` left this Node.js library without a cooldown period on dependency updates, meaning Dependabot could immediately propose upgrades to newly published — potentially malicious or unstable — package versions. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, introducing a mandatory waiting period before any newly released version is surfaced as an update candidate. Because this project