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

  1. Open the Token Leak Checker.
  2. Paste your live URL.
  3. 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/

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.

Stripe sk_live_ in static bundle
curl -s https://gapbench.vibe-eval.com/site/indie-saas/ | grep -oE 'sk_(live|test)_[A-Za-z0-9]{20,}'
expected A Stripe secret key embedded inline — full charge / refund authority
Supabase service-role JWT in window.__ENV
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
expected A JWT whose payload decodes to role: service_role
OpenAI key in JS bundle
curl -s https://gapbench.vibe-eval.com/site/agent-app/ | grep -oE 'sk-(proj-)?[A-Za-z0-9_-]{40,}'
expected OpenAI API key inlined via VITE_OPENAI_API_KEY style env
Sentry DSN with project-write scope
curl -s https://gapbench.vibe-eval.com/site/sentry-dsn-leak/ | grep -oE 'https://[a-f0-9]+@[a-z0-9.-]+/[0-9]+'
expected Sentry DSN — write-only by design but spam-friendly without project-side rate limit
Clean control — ref0 has no secrets
curl -s https://gapbench.vibe-eval.com/site/ref0/ | grep -oE 'sk_(live|test)_[A-Za-z0-9]{20,}|eyJ[A-Za-z0-9_-]+'
expected Empty — the clean reference site ships no secrets

COMMON QUESTIONS

01
What counts as a leaked secret in this study?
Any key that grants write, billable, or privileged access and is reachable by a normal browser visiting the public URL. That includes Stripe secret keys (sk_live_, sk_test_, rk_live_), Supabase service-role keys, OpenAI and Anthropic API keys, AWS access keys, and 90+ other formats with abuse paths. We exclude publishable keys (pk_live_, the Supabase anon key) from the leak count because they are designed to ship — but we track them separately because they create exposure when paired with weak authorization.
Q&A
02
Why are AI-generated apps especially prone to secret leaks?
Three reasons. First, AI builders tend to handle integrations end-to-end in client code rather than introducing a backend proxy — there is no natural place to hide the secret. Second, AI generators frequently use environment variables that are exposed via build-time inlining (NEXT_PUBLIC_*, VITE_*) without warning the builder that the variable will be public. Third, builders without backend experience often paste secret keys directly into the AI prompt, which embeds them in the generated code.
Q&A
03
How fast is a leaked key abused?
Public bundles are continuously scraped by credential-harvesting bots indexing GitHub, npm, and deployed sites. Industry studies put time-to-first-abuse for high-value keys (AWS, Stripe) at single-digit minutes. The companion Honeypot Supabase study reproduces the same finding for Supabase anon keys deliberately leaked across seven exposure surfaces.
Q&A
04
Does obfuscation help?
No. Minification and obfuscation do not hide keys from automated scanners. Stripe's secret key prefix sk_live_ is matched on substring; Supabase JWTs decode trivially; AWS keys have a fixed format. Every commercial scanner — and most free ones, including ours — trivially defeats common obfuscation.
Q&A
05
What about server-side secrets that happen to be visible in network responses?
We classify those as separate findings (verbose error responses, debug endpoints, etc.) and do not include them in the bundle-leak catalog. This report is specifically about keys actually present in the JavaScript that ships to every visitor.
Q&A
06
How do you fix a leaked key?
Rotate immediately in the provider dashboard. Then introduce a backend proxy or edge function so the key never reaches the browser. Then rebuild and redeploy the frontend. Then audit logs from that key for anomalies — public exposure even for minutes is enough to assume it has been scraped. Rotation without proxying is incomplete; you will leak the new key the same way.
Q&A
07
What CWE numbers map to a frontend secret leak?
CWE-798 (Use of Hard-coded Credentials) is the primary mapping when the key is literally inlined. CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) applies when the key reaches the browser via any channel — bundle, source map, unauthenticated API. CWE-522 (Insufficiently Protected Credentials) covers the systemic case. OWASP A02:2021 (Cryptographic Failures) and A05:2021 (Security Misconfiguration) are the OWASP web mappings; A07:2023 in the LLM Top 10 covers prompt-leaked secrets in AI-feature stacks.
Q&A
08
Where can I see a leaked key on a real URL right now?
https://gapbench.vibe-eval.com/site/indie-saas/ ships a Stripe sk_live_ in the bundle. https://gapbench.vibe-eval.com/site/supabase-clone/ ships a Supabase service-role JWT. https://gapbench.vibe-eval.com/site/sentry-dsn-leak/ exposes a Sentry DSN. https://gapbench.vibe-eval.com/site/config-leak/ centralizes a misconfigured config.js. ref0 is the clean control — same shape, no secrets — for false-positive calibration.
Q&A
09
How fast does a leaked key actually get abused in the wild?
Our companion honeypot study (twenty deliberately leaked Supabase anon keys, seven exposure surfaces) measured a median of 11 minutes from exposure to first malicious request — fastest 47 seconds for a public GitHub commit, 6-minute median for frontend bundle leaks. The window between 'we shipped' and 'someone abuses it' is shorter than most teams' rotation playbook. See the Honeypot Supabase study for the per-surface breakdown.
Q&A

SCAN YOUR BUNDLE FOR LEAKED KEYS

The free Token Leak Checker runs 100+ key signatures against your live JavaScript bundle in 15 seconds.

RUN TOKEN CHECKER