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.


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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #83

Related Articles

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How Hardcoded Secrets Compromise Authentication in JavaScript and How to Fix It

A critical vulnerability in `Tool/QuantumultX/Rewrite/RRSP.js` exposed hardcoded API authentication credentials—a TOKEN and UMID device identifier—directly in source code. Anyone with repository access could extract these credentials to impersonate the legitimate user and gain full account access to the RRTV API service. The fix replaced hardcoded secrets with empty placeholders, forcing users to manually configure credentials through secure channels.

critical

How API Key Exposure in Request Bodies happens in React and how to fix it

The Chatbot component in gitforme was transmitting Azure OpenAI API keys inside JSON request bodies, causing them to be logged by servers, proxies, and middleware. By moving the apiKey from requestBody.apiKey to an Authorization header, credentials are now protected from persistence in generic request logging infrastructure.

critical

How Hardcoded API Keys in WASM Modules Happen in KAP and How to Fix Them

A critical security vulnerability in `wasm/kap/standard-lib/fhelp-impl.kap` exposed hardcoded Gemini API keys directly in source code distributed to end users via WASM modules. The fix replaces the embedded credential with secure environment variable retrieval, preventing credential extraction through browser developer tools or binary inspection.

critical

How Hardcoded API Key Exposure Happens in Node.js and How to Fix It

A critical vulnerability in `archive/open_claude_code/src/api/client.mjs` exposed five different API providers' credentials through direct `process.env` access. The fix introduced a centralized `readApiKey()` function to enforce secure credential retrieval across Anthropic, OpenAI, Google, and other integrations.

high

How Insufficiently Protected Credentials happens in Node.js and how to fix it

A regex-based guard in `skills/xmemo/scripts/xmemo-skill.mjs` was supposed to block sensitive credentials from being passed as CLI flags, but it only matched a handful of keyword patterns—missing `password`, `client-secret`, `access-token`, `xmemo-key`, and more. The fix expands the blocklist regex to cover these additional credential patterns, closing a gap that could let secrets end up in shell history and process listings.