WHERE VIBE CODERS LEAK THEIR KEYS: 2026 FRONTEND SECRETS REPORT
Stripe, Supabase service-role, OpenAI, and AWS keys are the modal secrets that ship inside AI-built app frontend bundles. This catalog ranks what leaks, where it hides in the bundle, which platform's defaults make it likely, and how to fix it — each shape reproducible against a live gapbench scenario.
We load AI-built apps as a normal browser would and run 100+ key signatures against everything the browser fetches. The catalog below is what consistently leaks, where it hides in the bundle, and which platform’s defaults push builders into each shape. Every leak shape is reproducible against a live deliberately leaky scenario on the gapbench public benchmark.
Catalog scope
| Field | Value |
|---|---|
| Window | Nov 2025 – Apr 2026 |
| Source | Anonymized customer engagements + gapbench reproducible scenarios |
| Signature set | 100+ regular expressions for known key formats + high-entropy heuristic |
| Calibration control | ref0 — ships zero secrets |
| Reproducibility anchor | indie-saas (Stripe), supabase-clone (service-role JWT), agent-app (OpenAI), sentry-dsn-leak, config-leak |
We do not publish a corpus-wide leak rate because the underlying engagement set is anonymized and not a uniform random sample. Every leak shape below is reproducible in seconds against the listed gapbench scenario.
What is leaking, by key type
Ranked by relative frequency of occurrence in engagements and reproducibility on gapbench. Severity floor is the lower bound — actual impact depends on the key’s permissions.
| Rank | Key type | Severity floor | Reproducible on |
|---|---|---|---|
| 1 | Stripe secret key (sk_live_, sk_test_, rk_live_) |
Critical (full charge/refund) | indie-saas |
| 2 | Supabase service-role JWT | Critical (RLS bypass) | supabase-clone |
| 3 | OpenAI API key (sk-proj-, sk-) |
High (billing abuse) | agent-app |
| 4 | AWS access key (AKIA*, ASIA*) |
Critical (depends on IAM) | — |
| 5 | Google / Firebase service key (AIza*) |
High (depends on rules) | — |
| 6 | Anthropic API key (sk-ant-) |
High (billing abuse) | agent-app |
| 7 | SendGrid / Mailgun / Resend keys | High (email abuse) | — |
| 8 | Twilio auth tokens | High (SMS billing) | — |
| 9 | GitHub personal access tokens | Critical (repo access) | — |
| 10 | Generic high-entropy strings (likely secrets) | Triage required | — |
Stripe secret keys lead because the pk_* / sk_* distinction is exactly where vibe-coding fails: builders paste both into the AI prompt because both are labeled “key”, and the AI does not consistently route the secret through a backend.
Where the keys are hiding
Ranked by relative frequency across engagements.
| Location in the bundle | Relative frequency |
|---|---|
| Inlined in JavaScript bundle (Webpack/Vite/esbuild output) | Most common |
Inlined in HTML head as window.__ENV or similar |
Common |
| Returned by an unauthenticated API call | Less common |
Inlined in .env.js or config.js shipped to client |
Less common |
| Visible in source maps that shipped to production | Uncommon but recurring |
Source maps are the easiest fix and the most embarrassing — production builds with source maps publish your full source tree to the public. Most build tools default to producing source maps; few default to excluding them from production deployment.
Per-platform modal leaked key
The class of secret that most often leaks per platform — driven by the platform’s default scaffolding and prompt patterns.
| Platform | Relative leak incidence | Modal leaked key |
|---|---|---|
| Bolt.new | Highest | Stripe secret key |
| Lovable | High | Supabase service-role JWT |
| Replit | High | AWS access key |
| Cursor | Moderate | OpenAI API key |
| V0 | Lower | Stripe secret key |
Bolt.new’s higher rate reflects the platform’s preference for frontend-only quick-prototype patterns. Cursor’s lower rate is partly an artifact — Cursor users are more likely to be technical, more likely to introduce a backend, and therefore less likely to ship a secret to the browser at all. Lovable’s modal leak is service-role keys because builders paste them into the prompt as “the key” without realizing Lovable’s workflow expects only the anon key.
CWE / OWASP mapping by leak shape
The CWE varies with how the key reached the browser. The fix shape varies the same way.
| Leak shape | CWE | OWASP | Fix shape |
|---|---|---|---|
| Inlined in JS bundle (build-time substitution) | CWE-798 Hard-coded Credentials | A02:2021 Cryptographic Failures · A05:2021 | Move to server-only env; route the integration through a backend handler |
Inlined via window.__ENV or <script> block |
CWE-200 Sensitive Info Exposure | A05:2021 Security Misconfiguration | Same as above; never inject secrets into the SSR’d HTML |
| Returned by an unauthenticated API call | CWE-522 Insufficiently Protected Credentials | A07:2021 Identification and Auth Failures | Gate the endpoint behind auth; return only what the client genuinely needs |
Inlined in .env.js / config.js shipped to client |
CWE-540 Inclusion of Sensitive Info in Source Code | A05:2021 Security Misconfiguration | Server-only config; do not ship config files to the browser |
| Visible in source maps shipped to production | CWE-538 Insertion of Sensitive Info into Externally-Accessible File | A05:2021 Security Misconfiguration | Disable source maps for production builds, or upload-only-to-Sentry |
| LLM-prompt-embedded secret reflected in output | CWE-200 / LLM07:2025 | OWASP LLM Top 10 — System Prompt Leakage | Never include live secrets in prompts; use signed short-lived tokens |
The LLM-prompt path is the newest leak shape and the one most under-tested. We see it when builders paste sk_live_ into a Lovable prompt as “the Stripe key”, the model embeds it in a code path that reflects the prompt content into a debug error — and the error surfaces in production.
Per-leak-shape fix patterns
The mechanical fix per shape is short. Most teams stop at rotation and skip the proxy step — that is what produces re-leaks two weeks later.
# Step 1, every shape: rotate the key in the provider dashboard.
# Stripe: sk_live_ → revoke, generate new restricted key
# Supabase: project settings → reset service-role JWT
# OpenAI: platform.openai.com/api-keys → revoke + regenerate
// Step 2 for "inlined in JS bundle": move the secret to a server-only env,
// route the integration through a backend handler.
// WRONG — Vite inlines anything prefixed VITE_ into the bundle.
const stripe = new Stripe(import.meta.env.VITE_STRIPE_SECRET_KEY);
// RIGHT — secret stays server-only, frontend hits your route.
// app/api/charge/route.ts (Next) or supabase/functions/charge (Edge Function)
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const session = await getSession(req);
if (!session) return new Response(null, { status: 401 });
// ... charge logic, returning only the public confirmation to the client.
}
// Step 2 for "returned by unauthenticated API call": auth-gate, then minimize.
// WRONG — public endpoint reflects the whole config object.
app.get('/api/config', (req, res) => res.json(serverConfig));
// RIGHT — return only what the client genuinely needs, post-auth.
app.get('/api/config', requireAuth, (req, res) => {
res.json({ stripe_publishable_key: serverConfig.STRIPE_PUBLISHABLE_KEY });
});
// Step 2 for "source maps in production": ship maps off-platform.
// vite.config.ts
export default defineConfig({
build: {
sourcemap: 'hidden', // generate but do not reference from production assets
},
});
// Upload the maps to Sentry / Datadog at deploy time; never to the public bucket.
For LLM-prompt leaks, the fix is a hard rule: secrets never enter the prompt context. If the model needs to act on behalf of a user, mint a short-lived signed token scoped to the action and pass that — not the upstream API key.
The damage envelope of a Stripe sk_live_ leak
A sk_live_ key in a frontend bundle gives an attacker the ability to refund every charge to a card they control. The damage envelope depends on the account’s recent volume and Stripe’s velocity flags:
- Refundable balance accessible. Stripe permits refunds for charges up to 180 days old; in practice the past 30–90 days of completed charges are the realistic attacker surface before velocity controls fire.
- Refund target. The attacker can refund to a card they control by attaching a new payment method to a refund destination.
- Velocity floors. Stripe’s account-level fraud controls flag unusual refund velocity, but the floor depends on baseline activity — low-volume accounts have a lower floor that triggers later than the attacker can drain.
The damage of a single key leak ranges from “a Sunday’s revenue” to “the last quarter’s revenue”, depending on Stripe’s velocity flags and how quickly rotation happens.
Methodology
Source. Leak shapes were identified across (a) anonymized customer engagements with apps built on Lovable, Bolt.new, Cursor, Replit, and V0 between Nov 2025 and Apr 2026, and (b) deliberately leaky scenarios on the gapbench public benchmark. We do not publish a corpus N because the engagement portion is anonymized by design.
Probe. Each app is loaded as a normal browser. Every fetched resource — initial HTML, JavaScript bundles, dynamic imports, source maps if served — is parsed and matched against 100+ regular expressions for known key formats, plus a high-entropy heuristic for likely-secret strings of unknown format.
Validation. Stripe and Supabase keys are validated structurally (format and checksum where present); we do not attempt any authenticated request against the provider, so a positive detection identifies keys that look valid by format and were reachable, not keys confirmed to be active.
De-duplication. Multiple occurrences of the same key in the same bundle count once. Multiple distinct keys of the same type in the same app count separately.
Limits. This catalog covers keys present in the static or runtime-loaded bundle visible to a normal browser. It does not cover keys exposed only after authentication, keys exposed via debug endpoints triggered by query parameters, or keys exposed by side channels (server logs, error responses on edge cases).
Calibration against ref0. Every signature is also run against ref0 — a clean reference site that ships zero secrets. Any signature that fires on ref0 is a false positive and the rule is killed. The 100+ signatures in production are net of false-positive elimination; every claim in this catalog reflects signatures that distinguished ref0 from a deliberately leaky scenario.
Reproduce on the public benchmark
Each leak shape maps to a live scenario on gapbench.vibe-eval.com. The scenarios are running; the curl commands return real, deliberate-by-design leaks today.
| Scenario | URL | What leaks |
|---|---|---|
| Indie SaaS | /site/indie-saas/ | Stripe sk_live_ inlined in bundle |
| Supabase clone | /site/supabase-clone/ | Supabase service-role JWT in window.__ENV |
| Agent app | /site/agent-app/ | OpenAI / Anthropic API keys via VITE_* env |
| Sentry DSN leak | /site/sentry-dsn-leak/ | Sentry DSN with abusable scope |
| Config leak | /site/config-leak/ | Centralized config.js shipped to client |
| Source-map leak | /site/source-maps-and-git-exposed/ | Production .js.map and .git/ exposed |
| ref0 (clean control) | /site/ref0/ | Nothing — the false-positive reference |
For the anatomy of how Supabase service-role keys end up in frontend bundles and how the fix looks per generator, see the companion pattern walkthrough: The Supabase service-role key in your frontend bundle and Source maps and .git in production.
How to reproduce a single data point
- Open the Token Leak Checker.
- Paste your live URL.
- Read the report. Any key marked critical or high must be rotated immediately, then proxied behind a backend before re-deploy.
Sources and references
- gapbench secret-leak scenarios. indie-saas (Stripe sk_live_), supabase-clone (Supabase service-role JWT), agent-app (OpenAI/Anthropic), sentry-dsn-leak, config-leak, source-maps-and-git-exposed, ref0 (clean control).
- Companion honeypot study. Honeypot Supabase — twenty deliberately leaked Supabase anon keys, seven exposure surfaces, 11-minute median time-to-abuse.
- CWE-798, CWE-200, CWE-522, CWE-540, CWE-538. cwe.mitre.org.
- OWASP A02:2021 Cryptographic Failures, A05:2021 Security Misconfiguration, A07:2021 Identification and Auth Failures. owasp.org/Top10.
- OWASP LLM Top 10 (2025) LLM07 System Prompt Leakage for prompt-embedded secret exposure.
- Stripe key reference. stripe.com/docs/keys — distinction between publishable and secret keys, restricted-key best practice.
Citations
VibeEval. Where Vibe Coders Leak Their Keys: 2026 Frontend Secrets Report. May 2026. https://vibe-eval.com/data-studies/frontend-secrets-leak-report-2026/
Related
- Pattern walkthrough: The Supabase service-role key in your frontend bundle
- Pattern walkthrough: Source maps and .git in production — Next.js leaks you didn’t know you shipped
- Pattern walkthrough: Stripe trust on the wrong side — webhook signatures skipped, paid-flag tampering
- Data study: 2026 AI App Security Benchmark
- Data study: Supabase RLS in the Wild — 2026 Misconfiguration Atlas
- Data study: Honeypot Supabase: How Long Does a Public Anon Key Survive?
- Tool: Free Token Leak Checker
RUN IT YOURSELF
Each scenario below is live on the public benchmark. The commands are copy-paste ready. Outputs may evolve as we tune the scenarios; the bug stays.
curl -s https://gapbench.vibe-eval.com/site/indie-saas/ | grep -oE 'sk_(live|test)_[A-Za-z0-9]{20,}'
curl -s https://gapbench.vibe-eval.com/site/supabase-clone/ | grep -oE 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' | head -1
curl -s https://gapbench.vibe-eval.com/site/agent-app/ | grep -oE 'sk-(proj-)?[A-Za-z0-9_-]{40,}'
curl -s https://gapbench.vibe-eval.com/site/sentry-dsn-leak/ | grep -oE 'https://[a-f0-9]+@[a-z0-9.-]+/[0-9]+'
curl -s https://gapbench.vibe-eval.com/site/ref0/ | grep -oE 'sk_(live|test)_[A-Za-z0-9]{20,}|eyJ[A-Za-z0-9_-]+'
COMMON QUESTIONS
SCAN YOUR BUNDLE FOR LEAKED KEYS
The free Token Leak Checker runs 100+ key signatures against your live JavaScript bundle in 15 seconds.