Secrets in Frontend Bundles
API keys in React env vars are public: what’s safe in the browser, map keys, and how leaks happen.
- security
- secrets
- api-keys
- bundling
- env
Anything shipped to the browser is public. REACT_APP_*, VITE_*, NEXT_PUBLIC_* values are embedded in JS bundles. Treating them as server secrets is a perennial incident report.
Docs: OWASP Secrets Management, Next.js env.
The failure
// Build injects into client bundle
const stripeSecret = import.meta.env.VITE_STRIPE_SECRET_KEY;
Anyone opens DevTools → Sources → searches sk_live → your billing is gone.
Classification
| Kind | Browser OK? | Pattern |
|---|---|---|
| Publishable Stripe/key-like | Yes, with domain restrictions | Rate limit + restrict referrers |
| Secret API keys | No | Server only |
| DB passwords, private keys | No | Server / KMS |
| Session tokens | Prefer HttpOnly cookie | Not in bundle |
| Feature flags (non-secret) | Yes | Expect manipulation |
Map & SaaS client keys
Google Maps, Firebase web config, Algolia search-only keys can live in the client if locked down:
- HTTP referrer / domain restrictions
- Least privilege (search-only, not admin)
- Quotas and billing alerts
- Rotate on leak
A “publishable” key is still abuse-able for quota theft — restrict it.
How secrets leak into FE accidentally
# .env
SECRET_KEY=...
NEXT_PUBLIC_SECRET_KEY=... # oops
- Copy-paste into client config
- Source maps uploaded publicly
- Error monitoring sending env dumps
- Storybook builds including
.env
Correct architecture
Browser → your BFF/API (holds secret) → third-party
// browser
await fetch('/api/maps/session', { method: 'POST' });
// server attaches secret, returns short-lived client capability if needed
Scanning
gitleaks/trufflehogin CI- Bundle grep for
sk_live,BEGIN PRIVATE KEY - Rotate immediately on hit
Interview out-loud
“Client bundles are public. Only publishable, tightly scoped keys belong there. Real secrets stay on the server behind my API. I lock down referrer-restricted keys, watch quotas, and scan repos and builds for accidents.”
Reviewer prompts
- What is the asset (session, PII, money movement)?
- What is the attacker capability (web, XSS, network, dependency)?
- Which control fails closed if misconfigured?
- Is the server still enforcing authz?
- Any new third party, iframe, or URL sink?
Residual risk note
Browser controls reduce likelihood and impact; they do not eliminate bugs. Prefer defense in depth: safe defaults in code, strict headers, and monitoring (CSP reports, auth anomaly alerts). When product pressure weakens a control, write down the accepted risk and a revisit date.
Related
- Dependency supply chain risk
- JWT storage pitfalls
- Frontend security threat model
- Subresource integrity
Further depth
Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.
Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.
Further reading
Related guides
- Auth Session UX SecuritySession UX that doesn’t weaken security: login states, logout everywhere, idle timeouts, and step-up auth.
- Clickjacking and X-Frame-OptionsStop UI redress attacks with frame-ancestors CSP and X-Frame-Options; know when embedding is intentional.
- Content Security Policy (CSP)Reduce XSS blast radius with CSP — script-src nonces/hashes, strict-dynamic, report-only rollout, and what CSP does not fix.
- CSRF Basics for SPAsCross-site request forgery against cookie sessions: SameSite, CSRF tokens, and SPA fetch patterns.
- Dependency Supply Chain Risknpm malware, lockfiles, pin policies, and practical frontend controls against dependency attacks.