ManagedQA PartnersSee plans

AI code · 2026-09-02 · 2 min read

Row-level security: the most documented failure on AI-built apps

A CVE, a platform-wide database exposure and the vendor's own going-live note all describe the same thing. Two checklist items catch it on every release.

Supabase exposes Postgres to the browser through an API (the interface other software calls; tested directly, without the UI in between). The publishable key is in the bundle (the JavaScript your app sends to the browser or ships inside the mobile app; anyone can read it) by design. What keeps one user out of another user's rows is row-level security (database rules that decide which rows each signed-in user may read or write), and a table without it is readable and writable by any role with a grant. (Source: Supabase docs, 2026-09 · verified 2026-09-21)

Three documents, one failure

CVE-2025-48757, rated 9.3, describes an insufficient row-level security policy on generated sites that let unauthenticated attackers read or write arbitrary tables. (Source: NIST National Vulnerability Database, 2025-05-30 · verified 2026-09-21) In January 2026, a key exposed in client-side JavaScript combined with missing policies gave full read and write access to a social platform's database: about 1.5 million API tokens and 35,000 email addresses. (Source: Wiz Research, 2026-02-02 · verified 2026-09-21) Lovable's own Supabase integration docs say every table needs a policy before going live and call missing policies the most common way app data gets exposed.

Three sources, one mechanism: a table was created, by a migration (a scripted change to the database schema that ships with a release and should be reversible) or by a tool, and nobody enabled the policy, or the policy said true.

Why it happens

A table created through the dashboard prompts for it. A table created by SQL, by a migration, or by an agent that writes migrations does not. And when a query fails under the anonymous key, the fastest fix is a policy whose condition is true for everyone, or a switch to the secret key that bypasses policies entirely. Both make the query work. Neither is isolation.

The two items

AC-01 queries the database catalog for every table in an exposed schema and its RLS (row-level security: database rules that decide which rows each signed-in user may read or write) flag. Pass is every table enabled, with exemptions named in the release record (a public read-only lookup table, say). It is automated and runs on every release, because every release can add a table.

AC-02 is the two-user test (sign in as one user, create a row, then try to read or change it as another user; pass is nothing comes back). Sign in as user A and create a row in each table. Sign in as user B and, through the client API with the publishable key, attempt select, update and delete on it. Pass is zero rows on select and a permission error on the writes, for every table. This is the only proof that a policy works; reading it is not.

The related items

SC-01 scans bundles for the secret key. The docs say it must never leave your control and that Supabase returns 401 to a secret key sent from a browser, but that check keys on the User-Agent header and does not cover mobile apps or scripts. AU-03 and AU-04 cover the auth endpoints the same incidents exercised.

The Supabase hub lists the vendor defaults with dates. The access control category has all six items with pass criteria.

More in AI code

Blog · ManagedQA Partners