Is there an API key hardcoded in a website's JavaScript?
Front-end code is public by definition: the browser has to download it before it can run it. A secret compiled into that code is therefore published, not hidden, and reading it takes nothing more sophisticated than opening the page source. This is the leak that no amount of blocking file paths can prevent, because there is no path to block.
Why a key in the bundle is worse than a key in a file
An exposed .env file at least requires someone to ask for it by name. A key inside a bundle needs no request at all beyond the ones the site already invites: whoever loaded the page has the bytes on their machine, and so does every crawler, archive and browser cache that ever visited. There is no window in which it was exposed. There is only the period since deployment.
The blast radius is set by the credential, not by the site. A payment provider's secret key issues refunds and reads customer records. A model provider's key bills usage to the owner's card, and those bills arrive in hours rather than months. A cloud key reaches every service in the account, most of which have nothing to do with the website. None of that is bounded by what the site itself was supposed to be able to do.
And the fix is the one that has to happen first, not the one people reach for. Removing the key from the next build stops future visitors from receiving it and changes nothing about the copies already taken. Rotation at the provider is what actually ends the exposure, which is why every finding in this group leads with it.
The keys that belong in the page, and the ones that do not
This is the check that a naive scanner gets loudly wrong, because plenty of long opaque strings in a page are supposed to be there. A Stripe publishable key, a Firebase web config, a Google Maps browser key, a reCAPTCHA site key and an analytics id are all public identifiers by design, restricted at the provider by domain or referrer rather than by secrecy. Reporting one as a leak is not harmless caution: it teaches the reader that the section cries wolf, and the next finding under it is the real one.
CheckWeb checks the allowlist of public-by-design formats first, before any detection rule runs, and a key that matches it produces nothing at all. What is reported after that is format-anchored: a string that opens with a vendor's own secret-key prefix, a PEM private key block, a token shape that only one issuer produces. Those do not occur by accident.
The one case that needs a closer look is Supabase, where the key that belongs in every page and the key that bypasses every access rule in the project are the same length, the same alphabet and sit under the same field name. The difference is a claim inside the token, so the claim is what gets read: anon passes silently, service_role is reported as critical.
Nothing is ever tested against the provider. Calling an API to see whether a key is live would mean using a credential that is not ours, on an account that is not ours, and leaving a record of it in someone's audit log. Detection here is by shape alone.
How it looks in the report
The finding names the provider, the file, the line and a masked fingerprint: a format prefix and a length, or four characters from each end. The value itself is read, classified and dropped. It is never written to the report, the PDF or the database, which means the scan can say what was found without becoming a second place the secret lives.
Severity follows what the credential reaches. A payment or model provider's secret key, a private key and a cloud service account are critical. A messaging or mail key is high. Where the same key appears in several bundles it is one finding, because it is one mistake with one fix, not four separate incidents.
This is the active layer, and it runs only after domain ownership is verified. The free passive scan reads a site's headers and its library versions; it does not download the bundles of a domain it was merely pointed at and read them for credentials.
Stripe secret key is published in the site's front-end code
A Stripe secret key is compiled into code the site serves to every visitor. Roll it in the Stripe dashboard, then check recent activity for charges and refunds nobody made. The browser only ever needs the publishable pk_ key.
/assets/index-4f2.js · sk_live_…, 39 chars
Compiled in vs kept on the server
Compiled in
The build inlines a secret into the bundle, usually because the variable was named so that the bundler was allowed to expose it, or because a code assistant wrote the working value straight into the file to make the call succeed. The site works, nothing warns, and the key ships to every visitor from then on.
Kept on the server
The browser calls an endpoint on the same site, and that endpoint holds the credential and talks to the provider. The front-end carries only public identifiers: a publishable key, a project id, an analytics tag. Nothing in the bundle is worth extracting.
The bundler is not the safeguard here. Prefixes such as NEXT_PUBLIC_ or VITE_ mark a value as safe to publish; they do not check whether it is.
How to fix a key that is already in the bundle
- Rotate the key at the provider first, before touching the code. It has been public since the build was deployed, and it stays valid until the provider is told otherwise. Every other step protects future visitors only.
- Read the account's usage, billing and audit trail for the period the key was live. A leaked model or payment key is used within hours of being found, and the charge is the first sign for most owners.
- Move the call behind an endpoint on the site itself. The browser asks the site, the site holds the credential and asks the provider. This is the change that makes the leak impossible rather than fixed.
- Where the browser genuinely needs a credential, use one that is scoped: a publishable key, a token issued per session with a short lifetime, or a signed URL. Those are designed to be seen.
- Check what else was compiled in at the same time. A key rarely arrives alone, because whatever put one value in the bundle usually put the rest of the configuration there too. (The wider secrets catalogue covers the file half of the same problem.)
- Turn off source maps in the production build while making these changes, so the readable original of the code is not published alongside it. (Public source maps covers what a published map gives away.)
- Then re-run the audit and confirm the finding is gone. A rebuild that was never deployed, or an edge cache still holding the old bundle, both look exactly like a fix until something asks the site again.
Run a free scan
The free passive scan grades headers, TLS and known CVEs on any site in about thirty seconds, with no signup. Verifying that a domain is owned adds the deep scan, which reads the page and the scripts it loads and reports the credentials compiled into them, masked rather than copied.
Check my websiteThis one needs an active scan
A passive scan reads what a site shows every visitor, and this finding is not in that layer. It takes an active audit, run by the owner on a domain they have verified, to look where it hides.
How the deep audit works →FAQ
- Does minifying or obfuscating the bundle hide the key?
- No. Minification renames variables and removes whitespace; the string literal survives untouched, because the code has to send the real value for the call to work. Automated scanners search for these formats in minified bundles specifically, since that is where they live.
- The key is in an environment variable, so is it safe?
- Only if the build keeps it on the server. A variable marked as public, which is what prefixes such as NEXT_PUBLIC_ and VITE_ mean, is inlined into the bundle at build time and shipped. The prefix records a decision that the value is publishable; nothing verifies that the decision was right.
- Is a Firebase apiKey in the page a leak?
- No, and it is the most common false alarm in this category. The Firebase web config, like a Stripe publishable key or a Maps browser key, is an identifier the browser is meant to hold; it is restricted by domain and by security rules rather than by being secret. CheckWeb checks that allowlist before anything else and never reports one.
- Is the key tested to see whether it still works?
- Never. Detection is by format alone. Calling a provider to validate a credential would mean using an account that is not ours and leaving the attempt in somebody's audit log, which is exactly the behaviour a security scan should not have.
- Why can the free scan not check this on any site?
- Downloading a site's bundles and reading them for credentials goes past what a passive look at a page involves, so it runs on verified domains only. It is also why a clean free report never claims a site has nothing hardcoded in its front-end code.
Related checks
- Public source mapsThe neighbouring finding: a published .map file rebuilds the readable original of the same bundle, comments and all.
- Exposed secrets and API keysThe file half of the problem: config files, private keys, cloud credentials and backups that answer a plain request.
- Exposed .env fileWhere the same credentials live on the server, and the three ways that file ends up publicly readable.
- GraphQL introspectionWhat a leaked key opens once the API has also published its schema: every field, named and ready to ask for.