Frontend Secret Leak Prevention Checklist
Modern frontend frameworks use environment variable prefixes to determine which values are bundled into client-side code. A single misconfigured variable can expose API keys, database credentials, or webhook secrets to every visitor. This checklist covers how to prevent and detect such leaks.
1. Understand Framework Prefixes
Each framework has a prefix that marks environment variables as client-safe:
| Framework | Client Prefix | Server-only |
|-----------|---------------|-------------|
| Next.js | NEXT_PUBLIC_ | All others |
| Vite | VITE_ | All others |
| Create React App | REACT_APP_ | All others |
| Nuxt | NUXT_PUBLIC_ | All others |
| SvelteKit | PUBLIC_ | All others |
Any variable with the client prefix is embedded in the JavaScript bundle and visible to anyone who views the page source. Only use these prefixes for values that are truly public (Supabase anon key, Firebase API key, Stripe publishable key).
2. Audit Current Variables
Review every environment variable in your project:
# List all environment variables referenced in code
grep -rn "process\.env\." src/ app/ --include="*.ts" --include="*.tsx" --include="*.js"
grep -rn "import\.meta\.env\." src/ --include="*.ts" --include="*.tsx"
For each variable with a public prefix, ask: "Would I be comfortable if this value appeared on the front page of Hacker News?" If not, it should not have the public prefix.
Common mistakes:
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY— The service role key must NEVER be publicNEXT_PUBLIC_STRIPE_SECRET_KEY— Only the publishable key should be publicVITE_DATABASE_URL— Database connection strings are always server-onlyNEXT_PUBLIC_RESEND_API_KEY— Email API keys must stay server-side
3. Search Built Bundles
Even if your source code looks clean, verify the build output:
# Build the production bundle
npm run build
# Search for secret patterns in the output
grep -rn "sk_live\|sk_test\|service_role\|supabase_service" .next/ dist/ build/ out/
grep -rn "-----BEGIN.*KEY-----" .next/ dist/ build/ out/
grep -rn "postgres://\|mysql://\|mongodb://" .next/ dist/ build/ out/
Automate this as a CI step that fails the build if any secret patterns are detected in client bundles.
4. Use Server-Side API Routes
When you need to call an API that requires a secret key, create a server-side API route as a proxy:
// app/api/send-email/route.ts (Next.js)
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { to, subject, body } = await request.json();
// RESEND_API_KEY is server-only (no NEXT_PUBLIC_ prefix)
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ from: 'noreply@yourapp.com', to, subject, html: body }),
});
if (!response.ok) {
return NextResponse.json({ error: 'Failed to send email' }, { status: 500 });
}
return NextResponse.json({ success: true });
}
The client calls /api/send-email and never sees the Resend API key.
5. Source Map Security
Production source maps can reveal your entire source code, including comments that might reference secrets or internal architecture:
- Do not deploy source maps to production unless they are served only to your error monitoring service (e.g., Sentry).
- If you must deploy source maps, configure your CDN or server to restrict access to them (e.g., require a specific header).
- Verify:
curl https://yourapp.com/static/js/main.js.mapshould return 404 or 403 in production.
6. Environment Variable Documentation
Create a .env.example file that documents every variable, its purpose, and whether it is safe for the client:
# .env.example
# Public (safe for client bundle)
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
# Server-only (NEVER add NEXT_PUBLIC_ prefix)
SUPABASE_SERVICE_ROLE_KEY=eyJ...
STRIPE_SECRET_KEY=sk_live_...
RESEND_API_KEY=re_...
7. Content Security Policy
Deploy a Content Security Policy (CSP) header to restrict what scripts can run and where data can be sent:
Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self' https://xxx.supabase.co;
CSP prevents injected scripts from exfiltrating data and limits the damage if a third-party script is compromised. Review your CSP after adding any new third-party service.
8. Pre-Commit Secret Detection
Install gitleaks as a pre-commit hook to catch secrets before they reach version control:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
Run gitleaks detect in CI to scan the entire Git history for previously committed secrets. If found, rotate those secrets immediately regardless of whether the repository is private.
Use AuditYour.app to automatically detect exposed secrets and misconfigurations in your frontend application.
Scan your app for this vulnerability
AuditYourApp automatically detects security misconfigurations in Supabase and Firebase projects. Get actionable remediation in minutes.
Run Free ScanRelated
checklists
API Key Management Checklist
Checklist for proper API key handling and rotation
checklists
Pre-Launch Security Checklist
Security checklist before deploying BaaS applications to production
checklists
Supabase Security Checklist
Comprehensive security checklist for Supabase projects
checklists
Firebase Security Checklist
Comprehensive security checklist for Firebase projects