What is API Key Exposure?
You ask an AI to connect your app to Stripe, Supabase, or OpenAI. It generates working code — and drops the secret key right into a client-side component. Now anyone who opens their browser's dev tools can see it. Or it ends up in your Git history, your build output, or a public config file.
The danger isn't theoretical. Bots constantly scan GitHub for exposed keys. When they find one, the key is tested and exploited within minutes. A leaked Supabase service role key bypasses every Row Level Security policy you've set up. A leaked Stripe secret key gives full access to your payment infrastructure.
This is classified under CWE-798 (Hard-Coded Credentials) and maps to OWASP #2: Cryptographic Failures. It's one of the most common issues in AI-generated code because the model's goal is to make things work, and hard-coding a key is the fastest path to working code.
How does API Key Exposure work?
The pattern is simple: a secret value that should only exist on the server gets placed somewhere the client can reach it. In Next.js, any environment variable prefixed with NEXT_PUBLIC_ is bundled into the client-side JavaScript and visible to everyone.
Here's what AI-generated Supabase code often looks like:
// lib/supabase.ts (client-side)
import { createClient } from '@supabase/supabase-js';
// Problem: NEXT_PUBLIC_ prefix exposes this to the browser.
// The service role key bypasses all RLS policies.
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!,
);
export default supabase;// lib/supabase-server.ts (server-only)
import 'server-only';
import { createClient } from '@supabase/supabase-js';
// Service role key: no NEXT_PUBLIC_ prefix,
// never reaches the browser.
const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
);
export default supabaseAdmin;
// lib/supabase-client.ts (client-side)
import { createClient } from '@supabase/supabase-js';
// Only the anon key is safe for client-side use.
// It respects RLS policies.
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
export default supabase;Why do AI tools generate API Key Exposure vulnerabilities?
AI code generators optimize for one thing: making the code work. And hard-coding a key is the fastest way to get a working API call. The model doesn't understand the difference between a public anon key and a secret service role key — they're both strings that make the request succeed.
- Working code is the goal, not secure code. When you say "connect to Supabase," the model picks the most direct path. That path often involves the service role key because it works without RLS getting in the way.
- Environment variable conventions are framework-specific. The NEXT_PUBLIC_ prefix rule, Vite's VITE_ prefix, Expo's EXPO_PUBLIC_ — these are framework details that models frequently get wrong or ignore entirely.
- Training data is full of examples with exposed keys. Tutorials, blog posts, and Stack Overflow answers routinely hard-code keys for simplicity. The model learned from thousands of these examples.
GitGuardian's 2024 report found 12.8 million new secrets exposed in public GitHub repos in a single year. With AI tools generating more code faster than ever, that number is only going up.
Common API Key Exposure patterns
NEXT_PUBLIC_ prefix on secret keys
Service role keys, Stripe secret keys, or API tokens prefixed with NEXT_PUBLIC_, exposing them in the browser bundle.
Keys in Git history
Key was in the code, got removed in a later commit — but the old commit still has it. Bots check Git history.
Exposed .env files
.env or .env.local accessible via the web server because the deployment config doesn't block dotfiles.
Keys in error messages or logs
Stack traces, debug logs, or error responses that include connection strings or API keys.
How Flowpatrol detects API Key Exposure
Flowpatrol checks for exposed credentials the way an attacker would — by inspecting what's actually visible from the outside:
- 1Scans client-side JavaScript bundles for patterns that look like API keys, tokens, and connection strings.
- 2Checks common config file paths — .env, .env.local, config.js, and other files that should never be publicly accessible.
- 3Identifies key types by matching against known formats for Supabase, Stripe, AWS, OpenAI, and other popular services.
- 4Validates severity — distinguishes between public anon keys (safe) and service role or secret keys (critical) so you know what actually needs fixing.
A leaked anon key is fine — that's what it's for. A leaked service role key is a full database bypass. Flowpatrol tells you the difference.
Related terms
Check your app for exposed keys.
Flowpatrol scans your client-side bundles and config files for leaked credentials. Five minutes. One URL.
Try it free