How to Fix Exposed API Keys in 5 Minutes
Found a leaked API key in your frontend code? Here's exactly how to rotate it, move it server-side, and prevent it from happening again - in under 5 minutes.
By Daniel A · Kraftwire Software
· 8 min readYou Found an Exposed Key. Now What?
You just ran a security scan (or worse, got an alert from your cloud provider) and discovered an API key sitting in your frontend JavaScript. This happens to more developers than you might think. The good news is that you can contain the damage and fix the problem in under 5 minutes if you act quickly.
Here is exactly what to do, step by step.
Step 1: Revoke the Exposed Key Immediately (30 seconds)
Before doing anything else, invalidate the compromised key. Every second it stays active is a second an attacker can use it. Do not waste time removing it from your code first. Go straight to the service provider and revoke it.
| Service | Where to Revoke |
|---------|----------------|
| OpenAI | platform.openai.com, then API Keys, then Delete |
| Stripe | dashboard.stripe.com, then Developers, then API Keys, then Roll Key |
| Supabase | Project Settings, then API, then Regenerate service-role key |
| AWS | IAM Console, then Users, then Security Credentials, then Deactivate |
| Firebase | Firebase Console, then Project Settings, then Regenerate |
| SendGrid | Settings, then API Keys, then Delete and create new |
| Twilio | Console, then API Keys, then Delete |
**Important: Do not just delete the key from your code.** If it was ever in a public bundle, it has been cached by CDNs, stored in browser caches, and potentially scraped by automated bots. Removing it from source code does not remove it from the internet. You must revoke it at the source so it stops working entirely.
**Why speed matters:** Automated bots scan the internet continuously for exposed API keys. Research from GitGuardian shows that exposed keys are discovered and exploited within minutes, not hours or days. The faster you revoke, the less damage can be done.
Step 2: Check for Damage (1 minute)
Once the key is revoked, quickly check if it was already exploited. Each service has its own way to check for unauthorized usage:
**OpenAI:**
Go to the Usage dashboard and look for unexpected spikes
Check if usage exceeds what your application should generate
Look for usage from time periods when your app was not active
Typical unauthorized usage: $50-500 per hour of abuse
**Stripe:**
Review recent charges for any you do not recognize
Check for unexpected refunds (attackers sometimes refund to their own cards)
Look at customer data exports and API call logs
Review connected accounts for unauthorized changes
**Supabase:**
Check database logs for unusual queries, especially SELECT * from sensitive tables
Look for new records created that your app did not generate
Review auth logs for new user accounts you did not create
Check if RLS policies were modified
**AWS:**
Review CloudTrail for unauthorized API calls
Check for new EC2 instances, Lambda functions, or S3 buckets
Look for IAM changes (new users, modified policies)
Review billing for unexpected charges
**If you find unauthorized activity:**
Change passwords for all admin accounts immediately
Enable MFA on every account if not already enabled
Contact the service's security team to report the incident
Document everything for potential compliance reporting
Consider whether user data was accessed and whether you need to notify affected users
Step 3: Move the Secret Server-Side (2 minutes)
Now that the immediate danger is handled, fix the root cause. Create a server-side function that proxies the API call. The secret stays on the server and never reaches the browser.
**For Supabase/Lovable apps**, create an Edge Function:
// supabase/functions/proxy-openai/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
serve(async (req) => {
const { message } = await req.json();
// Secret stays server-side
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("OPENAI_API_KEY")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: message }],
}),
});
return new Response(JSON.stringify(await response.json()));
});
**For Next.js apps**, use an API route:
// app/api/chat/route.ts
export async function POST(request: Request) {
const { message } = await request.json();
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: message }],
}),
});
return Response.json(await response.json());
}
Then call it from your frontend with no secrets exposed:
// Frontend - no secrets here
const response = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ message: userInput }),
});
Step 4: Add the Secret as an Environment Variable (30 seconds)
Store the new key in your platform's secret management system. This is different for each platform:
**Supabase/Lovable:** Use the Secrets dashboard to add the key. It will be available in Edge Functions as `Deno.env.get("KEY_NAME")`.
**Vercel:** Go to Project Settings, then Environment Variables. Add the key for Production, Preview, and Development as needed.
**Netlify:** Go to Site Settings, then Environment Variables.
**Replit:** Use the Secrets panel (lock icon in the sidebar).
**Critical rule: Never prefix secrets with `VITE_`, `NEXT_PUBLIC_`, or `REACT_APP_`.** These prefixes tell the build system to include the value in your frontend bundle, which is exactly what caused the problem in the first place. Only use these prefixes for values that are truly meant to be public, like your Supabase anon key or Stripe publishable key.
Step 5: Prevent It from Happening Again (1 minute)
The best defense against future key exposure is catching secrets before they get committed. Add a pre-commit hook that scans for common key patterns:
**Using a simple grep check:**
# .git/hooks/pre-commit
#!/bin/sh
if git diff --cached | grep -E "(sk-[a-zA-Z0-9]{20,}|sk_live_|service_role|PRIVATE_KEY)" > /dev/null; then
echo "ERROR: Possible secret detected in staged files!"
echo "Please remove the secret and use environment variables."
exit 1
fi
**Using dedicated tools:**
**git-secrets** (AWS): Scans for AWS-specific credential patterns
**truffleHog**: Scans git history for high-entropy strings and known key patterns
**GitLeaks**: Comprehensive secret detection with custom rules
**Additional prevention measures:**
Set up billing alerts on all API providers so you are notified of unusual charges
Use API key restrictions (IP allowlists, domain restrictions) where the provider supports them
Implement key rotation schedules (every 90 days for production keys)
Use separate keys for development and production environments
The Cheat Sheet
| Step | Action | Time |
|------|--------|------|
| 1 | Revoke the key at the provider | 30s |
| 2 | Check usage logs for unauthorized activity | 60s |
| 3 | Create server-side proxy function | 120s |
| 4 | Store new key as environment variable | 30s |
| 5 | Add pre-commit hook for secret detection | 60s |
| **Total** | | **~5 min** |
Common Mistakes During Remediation
**Mistake 1: Only removing the key from the current code.** If the key was ever committed to git, it exists in your git history forever. Anyone who clones your repo can find it. You must revoke the key, not just delete it from the code.
**Mistake 2: Replacing with a new key in the same location.** If the original key was in frontend code, putting the new key in the same place just creates the same vulnerability with a fresh key. Always move the key to a server-side location.
**Mistake 3: Not checking for damage.** Many developers revoke the key and move on without checking if it was used maliciously. This can mean missing fraudulent charges, data breaches, or unauthorized access that continues through other means.
**Mistake 4: Not rotating related credentials.** If an attacker had your OpenAI key, they may have also accessed your app in other ways. Consider rotating all secrets, not just the one you found exposed.
Do Not Wait for a Breach
The best time to fix an exposed key is before an attacker finds it. Run a free SimplyScan scan right now to check if your app is leaking any secrets. It takes 30 seconds and checks your entire frontend bundle, network requests, and common file paths.
[Scan your app now](/)</a>
Related Guides
[Why Exposed API Keys Are Dangerous](/blog/api-keys-in-frontend)
[Environment Variables Security](/blog/environment-variables-security)
[Supabase Security Checklist](/blog/supabase-security-checklist)
[Cursor Security Checklist](/blog/cursor-security-checklist)
[Bolt.new Security Guide](/blog/bolt-new-security-guide)
[Is Lovable Safe?](/blog/is-lovable-safe)