Back to Blog
critical SEVERITY7 min read

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

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

Answer Summary

This is a hardcoded secret vulnerability (CWE-798) in a JavaScript file (`src/js/init.js`, line 22), where a Bugsnag API key (`c9beb7c090034128a89c8e58f261e972`) was embedded as a plaintext string literal in client-side code. Anyone who views the page source or inspects the JavaScript bundle can extract this key and abuse it. The fix replaces the hardcoded value with `import.meta.env.VITE_BUGSNAG_API_KEY`, a Vite environment variable that is injected at build time from a `.env` file that is never committed to source control.

Vulnerability at a Glance

cweCWE-798 (Use of Hard-coded Credentials)
fixReplaced the hardcoded string with `import.meta.env.VITE_BUGSNAG_API_KEY` so the key is injected at build time from a `.env` file outside source control
riskAny user or attacker can extract the Bugsnag API key from the JavaScript bundle and abuse it for unauthorized error reporting, quota exhaustion, or data poisoning
languageJavaScript (Vite/Node.js)
root causeThe Bugsnag API key was passed as a string literal to `Bugsnag.start()` instead of being read from an environment variable
vulnerabilityHardcoded API Key / Hardcoded Secret

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

The src/js/init.js file is responsible for bootstrapping the application's error monitoring — a critical piece of infrastructure that should be among the most trustworthy parts of the codebase. Yet buried at line 22, a single string literal quietly undermined the security of every deployment:

window.bugsnagClient = Bugsnag.start({
  apiKey: 'c9beb7c090034128a89c8e58f261e972',
  ...
});

This is a textbook hardcoded secret: a real, production API key committed directly to source code. For developers who haven't encountered this class of vulnerability before, it can feel low-stakes — after all, Bugsnag API keys are "meant to be client-facing," right? This post explains why that reasoning is incomplete, how the key was fixed, and what patterns to follow so you never ship credentials in source code again.


The Vulnerability Explained

What Was Actually in the Code

At line 22 of src/js/init.js, the Bugsnag client was initialized with a hardcoded API key:

// VULNERABLE — src/js/init.js:22
window.bugsnagClient = Bugsnag.start({
  apiKey: 'c9beb7c090034128a89c8e58f261e972',
  appVersion: `${defaults.versionString}`,
  releaseStage,
  notifyReleaseStages: ['production'],
});

The string 'c9beb7c090034128a89c8e58f261e972' is a real API key. It is not a placeholder. It is not a development-only key. It is the production credential, sitting in plain sight in a JavaScript file that gets bundled and shipped to every user's browser.

Why "Client-Facing" Doesn't Mean "Safe to Hardcode"

The common rationalization for hardcoding Bugsnag keys goes like this: "Bugsnag API keys are designed to be used in frontend code, so it's fine if users see them." This reasoning has a fatal flaw.

There is a meaningful difference between:
1. A key being readable at runtime in a controlled, deployed environment
2. A key being committed to source code and therefore permanently embedded in git history, forks, CI logs, npm packages, and any mirror of the repository

Once 'c9beb7c090034128a89c8e58f261e972' is in git history, it is essentially permanent. Even if you rotate the key tomorrow, the old value lives in every git clone of the repository forever.

The Attack Surface

An attacker who obtains this key can:

  1. Flood your error dashboard with thousands of fake error reports, drowning out real alerts and causing alert fatigue that masks actual incidents.
  2. Poison your error data by submitting fabricated stack traces, misleading your engineering team about the health of production.
  3. Exhaust your Bugsnag quota, potentially disabling error monitoring entirely — at exactly the moment you need it most during an incident.
  4. Exfiltrate metadata submitted with error reports, depending on what your application attaches (user IDs, session data, environment details).

The exploitation path requires zero sophistication:

1. Visit the deployed web application
2. Open browser DevTools  Sources tab
3. Search for 'apiKey' or 'bugsnag' in the JavaScript bundle
4. Copy the value of apiKey
5. Use Bugsnag's API directly with the extracted key

No reverse engineering. No special tools. Thirty seconds of browser DevTools work.


The Fix

The fix is a single line change in src/js/init.js, but it represents a fundamental shift in how the credential is managed:

- apiKey: 'c9beb7c090034128a89c8e58f261e972',
+ apiKey: import.meta.env.VITE_BUGSNAG_API_KEY,

Before vs. After

Before (vulnerable):

window.bugsnagClient = Bugsnag.start({
  apiKey: 'c9beb7c090034128a89c8e58f261e972',  // hardcoded production key
  appVersion: `${defaults.versionString}`,
  releaseStage,
  notifyReleaseStages: ['production'],
});

After (fixed):

window.bugsnagClient = Bugsnag.start({
  apiKey: import.meta.env.VITE_BUGSNAG_API_KEY,  // injected at build time
  appVersion: `${defaults.versionString}`,
  releaseStage,
  notifyReleaseStages: ['production'],
});

How This Fix Works

import.meta.env.VITE_BUGSNAG_API_KEY is Vite's mechanism for injecting environment variables at build time. During the build process, Vite reads from a .env file (or CI/CD environment variables) and replaces import.meta.env.VITE_BUGSNAG_API_KEY with the actual value. Critically, the .env file containing the real key is:

  • Not committed to source control (listed in .gitignore)
  • Not visible in git history for new commits
  • Injected securely by your CI/CD system (GitHub Actions secrets, Vercel environment variables, etc.)

The resulting bundle still contains the resolved key value at runtime — this is unavoidable for a client-side SDK — but the key is no longer in your source code or git history, which is where the real damage from hardcoding occurs.

Setting Up the Environment Variable

To complete the fix, add the following to your .env.local file (development) and your CI/CD secrets (production):

# .env.local (never commit this file)
VITE_BUGSNAG_API_KEY=your_actual_bugsnag_api_key_here

And ensure .env.local is in your .gitignore:

# .gitignore
.env
.env.local
.env.*.local

Prevention & Best Practices

1. Treat All Secrets as Secrets, Even "Public" Ones

The Bugsnag key is a useful case study because it sits in a gray area — it is exposed at runtime. But the lesson is: the vector that matters is source control exposure, not runtime exposure. Apply the same rigor to all credentials regardless of their intended visibility.

2. Use a Secrets Scanner in CI

Add secret scanning to your CI pipeline so hardcoded credentials are caught before they reach the main branch:

  • Gitleaks: Scans git history for secrets
  • TruffleHog: Deep git history scanning with entropy analysis
  • GitHub Secret Scanning: Automatic for public repos, available for private repos on GitHub Advanced Security
  • Semgrep: Rule-based scanning that can detect API key patterns (see rules)

3. Audit Your Git History

If a key was ever hardcoded, assume it is compromised. The remediation steps are:

  1. Rotate the key immediately in the Bugsnag dashboard
  2. Remove the key from git history using git filter-repo or BFG Repo Cleaner
  3. Update the code to use environment variables (as done in this fix)
  4. Notify your security team if the repository is or was public

4. Follow the Twelve-Factor App Methodology

The Twelve-Factor App methodology's third factor is Config: store configuration in the environment, not in code. This is the canonical reference for why environment variables are the right approach.

5. OWASP and CWE References

This vulnerability maps to:
- CWE-798: Use of Hard-coded Credentials
- CWE-259: Use of Hard-coded Password
- OWASP A07:2021: Identification and Authentication Failures
- OWASP Secrets Management Cheat Sheet


Key Takeaways

  • The string 'c9beb7c090034128a89c8e58f261e972' in init.js was a real production Bugsnag key — not a placeholder — and was visible to anyone who inspected the JavaScript bundle.
  • "Client-facing" keys still must not be hardcoded in source code. The risk is git history exposure, CI log leakage, and fork proliferation — not just runtime visibility.
  • import.meta.env.VITE_BUGSNAG_API_KEY is the correct Vite pattern for injecting secrets at build time without committing them to source control.
  • Rotating the key is necessary but not sufficient — you must also change the code pattern, or the next key will be just as exposed.
  • Secret scanning tools can catch this automatically before it reaches production; add Gitleaks or Semgrep to your CI pipeline today.

How Orbis AppSec Detected This

  • Source: The hardcoded string literal 'c9beb7c090034128a89c8e58f261e972' at src/js/init.js:22, passed directly as the apiKey property to Bugsnag.start()
  • Sink: The Bugsnag.start({ apiKey: ... }) call in src/js/init.js:22, which initializes the error monitoring client with the exposed credential
  • Missing control: No environment variable indirection, no build-time secret injection, and no .gitignore protection for the credential value
  • CWE: CWE-798 — Use of Hard-coded Credentials
  • Fix: Replaced the string literal with import.meta.env.VITE_BUGSNAG_API_KEY so the key is read from environment configuration at build time rather than embedded in source code

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 secrets are one of the most preventable vulnerability classes in software development, yet they remain stubbornly common — especially for credentials that developers mentally categorize as "not really secret." The Bugsnag API key in init.js is a perfect example: it felt harmless because Bugsnag keys are used in client-side code. But the moment it was committed to source control, it became a permanent liability in the git history of every clone and fork.

The fix is as simple as the vulnerability: one line, replacing a string literal with import.meta.env.VITE_BUGSNAG_API_KEY. The real lesson is to build the habit of never typing a credential value directly into source code — reach for an environment variable first, every time, without exception.


References

Frequently Asked Questions

What is a hardcoded secret vulnerability?

A hardcoded secret is a credential, API key, token, or password that is written directly into source code as a string literal instead of being loaded from a secure external source like environment variables or a secrets manager.

How do you prevent hardcoded secrets in JavaScript?

Use environment variables (e.g., `process.env` for Node.js or `import.meta.env` for Vite), store sensitive values in `.env` files that are excluded from version control via `.gitignore`, and use secret scanning tools in your CI pipeline.

What CWE is hardcoded secrets?

Hardcoded credentials map to CWE-798 (Use of Hard-coded Credentials), which is part of the broader CWE-259 (Use of Hard-coded Password) family.

Is rotating the API key enough to prevent this vulnerability?

No. Rotating the key removes the immediate risk from the exposed value, but the underlying pattern — hardcoding secrets in source code — will expose the new key just as quickly. You must also change the code to read from an environment variable.

Can static analysis detect hardcoded secrets?

Yes. Tools like Semgrep, Gitleaks, TruffleHog, and GitHub's secret scanning feature can detect hardcoded API keys, tokens, and passwords in source code and git history automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1548

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."

high

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.

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.

critical

How eval() Code Injection happens in JavaScript and how to fix it

A critical code injection vulnerability was discovered in `js/lib/jsencrypt.js` at line 195, where a direct `eval()` call executed a JavaScript string shim for the `process` object in browser environments. If an attacker could influence the string passed to `eval()`—through a compromised dependency, a man-in-the-middle attack, or supply chain tampering—they could achieve arbitrary JavaScript execution in any user's browser. The fix replaces the `eval()` call with the equivalent inline JavaScript