Claude Code + Vite: secret keys end up in the client bundle during POCs — suggest a built-in guardrail
Context
I'm a solo founder. I was building a POC (dating app: Firebase + React + Flutter) with Claude Code. Speed over rigor — I wasn't reviewing security choices carefully, which is kind of the point of a POC.
What happened
Claude Code helped me wire up a Gemini API key into a Vite app via VITE_GEMINI_API_KEY in .env, consumed as import.meta.env.VITE_GEMINI_API_KEY. That's the standard Vite pattern and it "just works".
The problem: VITE_* env vars are inlined into the client bundle, which is publicly accessible. Within days bots scraped the bundle, extracted the key, and ran up ~€800 of Google Cloud charges before I noticed and rotated it.
This is partly on me — I also misconfigured GCP: I thought I'd set a spending limit, but it was actually just a billing alert. Two layers of "I assumed this was safe" stacked on top of each other.
A similar VITE_OPENAI_API_KEY leak happened on the same project (caught later, no exploitation that I'm aware of).
Why I'm filing this
I'm not pointing fingers — the assistant did what I asked and the pattern I used is everywhere on the internet. But this is exactly the kind of mistake where an assistant could save a non-senior dev a lot of money, especially in POC/solo-founder mode where nobody is reviewing.
Expected behavior
Claude Code should actively push back when the user tries to place a secret-shaped API key behind any client-bundled env var:
- Vite: \
VITE_*\ - CRA: \
REACT_APP_*\ - Next.js: \
NEXT_PUBLIC_*\ - Flutter compile-time \
--dart-define\holding a secret
…and proactively propose the safe alternatives:
- Proxy the call through a backend route (Cloud Functions / Express / API route)
- Ephemeral tokens for realtime use cases (Gemini Live, OpenAI Realtime, WebRTC)
- Only accept client-side env vars for public-by-design keys (Firebase Web API key, reCAPTCHA site key, Stripe \
pk_*\, Maps key with HTTP referrer restriction)
Ideally paired with a nudge on billing: "by the way, have you set a real spending cap — not just an alert — on the provider?"
Suggestion
A built-in guardrail: when Claude is about to generate code that places a value matching common secret prefixes (\sk-\, \sk-ant-\, \sk-proj-\, \AIza...\ outside a Firebase Web SDK context, etc.) into a client-bundled namespace, it should refuse and propose the backend route instead — even when the user is going fast.
Environment
- Claude Code CLI
- Vite 8 + React 19 + Flutter 3.41
- Solo developer / POC mode
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗