NEXT_PUBLIC Is Not a Vault: Frontend Environment Variables Are Not Secrets
A myth-busting audit for Next.js, Vite, source maps, and build artifacts: how to detect and remediate credentials accidentally shipped to browsers.
An API key lives in a .env file, the repository is private, and nothing obvious appears in page source. That means the key is protected—right?
Not necessarily.
As soon as a value is used by client-side code, it has to be delivered to the browser. Whether it originally came from .env, a secret manager, or a CI variable does not change that. Minification, bundled files, and unreadable variable names make a value inconvenient to inspect, not confidential.
The central rule is:
Anything the browser needs to execute can also be inspected by the user.
Myth 1: “It is in .env, so it is secret”
A .env file is a configuration mechanism. It does not automatically decide whether a value remains server-only.
Next.js inlines values prefixed with NEXT_PUBLIC_ into JavaScript sent to the browser at build time. Vite does the same by default for variables beginning with VITE_. Both projects explicitly warn against storing sensitive values there.
The prefix does not mean “publicly available but somehow protected.” It means: this value is allowed to become part of the public client bundle.
Appropriate public values may include:
- an analytics site identifier,
- a public Sentry DSN,
- a feature flag with no authorization effect,
- a browser-facing map or payment identifier whose security model explicitly permits public use,
- an API base URL without credentials.
Inappropriate values include:
- database passwords,
- private API keys,
- administrative tokens,
- service-role keys,
- SMTP credentials,
- webhook secrets,
- private signing keys,
- tokens with write, billing, or export permissions.
Myth 2: “The value is minified, so it is effectively invisible”
Minification reduces transfer size. It is not access control.
A user can:
- download JavaScript bundles,
- search strings,
- inspect requests and headers in developer tools,
- observe runtime values through breakpoints or the console,
- use source maps when they are publicly available,
- scan automatically for known token patterns.
Source maps can reconstruct the relationship between transformed or minified code and its original source. They are valuable for debugging, but publication should be deliberate: publicly accessible, access-controlled, or uploaded privately to an error-tracking service. The security rule does not depend on source maps, though. A secret must not enter the client bundle even when no map is exposed.
Myth 3: “The provider calls it a public key, so it cannot cause harm”
Some browser-facing keys are intentionally public. That does not make them harmless without restrictions.
Assess the actual capabilities:
- Does the key merely identify a public client?
- Is it restricted to approved domains, origins, or applications?
- Is every sensitive action authorized on the server?
- Can an attacker use it to generate cost?
- Does it permit reading, writing, or exporting data?
- Is rate limiting enabled?
- Can the credential be rotated independently?
A publishable payment key may be correct when secret operations remain server-side. A so-called public key that can access customer data or administrative functions is not public configuration; it is a broken authorization design.
Six real-world red flags
1. A secret was simply renamed
STRIPE_SECRET_KEY becomes NEXT_PUBLIC_STRIPE_SECRET_KEY because the browser otherwise cannot access it. That does not solve the integration. It ships the secret directly to every visitor.
2. Client components import server configuration
In full-stack frameworks, files can move across server and client boundaries surprisingly easily. A helper used only on the server yesterday may be imported by a client component tomorrow. Builds should enforce that boundary, and reviews should make such changes visible.
3. Nobody scans the build artifact
Secret scanning commonly focuses on the repository. A value may only be injected from CI into the bundle during the production build. The actual generated artifact therefore needs inspection too.
4. Preview deployments use production credentials
A pull request creates a publicly reachable preview built with production API keys. Even when the preview is deleted later, bundles, logs, or caches may already have been copied.
5. Old bundles remain available through the CDN
After a credential is rotated, a new deployment is released, but older hashed assets remain accessible. That is normally desirable for non-sensitive configuration. During a real leak, however, it proves that redeployment does not retroactively remove exposure.
6. The team “deletes” the secret but does not rotate it
GitHub recommends revoking or rotating an exposed credential first. Removing it from code, repository history, or build output is not enough because somebody may already have copied it.
A practical frontend-secret audit
1. Inventory public prefixes and replacements
Search for:
NEXT_PUBLIC_,VITE_,- custom
envPrefixvalues, - build replacements configured through
define, - generated runtime configuration files,
- globals such as
window.__CONFIG__, - inline configuration scripts in HTML.
Every value needs a documented reason why public exposure is acceptable.
2. Inspect client build output
Build the application exactly as production does. Then scan JavaScript, CSS, HTML, manifests, and source maps for:
- known secret names,
- token prefixes,
- account or domain identifiers,
- private endpoints,
- long Base64 or hexadecimal values,
- test and production credentials.
Repository scanning alone cannot detect every case because CI may inject the value only during the build.
3. Inspect network traffic
Open the website in a clean browser profile and review:
- request headers,
- query parameters,
- WebSocket connections,
- GraphQL payloads,
- bootstrap configuration,
- error reports and logs.
A value does not have to be embedded in a static bundle to be public. Once the browser receives or transmits it, the user can inspect it.
4. Evaluate capabilities, not labels
A variable named publicKey proves nothing. Review the provider's actual permissions, restrictions, quotas, and rotation options.
5. Enforce server boundaries
Sensitive operations belong behind a server-side route or function. The browser sends the necessary user input to your server; the server validates authentication, authorization, and input before using the secret.
6. Harden build and deployment
A reliable process can include:
- repository secret scanning and push protection,
- artifact scanning after the production build,
- separate preview and production credentials,
- short-lived or least-privilege tokens,
- no secrets in build logs,
- controlled source-map publication,
- documented rotation,
- tests that reject forbidden prefixes or values in client bundles.
What to do when a secret was already public
Do not treat this as a cosmetic code cleanup.
- Revoke or rotate the credential immediately.
- Review permissions and provider logs: Was it used? Were there unexpected requests, exports, or charges?
- Correct the client/server architecture.
- Issue replacement credentials with minimal permissions.
- Identify bundles, previews, artifacts, and caches containing the old value.
- Clean repository history where necessary—but only after rotation.
- Add an automated regression check.
- Document the incident and root cause.
For a production credential, order matters. Neutralize it first; clean up afterwards.
What Website-Pflichtencheck would inspect
A technical review should look beyond .env and .gitignore. It can examine:
- public environment prefixes and framework configuration,
- client/server boundaries in Next.js, Vite, and similar stacks,
- JavaScript bundles actually served to visitors,
- publicly accessible source maps,
- preview deployments and build artifacts,
- network requests and bootstrap configuration,
- permissions and restrictions of browser-facing keys,
- secret scanning, push protection, and rotation,
- caches retaining old artifacts,
- secure server-side replacement architecture.
The goal is not to warn against environment variables in general. It is to enforce a clear distinction:
Public configuration may exist in the browser. Secrets must never arrive there.
Teams that judge this boundary by filenames or prefixes are trusting labels. A reliable audit checks what the browser actually receives and what each exposed value can actually do.