Secrets in Browser Extensions and Client-Side Code: The Credential Risk Developers Ignore
July 3, 2026
The Frontend Is Not a Vault — But Most Developers Treat It Like One
Server-side secret hygiene gets most of the attention: rotate your AWS keys, don't commit .env files, lock down your CI/CD pipeline. But a quieter category of credential exposure is sitting in plain sight: your bundled JavaScript, your browser extensions, and anything else that ships to the client.
Unlike a server-side leak that requires an attacker to gain access to your infrastructure, a client-side secret leak is public by design — you intentionally shipped it to every visitor's browser. There's no breach required. Anyone with DevTools can read it.
How Secrets End Up in Client-Side Code
The path is usually innocent-looking. A developer needs to call an external API from the browser — a mapping service, a payment processor, an analytics platform. They reach for the API key they already have in their .env file and wire it in.
Bundlers and Environment Variable Injection
Frameworks like Vite, Create React App, and Next.js support environment variable injection at build time. Variables prefixed with VITE_, REACT_APP_, or NEXT_PUBLIC_ are deliberately inlined into the JavaScript bundle. That's the intended behavior — but developers often inject secrets that have no business being public:
# .env (developer intent: "just for the build")
REACT_APP_STRIPE_SECRET_KEY=sk_live_... # ← now shipped in your bundle
VITE_INTERNAL_ADMIN_API_KEY=... # ← readable by anyone who opens DevTools
The distinction between a publishable key (safe for clients) and a secret key (server-only) is real and documented by most API providers — but it's easy to miss under deadline pressure.
Source Maps
Production source maps — the .js.map files that help you debug minified code — can expose not just your original source structure but any secrets embedded in it, now with helpful variable names attached. Many teams ship source maps to their CDN without realizing they're readable by anyone who looks at the network tab.
Browser Extensions
Browser extensions are distributed as zip files of JavaScript and JSON, uploaded to the Chrome Web Store or Firefox Add-ons. Once published, anyone can download and unpack the extension with a single command:
unzip extension.crx -d unpacked_ext
grep -rE "(key|token|secret|api)['\"]?\s*[:=]\s*['\"][A-Za-z0-9_\-]{16,}" unpacked_ext/
This is the same technique security researchers use — and attackers too. Hardcoded Stripe keys, Airtable tokens, internal API credentials, and Slack webhook URLs have all been found in published browser extensions this way.
OAuth Client Secrets vs. Client IDs
OAuth flows legitimately require a client ID in the browser — it identifies your app to the authorization server and is not secret. The client secret, by contrast, must never leave your server. The naming is confusing enough that this mistake happens constantly, and the result is that an attacker can impersonate your OAuth application.
What Attackers Actually Do With These Keys
Exposed client-side secrets are not just a compliance problem. They enable concrete attacks:
- API abuse and bill inflation: An exposed key for a paid API (maps, AI, SMS) lets anyone run up charges on your account.
- Data exfiltration: An internal admin API key in your bundle may grant read access to customer data — no authentication bypass needed.
- Lateral movement: A leaked key for one service often shares permissions with others, especially in poorly scoped IAM environments.
- Persistent access: If the key isn't rotated after discovery, an attacker retains access indefinitely. Browser extension keys are particularly long-lived because developers forget to rotate them after publishing.
Auditing Your Frontend for Exposed Secrets
Before you can fix the problem, you need to find it. Here's a practical audit process:
1. Inspect Your Production Bundle
Download your deployed JavaScript bundle(s) and run a pattern search. Don't rely on your local build — check what actually shipped:
curl -s https://yourapp.com/static/js/main.chunk.js | \
grep -oE '(sk_live|ghp_|AKIA|xox[baprs]-)[A-Za-z0-9_\-]+'
Also check for any .map files served from the same path.
2. Scan Your Repo, Not Just Your Bundle
The bundle is the output; the source is where the fix needs to happen. Run a secret scanner against your full codebase, including frontend source files. Many scanners miss client-side patterns because they're tuned for backend codebases — make sure yours covers TypeScript, JSX, and JSON config files.
To catch this quickly across your whole repo, run a free GhostCred scan — it covers frontend source files and flags secrets mapped to SOC 2 and HIPAA controls, not just raw regex hits.
3. Audit Your Browser Extension Manifest and Scripts
Check your extension's manifest.json for any embedded values, then grep all .js files in the extension package for credential-shaped strings before every release.
4. Check What Your CI Build Exports
Look at your CI configuration and identify every environment variable injected at build time. For each one, ask: does this need to exist in the browser, or only on the server? Any secret that can be used server-side should stay server-side.
How to Fix It: Keeping Secrets Off the Client
Use a Backend Proxy for Sensitive API Calls
If you need to call a third-party API that requires a secret key, make the call from your own server and expose only a scoped internal endpoint to your frontend. The browser calls your API; your server calls theirs. The secret never leaves your infrastructure.
Use Publishable Keys Where Providers Offer Them
Stripe, Mapbox, Firebase, and many others offer separate publishable/restricted keys explicitly designed for client-side use with limited permissions. Use these instead of full-access keys.
Restrict Key Permissions and Add Referrer Locks
For APIs that require a key in the browser (like Google Maps), use the provider's referrer restrictions or IP allowlists so the key only works from your domain. This doesn't prevent someone from reading the key, but it significantly limits what they can do with it.
Treat NEXT_PUBLIC_ / REACT_APP_ / VITE_ as Public
Establish a team convention: if a variable has a public prefix, it must be reviewed for sensitivity before it's added. Add a lint rule or pre-commit hook that flags when values matching secret patterns are assigned to public env variable names.
Rotate Anything You Find
If you discover a secret that was shipped in a bundle, assume it's compromised. The bundle may be cached on CDNs, in browser caches, in Wayback Machine snapshots, and in monitoring tools. Rotate the secret immediately, then fix the source, then re-deploy.
The SOC 2 and HIPAA Angle
Auditors reviewing your security posture will ask about your secrets management practices. Shipping credentials in client-side code — even inadvertently — can directly implicate SOC 2 CC6.1 (logical access controls) and HIPAA's technical safeguard requirements around access control. If a secret grants access to systems that touch protected health information or customer data, an exposed client-side key is a reportable event waiting to happen.
Summary: Client-Side Is the Attacker's Easiest Win
Server-side secret scanning is table stakes. But your bundled JavaScript, your browser extensions, and your injected build variables are an equally real exposure surface — and one that's often completely unmonitored. Every key you ship to the browser is one an attacker can read without ever touching your servers.
Audit what you've already shipped. Proxy sensitive API calls server-side. Use restricted publishable keys where the provider supports them. And make secret scanning part of your standard build process, not a one-time exercise.
See what's exposed in your own code.
Run a free scan