NEXT_PUBLIC_ Secret Exposure
Auth Safety · AUTH-05 · Priority: P0
Why It Matters
In Next.js, any environment variable prefixed with NEXT_PUBLIC_ is bundled into the client-side JavaScript and visible to every user. When AI tools prefix secret keys — Stripe secret keys, Supabase service_role keys, API secrets — with NEXT_PUBLIC_, those secrets become publicly accessible in the browser bundle.
Escape.tech's scan of 5,600 vibe-coded apps found 400+ exposed secrets, with hardcoded API keys in JavaScript files being one of the most common patterns. Wiz Research flagged "exposed secrets — API keys hardcoded in JavaScript files" as one of four primary vibe-coding security pitfalls.
Priority: P0 — Exposed secrets grant attackers direct access to your backend services, database, and payment provider.
Affected Stack: Next.js (App Router and Pages Router)
The Problem
AI code generators don't distinguish between "safe to expose" and "must stay server-only" environment variables. They prefix everything with NEXT_PUBLIC_ because it's the simplest way to make variables accessible in React components — and the code works.
# ❌ What AI tools typically generate in .env
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # ✅ This one is OK
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJ... # ❌ Server-only!
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_... # ❌ Server-only!
NEXT_PUBLIC_RESEND_API_KEY=re_... # ❌ Server-only!
These variables are compiled into the JavaScript bundle at build time. Even if you later remove them from .env, they remain in any previously deployed build artifacts.
What's Safe vs. What's Not
| Variable | Safe for NEXT_PUBLIC_? |
Why |
|---|---|---|
| Supabase URL | ✅ Yes | Public by design |
| Supabase anon key | ✅ Yes | Public by design (RLS enforces access) |
| Supabase service_role key | ❌ No | Bypasses all RLS |
Stripe publishable key (pk_) |
✅ Yes | Public by design |
Stripe secret key (sk_) |
❌ No | Full API access |
| Any API secret / token | ❌ No | Server-only |
| Webhook secrets | ❌ No | Server-only |
The Fix
Remove the NEXT_PUBLIC_ prefix from any server-only secret. Access these variables only in server-side code (API routes, Server Actions, server components).
# ✅ Production-safe .env
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
# Server-only — no NEXT_PUBLIC_ prefix
SUPABASE_SERVICE_ROLE_KEY=eyJ...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
RESEND_API_KEY=re_...
// ✅ Server-only access
// app/api/admin/route.ts
const serviceRole = process.env.SUPABASE_SERVICE_ROLE_KEY; // Only available server-side
After fixing: Rotate any key that was previously exposed with NEXT_PUBLIC_. The old key may have been cached in browser bundles, CDN caches, or build artifacts.
References
- Next.js: Environment Variables
- OWASP: Security Misconfiguration (A05:2021)
- CWE-200: Exposure of Sensitive Information
Related Checks
- service_role Key Exposure — AUTH-01, ADM-17
- Stripe Secret Key Exposure — BIL-01
- Server-Side Auth for Protected Routes — AUTH-04
Is your app safe? Run Free Scan →