Vibe-Coded App Security in 2026: 170 Exposed Lovable Apps and 1.5M Keys
Every few weeks someone sends me a link to an app they built in an afternoon with Lovable, Bolt or Base44, and asks whether it is "safe to launch". The honest answer is usually that the front end is fine and the database is wide open. Vibe-coded app security in 2026 is not a story about exotic AI attacks. It is a story about the oldest mistake on the web, an unauthenticated read on a production table, now generated at a scale and speed that no security team ever had to deal with. This post walks through the documented incidents (CVE-2025-48757 in Lovable apps, Moltbook's 1.5 million leaked keys, the Base44 auth bypass), why almost all of them come down to Supabase Row Level Security and secrets in JavaScript bundles, what the scanners actually catch, and the checklist I would run before putting a vibe-coded app in front of real users.
What went wrong in documented vibe-coded app breaches?
These are the cases with a primary write-up behind them, not the screenshots that circulate on social media.
| Incident (date) | Platform | Root cause | What was exposed | Source |
|---|---|---|---|---|
| CVE-2025-48757 (reported Mar 2025, published May 29, 2025) | Lovable apps on Supabase | Missing or wrong RLS policies, queried directly with the public anon key | 170 of 1,645 scanned apps, 303 endpoints: names, phone numbers, payment details, third-party API keys | Matt Palmer's disclosure |
| Base44 auth bypass (reported Jul 9, 2025) | Base44 (Wix) platform | Undocumented registration and OTP endpoints needed only the public app_id |
Account creation on other people's private apps | The Hacker News on Wiz's report |
| Tea app (Jul 25, 2025) | Firebase storage, not confirmed AI-built | Publicly readable storage bucket | About 72,000 images including selfies and ID photos | Engadget |
| Escape scan (Oct 29, 2025) | Lovable, Create.xyz, Base44, Bolt, Vibe Studio | Keys in bundles, missing RLS, weak auth | 2,000+ vulnerabilities, 400+ secrets, 175 PII instances across 5,600 apps | Escape methodology |
| Moltbook (Jan 31, 2026) | Custom app on Supabase, AI-generated code | No RLS; publishable key in production JS bundle | 1.5M API tokens, about 35,000 emails, 4,060 private DM threads | Wiz Research |
Two notes on that table. First, Tea is often lumped into "vibe coding disasters", but nothing Tea published says the app was AI-generated. I include it because the failure shape is identical: a backend-as-a-service product with permissive defaults and nobody checking them. Second, the Lovable number is a floor. Palmer's team only tested the homepages of Lovable's public showcase, nothing behind a login.
Moltbook is the most instructive case because Wiz published the timeline. They reported the exposed agents table at 22:06 UTC on 31 January 2026. The first fix landed at 23:29. Then the researchers found they could still edit any post, then found three more exposed tables, one of them holding 29,000 email addresses. The final fix landed at 01:00. Four rounds of patching in three hours, because nobody had an inventory of what was exposed in the first place. That is an operations problem with AI-sized throughput.
Is the Supabase anon key safe to expose?
Yes, and this is where most of the confusion starts. Supabase's API keys documentation is explicit: the publishable key (sb_publishable_..., the successor to the legacy anon JWT) is meant to ship in browsers and mobile apps. It maps to the anon Postgres role when nobody is signed in and to authenticated when someone is. It is effectively a project identifier. Wiz put it the same way in the Moltbook write-up: the key is safe to expose when Row Level Security is configured.
The secret key (sb_secret_..., successor to service_role) is the opposite. It maps to the service_role Postgres role, which has the BYPASSRLS attribute, so no policy you write ever applies to it. Supabase now rejects secret keys sent from a browser with HTTP 401, matched on the User-Agent header, but the docs are clear that this is a seatbelt, not a lock: anyone who copies the key out of your bundle can use it from curl.
So the question is never "is my key visible". Your publishable key is visible by design. The question is: what can the anon and authenticated roles do to each table once someone has it? In a vibe-coded app, the honest answer is often "everything", because the generated code talks to the database directly from the browser and the only thing between the internet and your users table is a policy the model may or may not have written.
Supabase is also deprecating the legacy anon and service_role keys by the end of 2026. If a tutorial tells you to paste a long key starting with eyJ, it was written for the old system.
Why does AI-generated code keep skipping Row Level Security?
Three reasons, and only one of them is the model.
The first is architecture. Lovable, Bolt and similar tools generate single-page apps that call Supabase's Data API straight from the client. There is no server tier where an engineer would naturally put an authorisation check. All of the security lives in Postgres policies, and policies are invisible in the UI you are looking at while you prompt. The app works perfectly in the preview whether RLS is on or off, which means nothing in the feedback loop tells you it is missing.
The second is that models are mediocre at context-dependent security even when they try. Veracode's 2025 GenAI Code Security Report tested more than 100 LLMs on 80 tasks and found security flaws in 45% of the generated code, with cross-site scripting left unprotected in 86% of relevant cases. Authorisation logic is exactly the kind of thing that depends on context the model does not have: who should see this row, and under what condition.
The third is that "there is a policy" and "the policy is correct" are different claims. Palmer's disclosure criticised Lovable's early scanner for checking whether RLS policies existed rather than whether they actually blocked unauthorised access. A policy written as using (true) is technically present and completely useless. Supabase's own linter now has a dedicated check for that (rls_policy_always_true in Splinter), which tells you how common it got.
I build Riff, an open-source, self-hostable alternative to Lovable and v0, so I have an obvious interest here. The lesson I took from these incidents is that the builder has to treat authorisation as part of the generated artefact, tested like any other output, not as a setting the user is expected to discover later.
What do vibe-coding security scanners actually find?
The platforms have responded. Lovable now runs a Quick scan automatically when you publish, covering database access rules, dependencies and MCP servers, plus an optional Deep scan that looks at application code for access control gaps, unauthenticated endpoints, leaked secrets and exposed personal data. Whether critical findings block publishing depends on workspace settings; per the docs, blocking is on by default only for new Enterprise workspaces Lovable creates.
Supabase has moved too. Its 2025 security retro, published in January 2026, lists RLS on by default for tables created in the dashboard, email alerts when a table is created with RLS disabled, weekly security summaries to organisation owners, and automatic revocation of secret keys found in public GitHub repositories. The Security Advisor runs Splinter lints such as rls_disabled_in_public, rls_enabled_no_policy, sensitive_columns_exposed, security_definer_view and public_bucket_allows_listing.
The biggest change was due yesterday. Supabase's breaking change to Data API exposure means new tables in public no longer get automatic grants for anon, authenticated and service_role. It began rolling out as the default for new projects on 30 May 2026 and was scheduled to reach all existing projects on 30 October 2026. Existing tables keep their grants, so nothing already live changed, but a new table created by a migration or an AI tool now returns 42501 permission denied until someone grants access deliberately. That is a good default. It also means a lot of generated migrations will break, and the tempting fix ("grant everything to anon") recreates the original problem.
Here is what scanners are good and bad at, in my experience reading their output:
- Good at: tables with RLS off, policies with
true, secrets matching known formats in bundles, public buckets that allow listing, outdated dependencies. - Bad at: a policy that checks the wrong column, a policy that trusts
user_metadata(which users can edit themselves), an Edge Function that uses the secret key and never checks who called it, business logic like "users can mark their own invoice as paid".
That last one is not hypothetical. On the Lovable-built site that started the CVE-2025-48757 investigation, Palmer's team dropped the auth header and inserted a record with payment_status set to paid, bypassing Stripe. A rule-based linter like Splinter does not flag that, because the table had RLS; it was the wrong rule. Lovable's Deep scan now lists payments and billing as a category, but it has to infer intent from your code, and I would not bet a launch on it.
How do you test a vibe-coded app the way an attacker would?
You do what Wiz did: open the browser dev tools, find the Supabase URL and publishable key in the bundle, and ask the Data API for things you should not be able to see. Do it before anyone else does. Only run this against projects you own.
#!/usr/bin/env bash
# Pre-launch smoke test for a Supabase-backed app YOU own.
# Requires: curl, jq, Docker (for gitleaks v8.30.1). Run from the app repo root.
set -euo pipefail
SUPABASE_URL="https://YOUR_PROJECT_REF.supabase.co"
PUBLISHABLE_KEY="sb_publishable_xxx" # the key that ships in your bundle
TABLES=(profiles orders messages invoices) # list every table in public
# 1. Can an anonymous visitor read rows? Expect [] or a 401/42501 error.
for t in "${TABLES[@]}"; do
printf '%-12s ' "$t"
curl -s "$SUPABASE_URL/rest/v1/$t?select=*&limit=1" \
-H "apikey: $PUBLISHABLE_KEY" | jq -c '.' | cut -c1-120
done
# 2. Can an anonymous visitor write? Expect a 401/403 or RLS violation.
curl -s -o /dev/null -w 'anon insert into orders -> HTTP %{http_code}\n' \
-X POST "$SUPABASE_URL/rest/v1/orders" \
-H "apikey: $PUBLISHABLE_KEY" -H "Content-Type: application/json" \
-d '{"status":"paid"}'
# 3. Did a secret key or third-party token end up in the built bundle?
npm run build
grep -rEo 'sb_secret_[A-Za-z0-9_-]+|service_role' dist/ && echo 'SECRET IN BUNDLE' || echo 'no Supabase secret in dist/'
docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:v8.30.1 dir /repo/dist --no-banner
Any row of real data coming back from step 1 is a launch blocker. A 201 from step 2 is worse. Step 3 catches the other half of the problem: Escape found more than 400 exposed secrets, and they are not all Supabase keys. Palmer's team found Gemini, Google Maps and eBay tokens in the data they could read, because generated code tends to call third-party APIs straight from the browser. I wrote more about how long leaked keys stay valid in Secrets Sprawl 2026, and how to stop them reaching Git in the first place in the Gitleaks pre-commit guide.
Then test as a signed-in user. Create two accounts, sign in as the first, and try to read or update the second user's rows by changing an ID in the request. That single test catches most of the "policy exists but is wrong" cases that scanners miss.
A pre-launch security checklist for vibe-coded apps
This is the list I would want ticked before an app built with an AI builder takes signups or money. None of it is new. It is ordinary platform discipline, and it matters more when code arrives faster than anyone can read it.
- RLS on every table in an exposed schema. Run the Supabase Security Advisor and fix every
rls_disabled_in_publicandrls_enabled_no_policyfinding. The step-by-step Supabase RLS checklist has the SQL. - Policies scoped to a role and an owner. Every policy names
to authenticated(oranondeliberately) and compares a column to(select auth.uid()). Nousing (true)on anything with personal data. - No authorisation from
user_metadata. Users can edit it. Roles and plan tiers belong inapp_metadataor a table they cannot write to. - Grants are deliberate. Since 30 October, new tables need explicit grants. Grant the minimum each role needs, in the same migration that enables RLS.
- Secret keys never leave the server. Search the built bundle, not just the source. Third-party API calls (OpenAI, Stripe) go through an Edge Function or server route.
- Storage buckets reviewed. Private by default, per-user folder policies, no listing on public buckets.
- Two-account test. Sign in as user A, try to read and modify user B's data through the API directly.
- Business rules enforced server-side. Payment status, credits, roles and anything else worth forging is written only by a webhook or function holding the secret key.
If the app has real multi-tenancy (teams, organisations), the same ideas scale up, and my Postgres multi-tenant RLS guide covers the patterns.
FAQ
Is vibe coding safe for production apps?
It can be, but not by default. The generated front end is usually fine; the risk is in the backend configuration, especially database policies and where secrets live. Treat the output as a first draft from a fast junior developer and review authorisation before launch.
What is CVE-2025-48757?
It is the CVE assigned to insufficient Row Level Security in Lovable-generated apps on Supabase. Researchers found 170 of 1,645 showcased apps exposing 303 endpoints that could be read, and in some cases written, using only the public anon key. The fix is per-app: correct RLS policies on every table.
Can someone hack my app with my Supabase anon or publishable key?
Only if your RLS policies let them. The publishable key is designed to be public and acts as the anon role. If a table has RLS disabled or a permissive policy, that key is enough to read or modify it, which is exactly what happened at Moltbook.
Do Lovable's and Supabase's security scans make my app secure?
They catch the structural mistakes: RLS off, always-true policies, public buckets, known secret formats. Linters do not understand your business rules, so a policy that checks the wrong condition will usually pass. Use the scans, then run a two-account test by hand.
This quarter, if I owned a vibe-coded app with real users, I would do three things in order: run the Security Advisor and the curl test above against production, move every third-party API key out of the bundle and rotate it, and add a CI step that fails the build when a new migration creates a table without RLS and explicit grants. None of that needs a security team. It needs someone to decide that the database is part of the product.
Related: Secrets Sprawl 2026: 28.65M Leaked Keys and 64% Still Valid Years Later, No-Code Didn't Die. It Sold for 2.7x ARR While Its Replacement Raised at 27x and OWASP Top 10 for LLMs 2026 and Agentic Apps: 20 Risks, Real Incidents.
I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.
Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.
Related
How to Secure a Supabase App Before Launch: RLS Checklist (2026)
A step-by-step Supabase security checklist for 2026: enable RLS on every table, write auth.uid() policies, handle the 30 October grants change, move to publishable and secret keys, lock storage buckets, protect Edge Function secrets and test as anon.
13 minOct 31, 2026How to Implement SOC 2 Controls on AWS with Terraform (2026)
Step-by-step SOC 2 technical controls on AWS with Terraform: organization CloudTrail, AWS Config and a custom conformance pack, GuardDuty, Security Hub CSPM with CIS v5.0.0, Identity Center MFA, encryption defaults, AWS Backup vault lock, 400-day retention and evidence export.
18 minOct 31, 2026SOC 2 for Startups in 2026: Type 1 vs Type 2, $10K to $80K+, and Delve
SOC 2 for startups in 2026: Type 1 vs Type 2, the trust services criteria, published cost ranges from $7,500 to $80,000+, 3 to 12 month observation windows, what Vanta, Drata and Secureframe automate, the Delve allegations, and what engineers actually build.
11 min