You just shipped your app. The landing page looks great, the API works, users are signing up. Life is good.
Meanwhile, your Stripe secret key is sitting in your JavaScript bundle, visible to anyone who opens DevTools.
This isn't a hypothetical. It happens every day, to developers of all experience levels. And with AI-assisted development making it easier than ever to ship fast, it's happening more frequently.
The Anatomy of a Secret Leak
Let's be specific about what we're talking about. A "secret" in this context is any value that should never be visible to end users:
- API keys (Stripe, AWS, Firebase, SendGrid, etc.)
- Database connection strings
- JWT signing secrets
- OAuth client secrets (not client IDs — those are fine)
- Internal service URLs
- Private encryption keys
These end up in client-side code through several common paths:
Path 1: The Environment Variable Mix-Up
In Next.js, environment variables prefixed with NEXT_PUBLIC_ are bundled into client-side code. Everything else stays server-side. Simple, right?
Except when you're moving fast and accidentally prefix a secret key:
# .env
NEXT_PUBLIC_STRIPE_KEY=pk_live_... # ✅ Public key, fine
NEXT_PUBLIC_STRIPE_SECRET=sk_live_... # 🚨 SECRET exposed!
Or when you import a server-side module in a client component, and the bundler pulls in everything.
Path 2: The Hardcoded Shortcut
During development, you hardcode an API key "just for testing." It works. You forget about it. It ships to production.
// "I'll move this to env vars later"
const API_KEY = "sk_live_51HG8xPK9r...";
Narrator: They did not move it to env vars later.
Path 3: The Third-Party SDK
You install a package, follow the quickstart guide, and paste in your credentials. The SDK documentation didn't mention that the initialization happens client-side, and now your API key is in every user's browser.
Path 4: The Build Artifact
Source maps in production. Debug logs with credentials. Config files that got bundled accidentally. The build process has a dozen ways to leak things you didn't intend.
How Bad Is It, Really?
Let's put it bluntly: an exposed API key is a direct financial liability.
- Stripe secret keys: Attackers can issue refunds, create charges, access customer data
- AWS keys: Crypto mining on your account — bills can reach five figures overnight
- Firebase admin keys: Full database access, user data exposure
- SendGrid keys: Your domain gets used for spam, reputation destroyed
Security researchers regularly scan public JavaScript bundles for exposed keys. Automated bots do it too. The window between "deployed" and "exploited" can be hours.
Finding Secrets in Your App
Manual Checking (The Hard Way)
You can inspect your own app's bundle:
1. Open DevTools → Sources tab
2. Look through your compiled JavaScript files
3. Search for patterns like sk_, api_key, secret, token
4. Check network requests for credentials in URLs or headers
This works, but it's tedious, error-prone, and you'll miss things.
Using vibeGuard (The Fast Way)
This is literally why I built vibeGuard. Install the Chrome extension, open your app, and click the icon. It scans for 100+ secret patterns in:
- Inline scripts
- External JavaScript bundles
- Network request URLs and bodies
- DOM attributes and data attributes
Each finding tells you exactly what was found, where, and how to fix it. No configuration, no CI pipeline, no account required.
Fixing Leaked Secrets
If you find an exposed secret, here's your action plan:
Step 1: Rotate Immediately
Do not just remove the key from your code. If it was ever in a public bundle, assume it's been harvested. Go to the service provider's dashboard and rotate the key immediately.Step 2: Move to Server-Side
Secrets belong on the server. Period. If your client needs data from an API, proxy the request through your backend:
// ❌ Client calls API directly with secret
const data = await fetch("https://api.example.com/data", {
headers: { Authorization: "Bearer sk_secret_..." }
});
// ✅ Client calls your backend, which holds the secret
const data = await fetch("/api/proxy/data");
Step 3: Audit Your Environment Variables
Review every NEXT_PUBLIC_ (or VITE_, REACT_APP_, etc.) variable. If a value is a secret, it should not have the public prefix.
Step 4: Add Prevention
- Use
.gitignorefor.envfiles - Add pre-commit hooks that scan for secret patterns
- Use vibeGuard in your development workflow to catch leaks before they reach production
The Vibe Coding Connection
AI assistants are amazing at generating functional code quickly. But they don't inherently know your security boundaries. When you ask an AI to "connect to the Stripe API," it might put the secret key wherever it needs to go to make things work — including client-side code.
This isn't the AI's fault. It's optimizing for functionality, not security. That's your job.
The vibe coding workflow should include a security check as naturally as it includes a deployment step. Ship fast, then scan. It takes 10 seconds.
A Quick Checklist
Before every deployment, ask yourself:
- [ ] Are any
NEXT_PUBLIC_/VITE_variables actually secrets? - [ ] Do any client components import server-side modules?
- [ ] Are source maps disabled in production?
- [ ] Have I scanned the running app with vibeGuard?
- [ ] Are all API calls with secrets proxied through my backend?
Your app deserves to be fast and secure. Don't let a leaked key be the thing that ruins your launch.