deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Lovable-built apps leaked passport data and 200K messages through missing Supabase RLS

A Hacker News report on Lovable-built apps exposing passport data and over 200,000 private messages traces back to Postgres tables without Row Level Security. A dev.to guide explains how to audit and fix it.

Lovable-built apps leaked passport data and 200K messages through missing Supabase RLS

What happened

According to a dev.to writeup, a Hacker News post about applications built with the AI app builder Lovable drew nearly 600 points this week after it documented exposed passport information and more than 200,000 private messages. On close reading, the writeup found no sophisticated attack behind the incident. In most cases the Postgres tables behind these apps simply had Row Level Security (RLS) disabled, while the credentials needed to query them ship inside a JavaScript bundle anyone can download.

The author, who reviews freelance and startup projects in Vietnam that pair Supabase with AI code generation tools such as Lovable, Bolt and Cursor, reports encountering the same misconfiguration regularly.

Why the anon key is not a secret

The article traces the problem to a mindset gap. Developers arriving from traditional backend stacks treat API keys in .env files as secrets. Supabase works differently: the browser talks to the database directly through PostgREST, and the anon key is a JWT carrying the anon role, public by design. Any value named like VITE_SUPABASE_ANON_KEY ends up in the JavaScript served to every visitor.

That makes RLS policies the only barrier between an anonymous request and the rows. Yet tables created through raw SQL, or through migrations generated by an AI tool, do not get RLS enabled by default. The Supabase dashboard shows a warning, but a workflow built on prompts and migration files never opens that dashboard.

A five-minute self-test

The author recommends auditing from the attacker's position, using only curl and the production URL. First, fetch the page, locate the asset bundle, and extract the JWT (recognizable by its eyJ prefix) together with the project URL. Then request a single row from each table through the REST endpoint with an exact-count header. If the response reports more than zero readable rows on a table holding personal data, the app has the same hole as the apps in the Hacker News report; a safe setup returns no count at all.

The table list itself comes from the OpenAPI specification served at the REST root, which is exactly how an attacker would enumerate the schema. The article also advises testing writes, for example patching a role field on another user's profile, because many apps add read policies and forget INSERT and UPDATE entirely.

The policy mistakes that show up most

  • Policies with a condition of plain true. AI tools often emit these to silence permission errors, which makes enabling RLS pointless.
  • SELECT-only policies. Without a matching check on INSERT and UPDATE, users can rewrite someone else's user id or role.
  • Trusting user metadata for permissions. Users can edit it themselves through Supabase's update-user call, so authorization data belongs in app metadata or a dedicated table.
  • Calling auth.uid() directly inside policies. Wrapping it as a scalar subquery lets the planner cache the value, which the author says can be tens of times faster on tables with hundreds of thousands of rows.
  • A service_role key reaching the frontend. It bypasses RLS completely, so it must stay in server-side code such as Edge Functions.
  • Public storage buckets. Passport and identity document uploads placed in public storage are not covered by table policies.
  • Ordinary views. By default they run with owner privileges and sidestep RLS unless created with security_invoker = true.

Blocking regressions in CI

A one-time fix decays, because the next prompt-generated table reintroduces the hole. The author shares a small Python audit script, built on psycopg, that flags public tables without RLS, policies whose condition is literally true, and views missing security_invoker. The intended CI flow starts a local Supabase database, applies the migrations, runs the script against it, and blocks the pull request on any finding. Tables that are legitimately public, such as country or pricing lists, pass through an explicit allowlist rather than slipping through by accident.

Why it matters

As the dev.to author frames it, the leak was not the failure of one AI tool but the consequence of putting a database on the internet without understanding its security model. AI assistance shortens the path from idea to production, and it shortens the path to shipping a vulnerability just as effectively. In the Supabase model, RLS is what makes a public key acceptable, and it stays off until someone turns it on. Teams adopting AI-assisted development should treat that as an invariant enforced by CI, not a dashboard warning they hope to notice in time.

  • #supabase
  • #security
  • #postgresql
  • #row-level-security
  • #ai-code-generation

Related posts