Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.
Why this matters
Bundlers inline these variables into the browser JavaScript at build time:
Next.js / webpack: NEXT_PUBLIC_*
Create React App: REACT_APP_*
Gatsby: GATSBY_*
SvelteKit / generic: PUBLIC_*
Vite: VITE_* (via import.meta.env)
Anything carried by such a variable ships to every visitor. A secret, token, password, or private key placed here is published, not protected.
Keep the secret in a server-only variable (no public prefix) and read it only in server code (API routes, server actions, loaders). If a value is genuinely safe to expose (publishable key, client_id), name it so.
// ok: auth.secret.public-env-secret// Server-only secret, no public prefix — never inlined into the client bundle.export const sessionSecret = process.env.SESSION_SECRET;// ok: auth.secret.public-env-secret// Server-only Stripe secret key, no public prefix.export const stripeSecret = process.env.STRIPE_SECRET_KEY;// ok: auth.secret.public-env-secret// Public prefix but no secret-ish suffix — a plain URL is fine to expose.export const apiUrl = process.env.NEXT_PUBLIC_API_URL;// ok: auth.secret.public-env-secret// Publishable key is designed to be public.export const publishable = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;// ok: auth.secret.public-env-secret// Vite public URL, not a secret.export const viteApiUrl = import.meta.env.VITE_API_URL;// ok: auth.secret.public-env-secret// OAuth client_id is a public identifier, not a secret.export const clientId = process.env.NEXT_PUBLIC_CLIENT_ID;// ok: auth.secret.public-env-secret// Public key half of a keypair is meant to be shared.export const pubKey = process.env.NEXT_PUBLIC_PUBLIC_KEY;// ok: auth.secret.public-env-secret// `API_KEY` / `TOKEN` under a public prefix are intentionally NOT flagged:// many services ship publishable client keys / public tokens this way// (Stripe publishable, Mapbox token, Algolia search key, Inkeep widget key).// Flagging these would be a false positive — precision over recall here.export const inkeepKey = process.env.NEXT_PUBLIC_INKEEP_API_KEY;export const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN;export const vitePublicKey = import.meta.env.VITE_PUBLIC_API_KEY;
Suppressing this rule
If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.