TL;DR
The fastest way to find a leaked API key is to open your own JavaScript bundle and search it, because that is exactly what attackers do, automatically, at scale. GitGuardian detected 29 million new hardcoded secrets in 2025 alone, a 34% jump and the largest single-year increase ever, and 70% of secrets leaked in 2022 were still active years later. Anything shipped to the browser is public: minification is not encryption, and a bundled key is one view-source away from a breach. With the U.S. average data breach now costing $10.22M and stolen credentials a top-three initial access vector at $4.67M per breach, a hardcoded credential is not a code-smell, it is a six-to-eight-figure liability sitting in a file you serve to every visitor.
Minified is not hidden
There is a persistent and dangerous belief that because frontend JavaScript is minified, bundled, and obfuscated, the secrets inside it are somehow protected. They are not. Minification renames variables and strips whitespace; it does not encrypt anything. A string like "sk_live_51H8x..." survives minification perfectly intact, because the bundler has no reason to touch a string literal. Every byte you ship to the browser is readable by anyone with developer tools, which is everyone. "Obfuscated" is not a security boundary; it is a speed bump that takes a script ten seconds to clear.
Attackers know this far better than most developers do. They run automated crawlers that download JavaScript bundles, source maps, and inline scripts, then grep them for the unmistakable patterns of API keys, tokens, and credentials. They scan public GitHub, npm packages, and Docker images the same way, continuously. The scale is staggering: per the GitGuardian State of Secrets Sprawl 2026 report, 29 million new hardcoded secrets were detected in 2025, a 34% year-over-year increase and the largest single-year jump on record. This is an industrial-scale harvesting operation, and your bundle is in scope by default.
How keys end up in the browser
Nobody decides to ship a secret to the frontend on purpose. It happens through a handful of well-worn mistakes.
The "it's just for the API call" key
A developer needs to call a third-party API from the client, payments, maps, email, an LLM provider, and the SDK wants a key. The path of least resistance is to paste the key into the frontend code. It works in the demo, ships to production, and now your provider key is in every visitor's bundle. The leak of 113,000 DeepSeek API keys highlighted in GitGuardian's research is exactly this pattern at scale, and the surge in AI-service leaks, up 81% year over year, shows it accelerating as teams race to bolt LLM features onto their products.
The misnamed environment variable
Build tools inline environment variables prefixed for client exposure, NEXT_PUBLIC_, VITE_, REACT_APP_. A developer who does not realize the prefix means "bake this into the public bundle" puts a server secret behind it. The framework dutifully ships it to the browser. This one is especially treacherous because the code looks like it is reading an environment variable, which feels server-side and safe, when the build step has actually hardcoded the value into the client artifact.
The committed config and the source map
A .env file, a config object, or a debug source map gets committed and deployed. Source maps are particularly insidious, they can expose your entire un-minified source, comments and all, to anyone who requests the .map file. Per GitGuardian, 28% of 2025 secret incidents originated outside source code entirely, in places like Slack, Jira, and Confluence, but the bundle is still the most directly exploitable surface because it is served to the public by design, with no authentication required.
Why it is so expensive
A leaked credential is not a theoretical risk. It is a working key to something, your payment processor, your cloud account, your email sender, your model provider, and the cost depends on what it unlocks. A leaked cloud key can spin up crypto-mining infrastructure on your bill, sometimes tens of thousands of dollars before anyone notices. A leaked email key can send phishing from your domain, poisoning your sender reputation. A leaked LLM provider key can run up a five-figure inference bill overnight. And a leaked key with access to customer data is a breach with disclosure obligations.
The breach math is sobering. Per IBM's Cost of a Data Breach 2025, the global average breach costs $4.44M, and the U.S. average has climbed to $10.22M, the highest of any region. Breaches where compromised credentials were the initial access vector run about $4.67M and take a mean of roughly 246 days to identify and contain, among the slowest of any vector, because a valid key looks exactly like legitimate access. A single key in a bundle can be the entire kill chain, and the long dwell time means the damage compounds before you ever know it started.
The compounding problem: leaked keys stay valid
The most damning statistic in the secrets-sprawl research is about remediation, not detection. Per GitGuardian's analysis, 70% of secrets leaked in 2022 were still valid years later. Teams leak a key, maybe even notice, and never actually rotate it. The credential keeps working, sitting in a harvested-keys database, exploitable whenever an attacker gets around to it. Detection without rotation is theater, a found-but-not-revoked key is still a live key, and the clock on its exploitation never stops.
The architectural fix: keys never touch the client
The durable solution is structural: secrets live on the server, and the browser talks to your own backend, which holds the credential and calls the third-party API. The client gets a scoped, short-lived token at most, never the long-lived provider key.
// WRONG, the provider key ships to every visitor's browser
const result = await fetch('https://api.provider.com/v1/charge', {
headers: {Authorization: `Bearer sk_live_51H8xPROVIDER_SECRET`},
// ^ this string is now in your public bundle, forever
});
// RIGHT, the browser calls YOUR server, which holds the secret
const result = await fetch('/api/charge', { // your origin
method: 'POST',
body: JSON.stringify({amount, currency}),
});
// server-side handler (never shipped to the client):
// const key = process.env.PROVIDER_SECRET; // stays on the server
// return providerClient.charge({...req.body}, key);
For cases that genuinely need a client-side identifier, a publishable Stripe key, a domain-restricted Maps key, use the provider's public key type and lock it down with referrer and origin restrictions so a harvested copy is useless from another domain. The rule of thumb: if a key can move money, send mail, read data, or run inference on your account, it must never reach the browser. The distinction between a publishable key and a secret key exists precisely because one is safe in the client and the other is catastrophic there.
Scan the bundle the way an attacker would
Finding secrets is pattern matching against the shapes of known credential formats, and it should run continuously, both in CI on every commit and against the deployed bundle, because a secret can enter through a dependency or a build-config change you did not write.
# Pre-commit and CI: block the commit that introduces a secret
$ trufflehog filesystem ./src --only-verified --fail
$ gitleaks detect --source . --redact --exit-code 1
# Also scan the SHIPPED bundle and any source maps you serve
$ curl -s https://example.com/assets/app.[hash].js \
| grep -oE '(sk_live_|AKIA|ghp_|xoxb-|AIza|hf_)[A-Za-z0-9_-]{12, }'
# And confirm you are not serving source maps in production:
$ curl -sI https://example.com/assets/app.[hash].js.map | head -1
# A 200 here means your un-minified source is public.
The --only-verified flag matters: it actually tests whether the candidate key is live, which separates a real, exploitable, must-rotate-now credential from a placeholder or an expired test key. Verification also collapses the noise problem that makes teams ignore secret-scanning output, a wall of unverified maybes gets muted, while a short list of confirmed-live keys gets acted on. A scan that surfaces live secrets in your deployed bundle, ranked by what each one unlocks, is the difference between a clean disclosure and a five-figure incident.
Close the loop: detect, revoke, prevent
A one-time scan is a snapshot; secrets sprawl is continuous. Make detection part of the pipeline so a new secret fails the build, scan the deployed artifact on a schedule so dependency-introduced leaks get caught, and, most importantly, wire a rotation runbook to every finding so a discovered key is revoked within minutes, not months. Stop serving source maps publicly (or restrict them to authenticated internal access), audit your PUBLIC_-prefixed environment variables to confirm none hold real secrets, and move every consequential key behind your own backend. A Content-Security-Policy that restricts script origins adds a second layer, limiting what an injected script could exfiltrate even if one slips through.
The organizational reality the GitGuardian data points to is that detection has gotten good while remediation has stayed terrible. The differentiator is not whether you can find a leaked key, tools do that well, but whether finding one triggers an immediate, rehearsed rotation. Treat a verified live secret in your bundle as a security incident with a clock, not a backlog ticket.
The bottom line
An API key in your frontend is a public secret, minification hides nothing, and attackers scan bundles automatically at the same scale that produced 29 million leaked secrets in a single year. The cost when one is exploited tracks the breach economics: a $4.44M global average, $10.22M in the U.S., with stolen credentials a $4.67M vector that dwells for the better part of a year. And because most leaked keys are never rotated, the exposure lingers for years. The fix is architectural, keys live on the server, the browser talks to your backend, public keys get origin-locked, backed by continuous secret scanning in CI and against the deployed bundle, verified detection to cut the noise, and immediate rotation on every hit. Scan your bundle before an attacker does, because they are already looking.
Find the Gaps Before They Cost You
Scan audits your site for the accessibility, performance, AEO, and security gaps that quietly drain revenue and invite lawsuits, in one pass.
Try Scan Free →