General13 items

Frontend Secret Leak Prevention Checklist

Prevent secrets from leaking into client bundles

Last updated 2026-01-15

Quick Checklist

  • 1Audit all environment variables for public prefix usage
  • 2Verify server-side secrets are not in NEXT_PUBLIC_ or VITE_ prefixed vars
  • 3Search built bundles for secret patterns
  • 4Use .env.example to document safe vs. secret variables
  • 5Configure build-time checks for leaked secrets
  • 6Separate client and server environment variable files
  • 7Review source maps for exposed secrets in production
  • 8Audit third-party scripts for data exfiltration
  • 9Never log secrets in browser console statements
  • 10Use server-side API routes as a proxy for secret-dependent calls
  • 11Implement Content Security Policy headers
  • 12Review Git history for previously committed secrets
  • 13Test with production-like builds to verify variable scoping

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 public
  • NEXT_PUBLIC_STRIPE_SECRET_KEY — Only the publishable key should be public
  • VITE_DATABASE_URL — Database connection strings are always server-only
  • NEXT_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.map should 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 Scan