One in three Cursor-built apps leaks a secret key in the browser. Yours might be one of them.
AI assistants care if code works. They don't care if it's safe. And one of the most common mistakes they make is putting API keys, database credentials, and secret tokens directly in client-side JavaScript.
We've scanned thousands of Cursor apps, and roughly 1 in 3 leak at least one secret in their JavaScript bundle. Here's how it happens, how to find it, and how to fix it.
Three Ways Cursor Puts Your Keys in the Browser
When you ask Cursor to "add Stripe payments" or "connect to the database," it writes code that works. But it often puts the secret key right in the file that gets shipped to the browser. Here are the patterns we see most often:
Pattern 1: Stripe secret key in client code
// Cursor often generates this in a React component:
import Stripe from 'stripe'
const stripe = new Stripe('sk_live_51abc123...') // SECRET KEY IN CLIENT CODE
// Anyone who views your page source can see this key
// and charge any amount to any customer
Pattern 2: The NEXT_PUBLIC_ prefix trap
// In Next.js, any env var starting with NEXT_PUBLIC_ is bundled
// into client JavaScript. Cursor sometimes suggests:
const apiKey = process.env.NEXT_PUBLIC_OPENAI_KEY
// This means your OpenAI key is visible to everyone
// Same problem in Vite apps with VITE_ prefix:
const secret = import.meta.env.VITE_DATABASE_URL
Pattern 3: Hardcoded credentials in utility files
// lib/firebase.ts -- Cursor writes this, you commit it
const firebaseConfig = {
apiKey: "AIzaSyB...",
authDomain: "myapp.firebaseapp.com",
projectId: "myapp-prod",
// The API key here is meant to be public, BUT
// Cursor sometimes also adds:
serviceAccountKey: "-----BEGIN PRIVATE KEY-----..."
// ^ This is catastrophic if it ends up in client code
}
How to Find Leaked Keys in Your App
You can check manually right now. Open your deployed app in Chrome, press F12, go to the Sources tab, and search through your JavaScript files for these patterns:
# Strings to search for in your JS bundles:
sk_live_ # Stripe secret key
sk_test_ # Stripe test key (still dangerous)
AKIA # AWS access key prefix
AIzaSy # Google/Firebase API key
-----BEGIN # Private keys (RSA, service accounts)
mongodb+srv:// # Database connection strings
postgres:// # PostgreSQL connection strings
redis:// # Redis connection strings
ghp_ # GitHub personal access token
xoxb- # Slack bot token
sk- # OpenAI API key
That's 11 patterns to search manually. vibeAudit checks 40+ secret formats automatically — 30 seconds, free. Also see: Is your .env file public?
How to Fix It
The fix is simple in principle: secrets must never be in client-side code. They belong on the server.
Step 1: Move secrets to server-side API routes
// BEFORE: Secret in client component (DANGEROUS)
'use client'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET!)
// AFTER: Secret in server-side API route (SAFE)
// app/api/create-checkout/route.ts
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!) // No NEXT_PUBLIC_ prefix
export async function POST(request: Request) {
const session = await stripe.checkout.sessions.create({
// ...checkout config
})
return Response.json({ url: session.url })
}
Step 2: Audit your environment variable names
# .env.local — audit every variable
# SAFE: These are server-only (no NEXT_PUBLIC_ prefix)
STRIPE_SECRET_KEY=sk_live_...
DATABASE_URL=postgres://...
OPENAI_API_KEY=sk-...
# PUBLIC: These are bundled into client JS
# Only put truly public values here
NEXT_PUBLIC_SUPABASE_URL=https://abc.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
Step 3: Rotate any exposed keys immediately
If a secret was ever in your client code -- even briefly -- assume it's been compromised. Rotate it. This means generating a new key in the provider's dashboard and updating your server environment variables. The old key should be revoked.
Stop This From Happening Again
When using Cursor or any AI coding assistant, always review the code it generates before committing. Specifically look for:
- Any string that looks like a key or token in a client-side file
- Environment variables with
NEXT_PUBLIC_orVITE_prefix that contain secrets - Direct database or API instantiation in React components
- Any file with
'use client'at the top that imports a backend SDK
FAQ
Q: Does Cursor put API keys in client-side code?
Yes, roughly 1 in 3 Cursor-built apps leak at least one secret in the JavaScript bundle.
Q: How do I find leaked keys in my app?
Open DevTools (F12), go to Sources, and search for patterns like sk_live_, AKIA, sk-, or postgres://.
Q: Are NEXT_PUBLIC_ variables safe?
Only for truly public values. Any NEXT_PUBLIC_ variable is bundled into client JavaScript and visible to everyone.
Leaked keys get exploited fast. Run vibeAudit after every deploy. If there's a key in your bundle, we'll find it. Free scan, 30 seconds. Read-only scan, your app is never modified.