Skip to main content
NEW · Blast Radius: a field playbook for leaked API keys on AWS, GCP and Azure
Back to Blog

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.

Ben Kim
Written by
Ben Kim
Published
Updated
6 min read837 words
Share:
Illustrative Stripe key classification: publishable pk_live_ versus secret sk_live_

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.

typescript
// 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.

bash
rg -l 'sk_live_|rk_live_' .next/static

What 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.

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

Stripe — API key types and safe placement

Vercel — Environment variables and deployments

Share it with your networkLinkedInX

Read next

Get the next one in your inbox

Monthly NHI security brief from Cremit. One email, high signal.

We never sell your email. Unsubscribe anytime.

Frontend API key leaks: what to check in JavaScript | Cremit