# No service-role or secret keys exposed to the public bundle

- **Pattern:** `ab-002568` (`project-snapshot.security.no-service-role-in-public-bundle`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-25
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002568
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002568)

## Why it matters

A variable name like `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY` is a direct contradiction in terms — the `NEXT_PUBLIC_` prefix tells the bundler to inline the value into every client bundle, and a service-role key grants RLS-bypassing database access. Shipping one to the browser gives any visitor full admin privileges against the database, including reading other users' rows, writing arbitrary data, and deleting tables. This is a documented pattern in the Supabase + Lovable ecosystem where service-role keys prefixed with `NEXT_PUBLIC_*` get inlined into every JavaScript bundle sent to browsers, where anyone can extract them via devtools. It shows up constantly in AI-generated Supabase starters because the tool sees `SUPABASE_` env vars in docs and reaches for the public prefix out of pattern-matching habit, not understanding the access-tier distinction between anon and service-role keys. The variable name alone is enough signal to flag — intent doesn't matter when the prefix dictates build-time behavior.

## Severity rationale

Critical because the naming pattern guarantees client-bundle exposure on every deploy, and a leaked service-role key produces immediate full database compromise accessible from any visitor's devtools.

## Remediation

Rename to drop the public prefix, treat the credential as compromised, rotate it. For Supabase: use the anon key client-side (already public-safe) and service-role only in server code.

Deeper remediation guidance and cross-reference coverage for this check lives in the `security-hardening` Pro audit — run that after applying this fix for a more exhaustive pass on the same topic.

## Detection

- **ID:** `no-service-role-in-public-bundle`
- **Severity:** `critical`
- **What to look for:** Grep for env var names combining a public prefix with a sensitive substring: `NEXT_PUBLIC_*SECRET*`, `NEXT_PUBLIC_*SERVICE_ROLE*`, `NEXT_PUBLIC_*PRIVATE*`, `NEXT_PUBLIC_*ADMIN*`, `VITE_*SECRET*`, `VITE_*SERVICE_ROLE*`, `PUBLIC_*SECRET*`. Count matches across `.env.example`, `.env.sample`, source, config.
- **Pass criteria:** Zero matches.
- **Fail criteria:** Any match — public prefix + sensitive substring is a red flag regardless of intent. Common offender: `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY` shipping to the client bundle.
- **Skip (N/A) when:** No client-side framework detected.
- **Report even on pass:** `"Scanned env declarations + source: 0 public-prefixed sensitive matches"`.
- **Detail on fail:** `"NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY in .env.example — service role key ships to the client bundle"`.
- **Remediation:** Drop the public prefix, rotate the credential, treat as compromised. For Supabase: anon key client-side, service role only in server code.

Taxons: project-snapshot, cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-002568
