Anatomy of the Moltbook breach: 1.5M tokens, 35K emails, 72 hours.
Most breach post-mortems are about novel attacks. This one isn't. Moltbook is interesting precisely because the failure mode was the most-documented anti-pattern in the Supabase ecosystem — Row-Level Security disabled while the anon key is in client code — and it still shipped, still scaled, still leaked.
What follows is a forensic timeline based on the publicly-disclosed facts, plus a reconstruction of the technical mechanism, plus the four lines of SQL that would have prevented the entire incident.
Timeline
January 5th, 18:00 PT. Moltbook publishes its launch tweet. The product is a vibe-coded social network with a clean Lovable-built React frontend and a Supabase backend. Within 24 hours, ~3,000 signups. Engagement metrics meet the founder's expectations. The launch is, by any normal standard, a success.
January 6th, ~14:00 PT. A security researcher fingerprints the stack. The fingerprinting takes about three minutes — view-source on the landing, search for the substring eyJ, find the JWT, decode it, confirm iss: "supabase", identify the project. This is not novel work. Anyone with curl and a browser does it.
January 7th, ~11:00 PT. The same researcher tests the obvious tables. The Supabase REST API is consistent: every public schema table is at /rest/v1/<table_name>. Common names — users, posts, messages, auth_tokens — return rows. No authentication beyond the anon key (which is in the bundle). The researcher contacts Moltbook's founder via Twitter DM. No response.
January 8th, ~08:00 PT. A second actor runs the same probes. Either independently or having seen the public discussion. Bulk-pulls every table. Posts the dump to a public paste site with a write-up titled "Moltbook is open." 1.5 million authentication tokens (mostly stale, but not entirely — sessions had no expiration). 35,000 email addresses. Names. Last-login timestamps. IP addresses.
January 8th, ~10:00 PT. Moltbook's founder discovers the breach. Tables are taken offline. Within an hour, RLS is enabled and policies are written. The damage is done.
January 9th onward. Disclosure. Press coverage. The pattern is identified as a category, not a one-off. Lovable rolls out additional safeguards over the following months. Our breach tracker uses Moltbook as the canonical example.
The technical mechanism
What was meant to happen
Lovable's React-to-Supabase wiring is correct. The frontend uses the Supabase client SDK with the anon key:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Later, in a component:
const { data } = await supabase.from('posts').select('*').eq('user_id', currentUser.id);
The anon key is supposed to be public. It identifies the project. It says "I'm a request from Moltbook, talking to Moltbook's database." It does not, on its own, grant access to anything.
Row-Level Security is what grants (or denies) access. RLS is a Postgres feature that gates every SELECT/INSERT/UPDATE/DELETE through a policy expression evaluated per row. With RLS enabled and a policy like auth.uid() = user_id, a query for posts returns only the rows where the calling user owns the row.
What actually happened
Row-Level Security was never enabled on any table.
The Supabase default for new tables is RLS disabled. There's a UI banner that warns about this. The CLI emits a warning. The docs are explicit. The Lovable-generated code did not enable it because the Lovable code generator at the time didn't generate ALTER TABLE ... ENABLE ROW LEVEL SECURITY statements.
With RLS disabled, the anon key talks to Postgres with the privileges of the anon Postgres role — and the anon role had been granted SELECT on the public schema (also a default). Result: any holder of the anon key can run SELECT * FROM users against the database with no further auth.
The exact attack request
Three lines. Less than five seconds.
curl 'https://<moltbook-ref>.supabase.co/rest/v1/users?select=*' \
-H 'apikey: eyJ...<anon-key-from-bundle>...' \
-H 'Authorization: Bearer eyJ...<anon-key-from-bundle>...'
That returns every row of the users table. Substitute auth_tokens, posts, messages, etc. for the same shape against every other public table. Pagination is handled via ?offset=N&limit=1000. Pulling 1.5M rows takes about 20 minutes at the default Supabase rate limit.
Why this isn't an "unknown unknown"
This pattern was known. Documented. Cited. Wiz Research had published a study a year earlier showing that 20% of vibe-coding-platform organizations had public-facing database misconfigurations. SupaExplorer's January scan of 1,645 Lovable showcase apps found 10.3% had critical RLS failures (CVE-2025-48757, CVSS 8.26).
What made Moltbook a category-defining incident wasn't novelty — it was scale. A small app with 100 users leaking the same way is a footnote. A launch that hits 35,000 signups before it leaks is a frontpage story.
The four lines that would have prevented it
Per table, in the Supabase SQL editor:
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_select_own" ON public.users
FOR SELECT TO authenticated
USING (auth.uid() = id);
That's it. Two statements per table — one to enable RLS, one to define the access predicate. Repeat for auth_tokens (probably auth.uid() = user_id), posts (depending on whether posts are public-readable: is_public = true OR auth.uid() = author_id), messages (auth check via the conversation membership table), and so on.
Total: maybe 30 lines of SQL across the whole schema. Total time: half an hour with focus, two hours with tests.
Why it kept happening anyway
Moltbook is one of many. The pattern repeats because three forces stack:
- The default is permissive. Postgres + Supabase's anon role has SELECT on public-schema tables out of the box. RLS is opt-in. New tables ship open.
- The platform doesn't enforce. Supabase warns; it doesn't refuse to deploy. Lovable generates frontends; it doesn't generate policies.
- The check is invisible. A frontend developer writing
supabase.from('posts').select(...)sees their own data, because they're authenticated. The query works. They ship. The app's "everything looks fine" state in dev mode is identical to its "leak everything to anyone" state in production.
Each force is small. Together they make the breach inevitable for a meaningful percentage of launches.
What changed after
Lovable's response was substantive. By March 2026, Lovable's app generator emitted ENABLE ROW LEVEL SECURITY for every created table by default. The platform added a pre-publish check that flagged tables without policies. The Lovable Discord pinned a security checklist. The Lovable security crisis post-mortem walks through the platform-level changes.
Supabase's response was incremental. The warning banners got more aggressive. The CLI started emitting warnings that block accidental commits of service_role keys. Project settings got an "RLS coverage" column showing tables with policies vs. tables without.
The third-party response was vibecheck. A scanner that hits any deployed URL — not just Lovable apps — and surfaces the same finding pattern within 5 seconds. Read-only by design. Same vantage as the attacker. Free.
The takeaway for any operator launching a vibe-coded app
Three checks, in order:
- Inspect from outside before you launch. Run vibecheck (or curl the obvious tables). Do this before the launch tweet, not after.
- Treat RLS as the security boundary, not the anon key. The anon key is meant to be public. The database's policies are what protects the data. If RLS is off on any table, the anon key reads it.
- Add a deploy gate. One line in CI:
Fails the deploy if a service_role key, an open RLS table, or a Stripe live key shows up. Catches the regression Code Review wouldn't.npx @vibecheck/cli "$DEPLOY_URL" --exit-on critical
The takeaway for the platforms
Defaults matter more than docs. A platform that ships open by default and warns about it loudly is functionally equivalent to a platform that ships open silently. Most users don't read warnings. Most users will ship the default.
The platforms that thrive in the next AI-builder generation will be the ones that ship secure by default and require an explicit override to make the database public. RLS-on, policies-required, with a "make this collection fully public" toggle that's hidden behind a "are you sure" dialog.
That's not a hard product decision; it's a marketing decision dressed as a technical one. Every platform's velocity story includes "you can ship in 60 seconds." Adding a 30-second policy step feels expensive against that. Until the next Moltbook hits the front page.
Sources
The Moltbook timeline above is reconstructed from the public discussion and the breach write-up that surfaced on January 8th 2026. Specific numbers (1.5M tokens, 35K emails, 72-hour window) are from the original disclosure. Technical claims about Supabase defaults and the RLS model are checkable in Supabase's own RLS documentation. Population statistics (10.3% of Lovable showcase apps with critical RLS failures, 20% of organizations with public-facing misconfigurations) come from Wiz Research's vibe-coded app study and SupaExplorer's January 2026 scan.
Where you'll find a vibecheck-flagged version of this on your own app: vibecheck.themeridianlab.com.
Inspect your app for the Moltbook pattern