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:
- Flood your error dashboard with thousands of fake error reports, drowning out real alerts and causing alert fatigue that masks actual incidents.
- Poison your error data by submitting fabricated stack traces, misleading your engineering team about the health of production.
- Exhaust your Bugsnag quota, potentially disabling error monitoring entirely — at exactly the moment you need it most during an incident.
- 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:
- Rotate the key immediately in the Bugsnag dashboard
- Remove the key from git history using
git filter-repoor BFG Repo Cleaner - Update the code to use environment variables (as done in this fix)
- 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'ininit.jswas 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_KEYis 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'atsrc/js/init.js:22, passed directly as theapiKeyproperty toBugsnag.start() - Sink: The
Bugsnag.start({ apiKey: ... })call insrc/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
.gitignoreprotection for the credential value - CWE: CWE-798 — Use of Hard-coded Credentials
- Fix: Replaced the string literal with
import.meta.env.VITE_BUGSNAG_API_KEYso 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.