ASAASA Standard
Active Phase 1Production Foundation

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


Related Checks


Is your app safe? Run Free Scan →