How to find if your API keys are exposed in your website
API keys end up in client-side code through several distinct pathways, most of them subtle enough that the developer who introduced them often does not realize it happened. The most common cause in Next.js apps is the NEXT_PUBLIC_ prefix. Any environment variable with this prefix is bundled into the client-side JavaScript that the browser downloads, making it readable to anyone who views the page source or inspects the bundle. This prefix is appropriate for the Supabase anon key, your Stripe publishable key, or your analytics write key — all of which are designed to be public. It is not appropriate for your Supabase service role key, your Stripe secret key, your OpenAI API key, or any other secret that grants server-side access to a paid or privileged resource. AI coding tools frequently use the NEXT_PUBLIC_ prefix for all Supabase keys without distinguishing between the public and private ones.
The browser DevTools Sources tab is the fastest way to check for exposed keys in your own app. Open your deployed site, press F12 to open DevTools, navigate to the Sources tab, and use the global search (Ctrl+Shift+F) to search for common key prefixes: sk_live_ for Stripe, sk- for OpenAI, service_role for Supabase, Bearer for raw auth tokens, and ANTHROPIC or OPENAI for AI API keys. The search will scan across all loaded JavaScript files, including minified and compressed bundles. If any of these strings appear in your bundle, you have a key exposure issue. The key may be deeply nested inside a minified variable name, but it will still be a plain text string that anyone can extract with a few seconds of effort.
The Network tab provides a complementary check. With DevTools open, reload your page and then navigate through the main user flows — log in, load the dashboard, make a few API calls. Click each network request and inspect both the Request Headers and the request payload. Look for Authorization headers that contain raw tokens or API keys, query parameters with key-like strings, or request bodies that include credentials. A well-built app makes all sensitive API calls from the server; the browser should never send a private API key in a network request. If you see one in the Network tab, it means your server-side key handling has a gap — a route that should be proxied through your server is being called directly from the client.
GitHub's code search and its automated secret scanning are the external checks that matter most for keys committed to repositories. GitHub Secret Scanning automatically scans all public repositories for known key patterns — Stripe keys, AWS credentials, Google API keys, and dozens of others — and notifies the associated service provider when it finds a match. Many providers (Stripe, GitHub itself, AWS) will automatically revoke keys found in public repositories. But this revocation happens after the key has already been indexed and is potentially already in the hands of attackers who run their own scanners against public commits. The window between a commit containing a key and an attacker extracting it is often measured in minutes. If you have ever committed a key to a public repository — even if you later deleted the file — the key should be treated as compromised and rotated.
When you find an exposed key, the immediate action is to rotate it — generate a new key from the service provider's dashboard and update all the places that use it. Rotation invalidates the old key, so any attacker who had extracted it can no longer use it. After rotating, audit the service's access logs if available: most providers (Stripe, Supabase, OpenAI) keep logs of API calls, and you can look for calls that used the old key from IP addresses you don't recognize. Once you have rotated and audited, move the key out of any client-accessible location and into a server-only environment variable — one without the NEXT_PUBLIC_ prefix — and add a CI check to prevent the mistake from recurring.