Quick Answer
Hardcoded API keys are credentials (Stripe, OpenAI, AWS, etc.) written directly into source code instead of loaded from environment variables. AI coding tools like Bolt, Lovable, and Cursor frequently embed keys in code to make things work quickly. Once pushed to GitHub, these secrets are exposed to anyone - and bots scan for them within minutes.
How Big Is the Hardcoded Secrets Problem?
In 2024, GitHub reported that developers used its secret scanning to detect more than 39 million secret leaks. That's API keys, database passwords, and private tokens - sitting in plain text. GitGuardian's 2024 State of Secrets Sprawl report found that 11.7% of code authors on public GitHub leaked a secret in 2023.
For vibe-coded apps built with Bolt, Lovable, Cursor, or v0, the problem is worse. When you prompt "add Stripe payments" or "connect to OpenAI," the AI generates working code the fastest way it knows how - by putting the key right in the source file. It works. It deploys. And your secret key is now in your Git history forever.
According to Veracode's State of Software Security 2023, credentials management flaws appear in 46.7% of applications scanned with static analysis.
What AI Tools Actually Generate
Here's a realistic example of what Bolt might produce when you ask it to "add OpenAI chat to my app":
// ❌ BAD - Hardcoded API Keys
import OpenAI from 'openai';
import Stripe from 'stripe';
import { S3Client } from '@aws-sdk/client-s3';
const openai = new OpenAI({
apiKey: 'sk-proj-abc123def456ghi789jkl012mno345pqr678'
});
const stripe = new Stripe(
'sk_live_51ABC123DEF456GHI789JKL012MNO345PQR678STU',
{ apiVersion: '2024-06-20' }
);
// AWS credentials
const s3Client = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
}
});
Every one of these keys is a live credential with real billing attached. An exposed Stripe secret key lets attackers process charges. An exposed OpenAI key racks up usage costs. An exposed AWS key can spin up servers or access S3 buckets.
Why Removing the Key From Code Isn't Enough
Here's the part most people miss: deleting the key from your code and pushing a new commit does not remove it. Git stores every version of every file. Anyone can run git log -p and find your old key in the commit history.
Even if you removed the key months ago, it's still findable, so scan your whole history once, for example with gitleaks git (see our guide to secrets in Git history). VibeDoctor's scan runs Gitleaks on the files of the commit it scans, not on past commits, so a deleted key will not show up in its report.
If a secret was ever committed:
- Rotate the key immediately - generate a new one in the provider's dashboard and revoke the old key
- Move to environment variables - use
.envfiles locally and platform secrets in production - Add
.envto.gitignore- prevent future commits of secret files - Consider rewriting history - use
git filter-repoor BFG Repo Cleaner to remove the secret from all commits
The Correct Way to Handle Secrets
// ✅ GOOD - Environment Variables
import OpenAI from 'openai';
import Stripe from 'stripe';
import { S3Client } from '@aws-sdk/client-s3';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-06-20'
});
// AWS - use IAM roles in production, env vars in dev
const s3Client = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
Then create a .env file (never committed to Git) and a .env.example (committed, with placeholder values):
# .env.example - commit this to Git
OPENAI_API_KEY=sk-your-key-here
STRIPE_SECRET_KEY=sk_live_your-key-here
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
Where Secrets Hide: The Full Checklist
| Secret Type | Pattern to Search | Common in |
|---|---|---|
| AWS Access Keys | AKIA followed by 16 chars |
Bolt, Cursor |
| Stripe Live Keys | sk_live_ prefix |
Bolt, Lovable |
| OpenAI Keys | sk- or sk-proj- prefix |
All AI tools |
| GitHub Tokens | ghp_ or gho_ prefix |
Cursor, v0 |
| Supabase Service Keys | eyJhbG... (legacy JWT format) or sb_secret_ prefix |
Bolt, Lovable |
| Database URLs | postgresql:// or mongodb+srv:// |
All tools |
| SendGrid Keys | SG. prefix |
Cursor, Bolt |
| Private Keys | -----BEGIN RSA PRIVATE KEY----- |
All tools |
How to Scan Your Code for Hardcoded Secrets
Manual searching is unreliable - secrets come in many formats and can be buried deep in your codebase or Git history. Here's what works:
- Automated scanning: VibeDoctor's Vibe Check runs Gitleaks plus its own hardcoded-secret checks on every codebase scan and flags the exact file and line of each secret. It checks the files in the scanned commit, not past commits, so run
gitleaks giton your history once as well. Free to sign up. - GitHub Secret Scanning: If you're on a public repo, GitHub's built-in secret scanning can alert you. But it only works on public repos for free accounts.
- Pre-commit hooks: Install Gitleaks as a pre-commit hook to prevent secrets from being committed in the first place.
- Use platform secrets: Vercel, Netlify, Railway, and Render all support environment variables in their dashboard. Never rely on
.envfiles in production.
NEXT_PUBLIC_ and VITE_ - The Sneaky Frontend Exposure
This one is easy to miss in vibe-coded apps. When AI tools build Next.js or Vite apps, they sometimes prefix secret keys with NEXT_PUBLIC_ or VITE_ to make them "work" on the client side. This means the key is bundled into your JavaScript and visible to anyone who opens browser DevTools.
Never put secret keys in client-side env vars. Supabase anon keys are designed to be public (with Row Level Security). Stripe publishable keys (pk_live_) are fine on the client. But secret keys, service role keys, and API keys with write access must stay server-side only.
FAQ
What if I already pushed a secret key to GitHub?
Rotate the key immediately in the provider's dashboard. Then remove it from your code, add it to .env, and use git filter-repo or BFG Repo Cleaner to scrub it from history. Simply deleting and re-committing is not enough.
Are Supabase anon keys safe to expose?
The Supabase anon key is designed to be public - it respects Row Level Security (RLS) policies. But the service_role key bypasses RLS entirely and must NEVER be exposed on the client or committed to Git.
Does Bolt or Lovable hardcode secrets?
It can happen. When you provide API keys in the prompt or paste them into the editor, AI tools embed them directly in source code. Always replace hardcoded keys with process.env references before deploying.
How fast do bots find exposed keys on GitHub?
Automated scanners find exposed AWS keys within minutes of a public commit. Palo Alto Networks' Unit 42 tracked an attack campaign that detected and used exposed AWS IAM credentials within five minutes of their appearing on GitHub, then launched cryptomining instances. AWS credential exposure has led to six-figure cloud bills.
Which tool finds the most secret types?
Gitleaks (used by VibeDoctor's scanning pipeline) detects 100+ secret patterns including AWS, Stripe, OpenAI, GitHub, Slack, SendGrid, Firebase, and more. Run as gitleaks git, it scans your full Git history, not just the current code; VibeDoctor runs it on the files of each scanned commit.