← Back to blog
CURSOR

Your Cursor App is Probably Leaking API Keys

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_ or VITE_ 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.

Frequently asked questions

Does Cursor really leak API keys in production apps?

Yes. Cursor optimizes for speed, not security. vibeAudit's 46-app audit found 60% of Cursor-built apps leaked at least one API key in the client JavaScript bundle — Stripe secret keys, OpenAI keys, Supabase service_role JWTs, and raw postgres:// connection strings. All visible via View Source.

What is the NEXT_PUBLIC_ prefix trap in Cursor apps?

In Next.js, any env var prefixed with NEXT_PUBLIC_ gets bundled into client-side JavaScript. Cursor frequently adds this prefix to variables that should stay server-side (database URLs, secret keys). vibeAudit greps the deployed bundle for these patterns and verifies each hit against a live endpoint.

Which API key patterns does vibeAudit detect?

vibeAudit scans for sk_live_ (Stripe secret), sk- (OpenAI), AKIA (AWS access key), service_role (Supabase god-mode JWT), postgres:// (direct DB connection), GitHub tokens, Firebase config leaks, and -----BEGIN private key blocks. Each hit is validated against a live endpoint before it counts.

How do I fix a leaked API key after vibeAudit finds it?

Rotate the key immediately in the provider dashboard — the old one is compromised the moment it hit the bundle. Then remove the NEXT_PUBLIC_ prefix (or move the secret to a server-only env file), redeploy, and add a build-time check that fails the deploy if secret patterns appear in the client bundle.

How fast are leaked keys exploited in the wild?

Minutes. Public GitHub repos are scraped by bots within seconds of push; deployed frontends with leaked Stripe keys have been drained inside an hour of going live. vibeAudit exists because waiting to 'get around to security' means paying the breach before you pay the fix.

Can vibeAudit scan Cursor apps deployed on Vercel, Netlify, and Railway?

Yes. vibeAudit tests the live URL, regardless of hosting provider. It works with Vercel, Netlify, Railway, Cloudflare Pages, Render, and any custom domain. The scan takes ~30 seconds and needs no access to your source code or deploy pipeline.

Is there a free tier of vibeAudit for Cursor developers?

Yes. The free scan returns total vulnerability counts, severity breakdown, and the top categories of issues found. The $4.99 deep scan adds full evidence, reproduction steps, the exact bundle location of each leaked key, and copy-paste fix code.

Is your app vulnerable?

Scan your app for free — read-only, no signup, ~30 seconds. vibeAudit runs the tests this post describes.

Scan your app free