WHERE VIBE CODERS LEAK THEIR KEYS: 2026 FRONTEND SECRETS REPORT

Forty-one percent of AI-built apps in our 2026 corpus ship at least one secret key in their frontend bundle. Stripe, Supabase service-role, OpenAI, AWS — what is leaking, where, and on which platforms.

We loaded 1,514 AI-built apps as a normal browser would and ran 100+ key signatures against everything the browser fetched. Six hundred and fourteen of them — 41% — shipped at least one secret key. This is the breakdown by key type, platform, and modal location.

Headline numbers

Metric Value
Apps scanned 1,514
Apps with at least one leaked secret 614
Leak rate 41%
Median leaked keys per affected app 1
Apps leaking 3 or more keys 87
Window Nov 2025 – Apr 2026

What is leaking, by key type

Rank Key type Apps affected Share of leaking apps Severity floor
1 Stripe secret key (sk_live_, sk_test_) 134 22% Critical (full charge/refund)
2 Supabase service-role JWT 119 19% Critical (RLS bypass)
3 OpenAI API key (sk-proj-, sk-) 102 17% High (billing abuse)
4 AWS access key (AKIA*, ASIA*) 78 13% Critical (depends on IAM)
5 Google / Firebase service key (AIza*) 71 12% High (depends on rules)
6 Anthropic API key (sk-ant-) 58 9% High (billing abuse)
7 SendGrid / Mailgun / Resend keys 41 7% High (email abuse)
8 Twilio auth tokens 28 5% High (SMS billing)
9 GitHub personal access tokens 22 4% Critical (repo access)
10 Generic high-entropy strings (likely secrets) 117 19% Triage required

Stripe secret keys are the headline — 134 apps shipping a sk_live_ or sk_test_ to every visitor. The publishable / secret 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

Location in the bundle Share of findings
Inlined in JavaScript bundle (Webpack/Vite/esbuild output) 64%
Inlined in HTML head as window.__ENV or similar 18%
Returned by an unauthenticated API call 9%
Inlined in .env.js or config.js shipped to client 6%
Visible in source maps that shipped to production 3%

The 3% in source maps is 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 leak rate

Platform Apps in sample Leak rate Modal leaked key
Lovable 612 47% Supabase service-role JWT
Bolt.new 318 51% Stripe secret key
Cursor 246 32% OpenAI API key
Replit 201 38% AWS access key
V0 137 24% 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 24-hour cost of a Stripe sk_live_ leak

For the 134 apps leaking a Stripe secret key, we modeled the financial exposure assuming the key would be discovered within the industry-standard 5-15 minute window:

  • Median apps’ Stripe daily volume. $440 (estimated from public payment-form metadata)
  • Median refundable balance accessible. Last 30 days of charges, ~$13,200
  • Median attacker-controllable refund window. 90 days from charge, full balance

A sk_live_ key in a frontend bundle gives an attacker the ability to refund every charge to a card they control. The financial damage of a single key leak ranges from “a Sunday’s revenue” to “the last quarter’s revenue”, depending on Stripe’s velocity flags.

Methodology

Sample. All 1,514 apps scanned between Nov 2025 and Apr 2026. The Token Leak Checker probe was applied to each.

Probe. Each app was loaded as a normal browser. Every fetched resource — initial HTML, JavaScript bundles, dynamic imports, source maps if served — was 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 were validated structurally (format and checksum where present); we did not attempt any authenticated request against the provider, so the “leak rate” measures keys that look valid by format and were reachable, not keys we confirmed are 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 study measures keys present in the static or runtime-loaded bundle visible to a normal browser. It does not measure 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 probe 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; the per-key counts in the table above reflect only 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.

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. Our own honeypot data is ongoing — see the Honeypot Supabase study for the live measurement.
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 count. The 41% in the headline is 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 honeypot study measured a median of 11 minutes from exposure to first malicious request — fastest 47 seconds for a public GitHub commit. Frontend bundle leaks were a 6-minute median. The window between 'we shipped' and 'someone abuses it' is shorter than most teams' rotation playbook. See the Honeypot Supabase study for the full distribution.
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