Leaked API key in client-side code: how to find one
An API key shipped in HTML or a bundle can be read by anyone who views source. squirrel scans every page and script against 87 key patterns.
What a leaked secret is
A leaked secret is a credential that reaches the browser: an API key in a bundle, a database connection string in server-rendered HTML, a token in an inline script. Anything the browser can load, anyone can read, and automated scrapers do read it. A leaked key is used within hours of being published.
Not every key in client-side code is a leak. Stripe publishable keys, Firebase and Google browser keys, OAuth client IDs, Supabase anon keys and Sentry DSNs are designed to ship publicly and are safe as long as their server-side restrictions are configured. The dangerous ones are secret keys, service-role keys, personal access tokens and anything with a database password in it.
What squirrel checks
The rule scans the HTML of every crawled page, every inline <script>, and the content of external JavaScript files the crawl fetched. It matches against 87 patterns covering AI providers, cloud platforms, payment processors, databases, source hosts, messaging services and generic assignments such as api_key = "...". Findings are deduplicated by value across the whole site, and each reported value is masked. It emits these checks:
leaked-secrets-highfails when a high-confidence pattern matched. Message:2 high-confidence leaked secret(s) detected. Each item reads as the pattern name plus the masked value, labelledFound in inline-script (https://example.com/page). Severity error.leaked-secrets-mediumwarns on medium-confidence matches, which are mostly the generic assignment patterns. Message:4 potential secret(s) detected (verify manually).leaked-secrets-publicreports info for keys that are public by design. Message:3 public client-side key(s) found (public by design — verify usage restrictions are configured). These never count as leaks.leaked-secretspasses when no high- or medium-confidence match remains. Message:No leaked API keys or secrets detected.
The location on each item is html, inline-script or external-script. Masking keeps the first six and last four characters when the value is longer than twelve, and the first four otherwise, so the report identifies the key without republishing it. Weight is 10, the highest of any rule in the Security category.
How to fix it
// server route, not the client bundle
const res = await fetch("https://api.example.com/v1/data", {
headers: { Authorization: `Bearer ${process.env.EXAMPLE_SECRET_KEY}` },
});Move the call behind an endpoint you control and read the key from an environment variable that the bundler never inlines. In a framework with a public-variable prefix, check that the leaked name does not carry it. Rotate the exposed credential before anything else: it has been public for as long as the page has.
A medium-confidence hit on a variable named apiKey holding a placeholder is a false positive. Confirm the value before rotating.
| Rule ID | security/leaked-secrets |
| Category | Security |
| Scope | Site-wide |
| Severity | error |
| Weight | 10/10 |
Enable / disable
Disable this rule
[rules]
disable = ["security/leaked-secrets"]Disable all Security rules
[rules]
disable = ["security/*"]Enable only this rule
[rules]
enable = ["security/leaked-secrets"]
disable = ["*"]Related rules
- content/dev-leakage: staging URLs, stack traces and debug output in the same responses.
- integrity/obfuscated-script: injected code that would use a key it finds.
- security/csp: the policy that limits which scripts run at all.
- security/sri: integrity hashes on the third-party bundles the scan reads.
Security findings ship in every audit next to the SEO, performance and agent experience rules. See Website security scan with AI for how an agent works through a report.
References
Check your site
Run squirrel audit https://example.com and open the Security section of the report. Every match is listed with its pattern name, masked value and the page or script it came from. Local audits are free.