Frontend API key leaks: classify and investigate browser output
A browser-visible key may be public by design or an active secret. Use issuer key types, a documented Resend case, and build evidence to decide.
Editorial record: the earlier site-level exposure rate was removed because the underlying records could not be verified. The Resend case cites its incident report.


On this page(11)
Revised October 4, 2026. The former article presented a site-level exposure rate as Cremit research, but we could not locate the site sample, per-site results, or validation protocol needed to support it. We removed the rate and the unverified examples drawn from it. The Resend incident below is retained with a link to Resend’s own report and its stated limits.
If an API key is in frontend JavaScript, is it a leak?
Anyone who receives a public JavaScript bundle can read values in that bundle. Whether a visible value is a secret depends on the issuer’s key type and permitted use. A Stripe pk_live_ publishable key is designed for client code. A Stripe sk_live_ secret key or rk_live_ restricted key is not. First establish where the value appears, then ask the issuer what it represents and whether it still works. A prefix match alone cannot establish misuse.
Resend: a documented client-side credential incident
Resend’s January 10, 2024 incident report says an attacker found a database API key exposed as an environment variable on the client side of the Resend dashboard on December 30, 2023. The attacker used that access to read customer data starting January 7. Resend removed the client-side variable and rotated its database keys on January 10. The report says message bodies, unencrypted Resend API private keys, and unencrypted DKIM private keys were not accessed. This was a database API credential exposed through a client path; it does not imply every public frontend key can reach a database.
The useful lesson is about the boundary: a value intended for server-side use reached a browser-facing environment, and the issuer accepted it. The investigation needed both the delivered client code and the database API’s access logs. A source-code pattern match alone would not have established the timeline or affected data.
Three places to check in your own application
1. Client code and build output
Review variables with public framework prefixes, values embedded in client components, and values passed from server components into rendered HTML or client props. In Next.js, statically referenced NEXT_PUBLIC_ variables are inlined into the client bundle at build time. Values set in next.config.js env are also bundled. A secret stored in a hosting dashboard can still be exposed if application code places it in that output.
// Public setting: expected in browser code
const stripePublishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
// Wrong boundary if this holds a Stripe secret key
const stripeSecretKey = process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY;2. Public responses and static files
Inspect the actual HTML, JSON responses, source maps if published, and static assets served by production and preview deployments you control. A value need not appear in a named JavaScript variable to be public. Minification changes readability, not access. Record the URL and build identifier without copying the full credential into a ticket.
3. Repository and build records
Check whether .env files, configuration files, or build artifacts containing a key were committed. This is a separate path from browser delivery. A clean client bundle does not prove that Git history or CI logs were clean. Keep the discovery location and who could read it.
A safe local first pass
Search a fresh build of your own project for known secret prefixes. The command below prints filenames rather than matching values. It covers only those prefixes and files under the given path; provider-specific scanners, HTML and response inspection, and issuer checks are still needed. Do not paste a third party’s key into a verifier or make a privileged test call against it.
rg -l 'sk_live_|rk_live_' .next/staticWhat should happen after a real secret is found?
Determine the key’s issuer, owner, current status, permissions, and dependent workload. If it is an active private credential in public output, coordinate a replacement and revoke the old value at the issuer promptly. Remove the code path that published it, build and deploy again, and check older reachable deployments and copies. Review available issuer logs for use during the exposure window. “No observed abuse” is limited by what the logs recorded.
Do not rotate a deliberately public key just because a scanner matched an API-key pattern. Also do not dismiss a restricted secret key as harmless: Stripe treats restricted keys as server-side credentials even when their permissions are narrower. For a valid exposed secret, confirm that the replacement works and that the old key no longer authenticates.
Where Cremit fits
Cremit can find credentials in supported connected sources, check supported types with their issuers, show available AWS access-key or GCP API-key context, and track a response owner. It does not prove that every browser bundle was fetched, that a visible value was abused, or that every issuer key was rotated. Pair connected-source findings with your own build output, deployment records, and issuer logs.
Related Cremit guides
Vercel and Next.js: where environment values enter browser output
Git secret scanning: distinguish a match from a usable key
Primary sources
Resend — Incident report for January 10, 2024
Next.js — Environment variables and client bundling
Next.js — next.config.js env is bundled
Read next
MCP Servers Are Taking API Keys Through the Chat Window
We sent one initialize request to every remote MCP server in the official registry. 71.3% of 22,027 responding servers opened a session with no header authentication, and 3,705 credential fields sit in tool arguments. Three of them are marked secret.
How to count live credentials without confusing matches and keys
Count observed locations, distinct credential records and verification states separately. Define scope and check time before reporting exposure.
Secret scanning false positives: how to triage findings
A pattern match, a revoked key, and an unverified key need different responses. Learn how to separate them and prioritize exposed credentials.
Get the next one in your inbox
Monthly NHI security brief from Cremit. One email, high signal.