ScanVibeScanVibe
·7 min read·ScanVibe Team

Exposed API Keys: The #1 Vulnerability in AI-Built Apps

api-keyssecurityvibe-coding

Exposed API Keys: The #1 Vulnerability in AI-Built Apps

When we scan apps built with AI coding tools, one vulnerability appears in over 60% of them: exposed API keys in frontend JavaScript.

This isn't a theoretical risk. Exposed keys lead to real consequences: unauthorized database access, runaway cloud bills, data breaches, and account takeovers. And it's happening at scale because AI coding tools embed secrets in client-side code by default.

60%+ of AI-built apps we scan have exposed API keys in their frontend JavaScript

Why AI Tools Expose Keys

When you prompt Lovable to "add Supabase auth" or tell Cursor to "connect to my Firebase project," the AI does what you asked — it writes working code. But "working" doesn't mean "secure."

Here's what typically happens:

  1. The AI puts keys where the code needs them — usually in a frontend config file or directly in component code
  2. The app works, so the developer ships it
  3. The keys are now in the JavaScript bundle, visible to anyone who opens DevTools
The AI isn't being malicious. It's optimizing for "make it work," not "make it secure." And most vibe coders — especially beginners — don't know the difference between a publishable key and a secret key.

What Keys Get Exposed?

We categorize exposed keys by severity:

Critical Immediate Action Required

Supabase service_role key
Full database bypass. Read/write/delete everything.
Critical
Stripe Secret Key (sk_live_...)
Create charges, refund payments, access customer data.
Critical
Database connection strings
Direct database access, no API layer protection.
Critical
AWS Secret Access Keys
Access to S3 buckets, Lambda functions, cloud resources.
Critical
SendGrid / Resend API keys
Send emails as your domain. Potential phishing vector.
Critical

High Should Fix Soon

Firebase Admin SDK credentials
Bypass security rules, full Firestore/RTDB access.
High
OpenAI / Anthropic API keys
Make API calls on your bill. $1000+ bills reported.
High
Twilio Auth Tokens
Send SMS, make calls on your account.
High

Low Risk Generally Safe in Frontend

Supabase anon key
Designed for frontend use, protected by RLS (if enabled).
Low
Firebase apiKey
A project identifier, not a secret (often confused for one).
Low
Stripe Publishable Key (pk_live_...)
Designed for frontend use in Stripe.js.
Low
Google Maps API Key
Safe if restricted to your domain in Google Cloud Console.
Low

How to Find Exposed Keys

Manual Check

Open your deployed app, then:

  1. Open DevTools (F12)
  2. Go to Sources tab
  3. Search (Ctrl+Shift+F) for patterns like:
sk_live_ Stripe secret
service_role Supabase admin
AKIA AWS access key
sk- OpenAI key
SG. SendGrid key
eyJhb JWT / Supabase key

If you find any critical keys, rotate them immediately.

Automated Check

ScanVibe's secrets analyzer scans your app's JavaScript bundles, HTML source, and network requests for 30+ patterns of exposed credentials. One scan covers everything.


How to Fix It

The fix depends on your framework, but the principle is always the same: secrets go on the server, never in the browser.

Next.js

# .env.local

# PUBLIC — safe for browser (no secret access)
NEXT_PUBLIC_SUPABASE_URL=https://abc.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhb...public

# PRIVATE — server-side only
SUPABASE_SERVICE_ROLE_KEY=eyJhb...secret
STRIPE_SECRET_KEY=sk_live_...
OPENAI_API_KEY=sk-...
In Next.js, only variables prefixed with NEXT_PUBLIC_ are included in the browser bundle. Everything else stays server-side.

For API Calls That Need Secret Keys

Create a server-side API route:

// app/api/generate/route.ts (server-side)
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY, // Never in browser
});

export async function POST(request: Request) {
  const { prompt } = await request.json();

  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: prompt }],
  });

  return Response.json({ result: response.choices[0].message.content });
}

Then call it from your frontend:

// Client-side — no API key needed
const response = await fetch('/api/generate', {
  method: 'POST',
  body: JSON.stringify({ prompt: 'Hello' }),
});

Vite / React (without Next.js)

# Only VITE_ prefix vars go to the browser
VITE_SUPABASE_URL=https://abc.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhb...public

# These stay server-side (use in a backend)
SUPABASE_SERVICE_ROLE_KEY=eyJhb...secret
If you're using a pure React SPA with no backend, you need to add one (even a simple Express server or Cloudflare Worker) for any operations that require secret keys.

What To Do If Your Keys Are Already Exposed

If you've found exposed keys in production:

  1. Rotate the key immediately. Generate a new one in the service's dashboard.
  2. Check for unauthorized usage. Look at billing dashboards, API logs, and database audit logs.
  3. Update your code to use the new key server-side only.
  4. Check git history. If the key was ever committed to a repo, consider the old key permanently compromised — even if you removed it in a later commit.
  5. Use .gitignore to exclude .env files from version control.

Where to Rotate Keys

Supabase
Settings → API → Generate new keys
Stripe
Developers → API keys → Roll key
OpenAI
API keys → Create new secret key
Firebase
Project settings → Service accounts
AWS
IAM → Users → Security credentials
SendGrid
Settings → API Keys → Create key

Prevention: Stop It Before It Ships

🔒
5 steps to keep your secrets safe:

1. Use ScanVibe to scan every deployment. We catch exposed keys before attackers do.
2. Add a pre-commit hook with tools like gitleaks or trufflehog to catch secrets in commits.
3. Review AI-generated code before shipping. Check for hardcoded strings that look like keys.
4. Educate your team on the difference between public and secret keys.
5. Use environment variable validation to ensure server-side vars are set.
// lib/env.ts
function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing env var: ${name}`);
  return value;
}

export const env = {
  stripeSecret: requireEnv('STRIPE_SECRET_KEY'),
  supabaseServiceRole: requireEnv('SUPABASE_SERVICE_ROLE_KEY'),
};

The Bottom Line

AI coding tools are incredible for shipping fast. But they don't think about security — they think about making your code work. The #1 thing you can do to secure your AI-built app is to make sure your secrets stay on the server.

5 min to fix exposed keys. Months to recover from a breach.

Related articles

Scan your app now

Check your AI-built app for security vulnerabilities in seconds. Free, no signup required.

Start Scanning