One of the most common security issues we find when scanning websites isn't a sophisticated vulnerability — it's plain-text API keys sitting in publicly accessible JavaScript bundles. It happens more often than you'd think, and the consequences can be severe.
How API Keys End Up in Your Frontend
Hardcoded in Source Code
The most obvious leak: a developer puts an API key directly in a JavaScript file, environment variables get bundled by webpack or Vite, and suddenly your secret key is visible to anyone who opens DevTools.
// This ends up in your production bundle
const stripe = new Stripe('sk_live_abc123...');
const supabase = createClient(url, 'eyJhbGciOiJIUzI1NiIs...');
Environment variables prefixed with NEXT_PUBLIC_, VITE_, or REACT_APP_ are intentionally included in client-side bundles. But developers sometimes put secret keys there by mistake.
Visible in Network Requests
Even if keys aren't in the source code, they might be sent in HTTP headers or URL parameters that anyone can see in the Network tab:
- Authorization headers with service-role keys
- API endpoints with keys as query parameters
- Webhook URLs containing authentication tokens
Committed to Git History
A key that was in the codebase for even one commit lives in git history forever — unless you rewrite history. Public GitHub repositories are constantly scanned by bots looking for AWS keys, database credentials, and payment processor secrets.
Leaked Through Error Messages
Stack traces, debug output, and verbose error messages can expose configuration details including API keys, database connection strings, and internal URLs.
What Attackers Do With Leaked Keys
The impact depends on the key type and permissions:
- Stripe secret keys — create charges, issue refunds, access customer data
- Supabase service_role keys — bypass Row Level Security, read/write all data
- AWS keys — spin up resources (crypto mining), access S3 buckets, delete infrastructure
- Firebase admin keys — read/write entire database, manage users
- SendGrid/Resend keys — send emails as your domain (phishing campaigns)
- GitHub tokens — access private repositories, push malicious code
In many cases, attackers have automated systems that exploit leaked keys within minutes of exposure.
Detection Techniques
Pattern Matching
Most API keys follow recognizable patterns:
| Provider | Pattern |
|----------|---------|
| Stripe | sk_live_, sk_test_, rk_live_ |
| Supabase | eyJhbGciOiJIUzI1NiIs... (JWT) |
| AWS | AKIA[A-Z0-9]{16} |
| GitHub | ghp_, gho_, ghs_, ghu_ |
| Twilio | SK[a-z0-9]{32} |
| SendGrid | SG.[a-zA-Z0-9_-]{22}.[a-zA-Z0-9_-]{43} |
CheckVibe's API key scanner matches against 50+ known key patterns across all major providers.
Entropy Analysis
Not all secrets follow known patterns. High-entropy strings (random-looking character sequences) in source code or configuration files often indicate secrets. Entropy analysis flags strings that are statistically likely to be keys or tokens.
Network Traffic Analysis
Scanning HTTP requests and responses for authentication tokens, bearer headers, and keys passed as parameters catches secrets that don't appear in static source code.
Scanning Git History for Leaked Secrets
Removing a key from your current code is not enough. If it was ever committed -- even for a single commit, even on a branch that was later deleted -- it lives in your git history and can be extracted. Automated tools make scanning your full repository history straightforward.
TruffleHog
TruffleHog scans your entire git history for high-entropy strings and known secret patterns. It checks every commit, every branch, and every diff.
# Install TruffleHog
brew install trufflehog # macOS
# or
pip install trufflehog # Python
# Scan a local repository (all history)
trufflehog git file://. --only-verified
# Scan a GitHub repo directly
trufflehog github --repo https://github.com/your-org/your-repo --only-verified
# Scan with JSON output for CI integration
trufflehog git file://. --only-verified --json > trufflehog-results.json
The --only-verified flag is important: TruffleHog actually tests detected credentials against the provider's API to confirm they are active. This dramatically reduces false positives.
Gitleaks
Gitleaks is a fast, purpose-built tool for finding secrets in git repositories. It uses a TOML-based rule configuration that is easy to extend with custom patterns.
# Install Gitleaks
brew install gitleaks # macOS
# or download from https://github.com/gitleaks/gitleaks/releases
# Scan the entire git history
gitleaks detect --source . -v
# Scan only staged changes (for pre-commit use)
gitleaks protect --staged -v
# Generate a JSON report
gitleaks detect --source . --report-format json --report-path gitleaks-report.json
# Scan with a custom config
gitleaks detect --source . --config .gitleaks.toml
A sample custom .gitleaks.toml configuration to catch your own key formats:
[extend]
# Extend the default rules
useDefault = true
[[rules]]
id = "custom-internal-api-key"
description = "Internal API key"
regex = '''cvd_live_[a-f0-9]{32}'''
tags = ["key", "custom"]
[[rules]]
id = "supabase-service-role"
description = "Supabase service role key"
regex = '''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[A-Za-z0-9_-]{50,}'''
tags = ["key", "supabase"]
git log Inspection
For quick manual checks, you can use git itself to search history:
# Search all commits for a specific string
git log -p --all -S 'sk_live_' -- '*.ts' '*.js' '*.env'
# Search for AWS key patterns across all history
git log -p --all -G 'AKIA[A-Z0-9]{16}'
# List all files that ever contained a pattern
git log --all --diff-filter=A --name-only -S 'SUPABASE_SERVICE_ROLE_KEY'
Setting Up Pre-Commit Hooks
The best time to catch a leaked secret is before it enters your repository at all. Pre-commit hooks run automatically before every git commit and can block commits that contain secrets.
Using Gitleaks as a Pre-Commit Hook
# Install the pre-commit framework
pip install pre-commit
# Or on macOS
brew install pre-commit
Create a .pre-commit-config.yaml in your repository root:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaks
Then install the hooks:
pre-commit install
# Test it by trying to commit a file with a fake secret
echo "STRIPE_KEY=sk_live_test123456789012345678" > test-secret.txt
git add test-secret.txt
git commit -m "test"
# -> Gitleaks should block this commit
rm test-secret.txt
Using Husky for Node.js Projects
If your project already uses Husky for git hooks, add secret scanning there:
# Install Gitleaks globally
brew install gitleaks
# Add a pre-commit hook via Husky
npx husky add .husky/pre-commit "gitleaks protect --staged -v"
Now every commit is scanned before it is created. The --staged flag ensures only the files being committed are checked, keeping the hook fast.
Bypassing Hooks in Emergencies
If you need to bypass the hook for a legitimate reason (e.g., committing a test fixture with a known false positive), you can use:
git commit --no-verify -m "commit message"
But use this sparingly. A better approach is to add a .gitleaksignore file for known false positives:
# .gitleaksignore
# Test fixtures with fake keys
tests/fixtures/fake-stripe-key.json:sk_test_
GitHub Secret Scanning Setup
GitHub provides built-in secret scanning for repositories, and it is available for free on all public repositories and for GitHub Advanced Security subscribers on private repositories.
Enabling Secret Scanning
- Go to your repository Settings > Code security and analysis
- Enable Secret scanning
- Optionally enable Push protection to block pushes that contain detected secrets
Push Protection
Push protection is the most valuable feature. When enabled, GitHub scans every push before it is accepted and rejects any push that contains a recognized secret pattern. The developer sees an error message explaining which secret was detected and where.
remote: -------- Secret scanning push protection --------
remote: Found the following secret in commit abc1234:
remote:
remote: Stripe API Key: sk_live_51Hx...
remote: Location: src/lib/payments.ts:15
remote:
remote: To push, remove the secret from your commits.
Custom Secret Patterns
For private repositories with GitHub Advanced Security, you can define custom patterns to catch organization-specific secrets:
- Go to Settings > Code security and analysis > Secret scanning > Custom patterns
- Add your pattern (e.g., your internal API key format
cvd_live_[a-f0-9]{32}) - GitHub will scan for this pattern across your entire repository
Secret Scanning Alerts
When a secret is detected in existing code, GitHub creates an alert in the Security > Secret scanning alerts tab. Each alert shows:
- The secret type and provider
- The commit and file where it was found
- Whether the secret is still active (for supported providers)
- Remediation steps
For supported providers (including AWS, Stripe, GitHub, and others), GitHub automatically notifies the provider so they can revoke the key -- but you should not rely on this. Always rotate the key yourself immediately.
Secret Rotation Procedures for Major Providers
When a key leaks, you need to rotate it immediately. Here are step-by-step procedures for the most commonly leaked secrets.
Stripe
# 1. Go to https://dashboard.stripe.com/apikeys
# 2. Click "Roll key" next to the compromised key
# 3. You get a grace period where both old and new keys work
# 4. Update your environment variables with the new key
# 5. Deploy the change
# 6. Click "Revoke old key" in the Stripe dashboard
Stripe's rolling key feature is particularly useful: the old key continues to work for a configurable period (up to 24 hours) so you can deploy without downtime.
AWS
# 1. Create a new access key
aws iam create-access-key --user-name your-iam-user
# 2. Update your environment variables / secrets manager
# 3. Deploy the change
# 4. Verify the new key works
# 5. Deactivate the old key (keeps it for rollback)
aws iam update-access-key --user-name your-iam-user \
--access-key-id AKIA_OLD_KEY_HERE --status Inactive
# 6. After confirming everything works, delete it
aws iam delete-access-key --user-name your-iam-user \
--access-key-id AKIA_OLD_KEY_HERE
Important: After revoking AWS keys, audit CloudTrail logs for unauthorized activity during the exposure window. Check for new IAM users, EC2 instances, or Lambda functions that an attacker may have created.
Supabase Service Role Key
The Supabase service role key cannot be rotated independently -- it is derived from the project's JWT secret. To rotate it:
- Go to Project Settings > API in the Supabase dashboard
- Generate a new JWT secret (this invalidates both the anon key and service role key)
- Update all environment variables that reference either key
- Redeploy all services (Next.js app, Edge Functions, etc.)
Warning: This logs out all users because it invalidates all existing JWTs. Plan for a brief disruption.
GitHub Personal Access Tokens
# 1. Go to https://github.com/settings/tokens
# 2. Click "Delete" on the compromised token
# 3. Generate a new token with the same scopes
# 4. Update your CI/CD secrets and local environment
Review your repository's Settings > Security > Audit log for any unauthorized actions taken with the compromised token.
SendGrid / Resend
- Go to your provider's API key management page
- Delete the compromised key immediately (no grace period)
- Create a new key with the minimum required scopes
- Update your environment variables and deploy
- Check your email sending logs for any unauthorized emails sent during the exposure window
Best Practices for Key Management
Use environment variables — never hardcode keys. Keep them in .env files that are gitignored.
Separate public and secret keys — understand which keys are safe for the client (Supabase anon key, Stripe publishable key) and which must stay server-side.
Use server-side API routes — proxy sensitive API calls through your backend so secret keys never reach the browser.
Rotate keys regularly — if a key leaks, you want the exposure window to be as small as possible. Rotate quarterly at minimum.
Enable key restrictions — most providers let you restrict keys by IP, domain, or API scope. Use these restrictions.
Scan continuously — run automated scans after every deployment. A key that wasn't there yesterday might be there today.
Use a secrets manager for production. Environment variables on your local machine and in CI/CD are fine for development, but production secrets should live in a dedicated secrets manager (Vercel Environment Variables, AWS Secrets Manager, HashiCorp Vault, or Doppler). These provide audit trails, automatic rotation, and access control that plain .env files cannot. For a comprehensive API security approach, see our API security checklist.
Apply the principle of least privilege. When creating API keys, request only the scopes your application actually needs. A key that can read and write everything is far more dangerous when leaked than one that can only read a specific resource. Most providers (Stripe, GitHub, AWS IAM) support granular permissions -- use them. Our SaaS security checklist covers this and other pre-launch essentials.
Monitor key usage for anomalies. Most API providers offer usage dashboards. Set up alerts for unusual patterns: spikes in API calls, requests from unexpected IP addresses, or usage outside business hours. These are early indicators of a compromised key.
How CheckVibe Detects Leaked Keys
CheckVibe scans multiple surfaces simultaneously:
- Page source — HTML source code and inline scripts
- JavaScript bundles — fetches and parses all loaded JS files
- Network requests — monitors API calls for exposed tokens
- Meta tags and configs — checks for keys in page metadata and configuration objects
- GitHub integration — scans your repository for committed secrets
Each detected key is classified by provider, severity, and exposure type, with specific instructions for rotating and securing the key.
For a broader look at securing applications built with AI coding tools, see our guide on how to secure a vibe-coded app.
FAQ
How quickly do attackers find leaked keys?
Remarkably fast. Research and real-world incidents consistently show that leaked credentials on public GitHub repositories are discovered and exploited within minutes. Automated bots continuously scan GitHub's public event stream (which broadcasts every push in real time) for patterns matching AWS keys, Stripe secrets, database credentials, and more. In a well-known experiment, an AWS key pushed to a public repository was used to spin up crypto-mining EC2 instances within four minutes. GitHub's own data shows that secrets are typically exploited within an hour of being committed. This is why prevention (pre-commit hooks and push protection) is far more effective than detection after the fact -- by the time you get an alert, the key may already be compromised.
Can I undo a leaked key?
You cannot undo the leak itself, but you can limit the damage. The moment you discover a leaked key, you must rotate it immediately -- revoke the old key and generate a new one. Do not wait to investigate first; revoke first, then investigate. After revocation, check the provider's logs for any unauthorized activity during the exposure window. If the key was committed to a public git repository, remember that even after you remove it from the current code, it still exists in git history. You should consider the key permanently compromised regardless of whether you rewrite git history. The only effective response is rotation. For git history cleanup (to prevent future rediscovery), use git filter-repo to rewrite the repository history, but treat this as a secondary step after the key has been revoked.
How do I scan git history?
Use TruffleHog or Gitleaks as described in the "Scanning Git History" section above. Both tools scan every commit in your repository's history, not just the current state. For a quick start:
# TruffleHog -- scans all history, verifies credentials are active
trufflehog git file://. --only-verified
# Gitleaks -- scans all history with pattern matching
gitleaks detect --source . -v
For CI/CD integration, run these tools on every pull request to catch secrets before they reach your main branch. Both tools support JSON output for integration with alerting systems. If you only need to check whether a specific string was ever committed, use git log -p --all -S 'your-secret-string' -- but the dedicated tools are better for comprehensive scanning because they cover patterns you may not think to search for.
Are environment variables safe?
Environment variables are safer than hardcoded secrets, but they are not inherently secure. Their safety depends on where and how they are stored:
.envfiles on your machine -- safe as long as the file is gitignored and your machine is not compromised. Never commit.envfiles to version control.- CI/CD secrets (GitHub Actions, Vercel, etc.) -- safe for production use. These are encrypted at rest, access-controlled, and not included in build logs (most providers mask them automatically).
NEXT_PUBLIC_/VITE_/REACT_APP_variables -- NOT safe for secrets. These prefixes explicitly expose the variable to client-side code. Only use them for truly public values like your Supabase anon key or Stripe publishable key.- Docker environment variables -- visible via
docker inspect. Use Docker secrets or a secrets manager for sensitive values. - Server process environment -- accessible to any code running in the same process. A compromised dependency with access to
process.envcan exfiltrate all your secrets.
The safest approach is to use a dedicated secrets manager (Vercel Environment Variables, AWS Secrets Manager, Doppler) that provides encryption, access control, and audit logging. Treat environment variables as a transport mechanism, not a security boundary.
Don't let API keys be your weakest link. Scan your site with CheckVibe and find exposed secrets in seconds.