A login screen can look convincing while the database behind it answers requests from anyone.

That gap was at the center of a recent investigation into apps built with Lovable. Security researchers at Crissy Field examined 159,041 projects found through the Common Crawl archive. Of the 34,545 that used Supabase, 24,664 returned database records as soon as a site loaded. That alone does not prove a leak, because some records may be intended for public display. In additional read-only requests, however, the researchers found email addresses in 10,560 tables across 7,403 sites, alongside specific cases involving passport details, private messages, event orders, contracts, and internal communications. The researchers say their sample is not representative of every Lovable app, and they could not establish whether the exposed data had been misused. Crissy Field's report explains the sample and its limitations; Handelsblatt's original reporting covered the investigation.

The lesson is not that one app builder is uniquely unsafe. Crissy Field says access-control mistakes can happen in any application, whether people or AI build it. The uncomfortable point is simpler: a polished UI and a successful demo do not prove the backend enforces the privacy rules the product implies.

This guide turns that lesson into a release check. It uses Supabase because that was the backend in the reported sample, but the basic questions apply to any app: what can an unauthenticated visitor read, what can one signed-in user read about another, and what privileged credentials have reached the browser?

This is a researched walkthrough, not a report of a personal security audit. Run the checks against your own project with test data and authorized access. Never probe someone else's app or database.

The security boundary is the data request, not the screen

A client-side app can hide a page, redirect to a sign-in screen, or omit a button. Those are interface behaviors. If a database API still returns another user's rows to a request carrying only a public client key, the data boundary is open regardless of what the UI displays.

Supabase's documentation describes two distinct controls: grants decide which database operations a role can perform, while row-level security policies decide which rows those operations can touch. Its docs warn that existing tables may have broad grants, that adding policies does not revoke those grants, and that a service_role key bypasses RLS and must stay server-side. Supabase's RLS guide is explicit about checking both permissions and policies.

Think of the release question as a matrix, not a single login test:

  • No session: Can an anonymous visitor read, add, change, or delete anything?
  • User A: Can this account access only the rows it should own or share?
  • User B: Can a different account read or change User A's private rows by changing an identifier?
  • Privileged server path: Does a server-only operation verify who called it before using server-only credentials?
  • Public data: Is each intentionally public row actually safe to expose and limited to the fields the app needs?

A check that only confirms "I can sign in" misses most of this.

Step 1: Map the data before changing policies

Write down the tables, views, storage buckets, and server functions that touch user data. For every resource, record the intended readers and writers. Keep it concrete: "signed-in users can read their own support tickets; support staff can read tickets assigned to their team; anonymous users can read published announcements" is testable. "The app is secure" is not.

Classify each resource as one of these:

  • Public by design: published catalog items or public announcements.
  • User-private: profiles, saved drafts, private messages, or order history.
  • Organization-scoped: data visible to members of one tenant or workspace.
  • Privileged: administrative data or actions that should run only on a trusted server.

Do not assume a table is public because the app currently displays some of its rows. A public listing may need a deliberately small view, not unrestricted access to the underlying table. Do not assume a table is private because the page has authentication.

Step 2: Check RLS and grants for every exposed table

For each table in an exposed schema, verify that row-level security is enabled, then inspect the policies and grants separately. Supabase recommends enabling RLS, setting grants to match the app, writing policies per operation, and creating database tests that assert both allowed and denied behavior for select, insert, update, and delete under anon and authenticated roles. The official guide walks through this sequence.

A minimal ownership rule for a private table might look like this:

alter table public.notes enable row level security; revoke all on table public.notes from anon, authenticated; grant select, insert, update, delete on table public.notes to authenticated; create policy "Users read their own notes" on public.notes for select to authenticated using ((select auth.uid()) = user_id); create policy "Users add their own notes" on public.notes for insert to authenticated with check ((select auth.uid()) = user_id); create policy "Users update their own notes" on public.notes for update to authenticated using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id); create policy "Users delete their own notes" on public.notes for delete to authenticated using ((select auth.uid()) = user_id); 

This is an example, not a drop-in policy for every schema. It assumes public.notes has a user_id column tied to the authenticated user's ID and that the product really intends only that owner to access a row. Team sharing, delegated support access, or moderation needs different rules. Review the semantics before applying SQL.

For a resource intended to be public, grant only the necessary operation and write a policy that makes that intent unmistakable. Avoid broad for all rules when separate operations have different needs. Keep policy names clear enough that a reviewer can see the rule's purpose.

Also inspect views and functions. Supabase notes that views can bypass RLS by default depending on how they are created, so a protected base table is not enough if a view exposes the same rows. A function configured to run with extra privileges needs its own careful authorization checks.

Step 3: Test the negative cases, not just the happy path

Create two test users and seed records with distinct ownership. Then test what should be allowed and what must fail. For example:

  • Anonymous user cannot read a private note.
  • User A can read and update User A's note.
  • User A cannot read, update, or delete User B's note.
  • User A cannot insert a note while claiming User B as its user_id.
  • A public visitor can read an announcement that is meant to be public, but cannot see private fields or unrelated records.

Supabase's documented workflow supports SQL-based database tests under supabase/tests/ and running them with supabase test db. The official guide specifically recommends assertions for allow and deny behavior across operations and roles. Use that as the baseline for repeatable policy tests.

The key is to test denial. A test suite that proves User A can read User A's record but never tries User B's record is incomplete. Many authorization bugs are object-level: the route works, the identifier is valid, and the application forgets to verify that the current user owns the requested object.

Test the API and the application path. A route may apply an application-level check while a direct database request takes a different path. Conversely, a database policy may be correct while an edge function uses a privileged key and forgets to check the caller. Test each path that can reach sensitive data.

Step 4: Inspect the browser bundle and secret handling

Anything delivered to a visitor's browser should be treated as public. A publishable Supabase key is designed for client-side use, but it is not an access-control mechanism by itself. The database grants and policies determine what requests using that role can do.

Search the generated frontend, built JavaScript, source maps, and environment configuration for secrets such as service_role, Supabase secret keys, database passwords, payment-provider private keys, and AI-provider credentials. If a privileged key has ever been shipped to a browser or committed publicly, remove it from the client and rotate it. Deleting the text from the latest source file does not invalidate a credential that has already escaped.

Supabase says secret keys authorize access through the service_role role, which bypasses RLS, and should never be exposed to customers. Its documentation on bypassing RLS explains why that key belongs only in trusted server-side code.

A server function using such a key should still authenticate the request, authorize the specific action, validate inputs, and avoid returning more data than the caller needs. Moving a key server-side does not automatically make the endpoint safe.

Step 5: Repeat the test against the deployed app

A green local test is necessary, not sufficient. Before launch, use a clean private browser window with no existing session. Inspect the app's own network requests while loading public pages. Confirm that private records are not present in responses before login, and that logging out removes access rather than merely hiding UI elements.

Then sign in as User A and attempt to access User B's test record through the same requests the app uses. Change only the test record identifier. The expected result is denial or an empty response, not another user's data. Repeat for reads and writes, and test uploads and file URLs if the app handles attachments.

Run the test on the production configuration after deployment. Environment variables, database migrations, redirect rules, and generated builds can differ from local development. If a check fails, treat it as a release blocker. Fix the policy or grant, deploy the change, and rerun the same test. Do not accept "the scanner passed" as proof that the deployed access boundary behaves correctly.

Supabase's production security checklist recommends RLS on every table and a project-specific threat review. The platform's Security Advisor can help identify some configuration problems, but automated findings do not replace testing the access rules your product requires.

A release checklist worth saving

Before real customer data enters an AI-built app, check each item:

  • Data map: Every table, view, bucket, and privileged function has an intended reader/writer list.
  • RLS: Enabled on every exposed table that contains non-public data.
  • Grants: Anonymous and signed-in roles have only the operations they need.
  • Policies: Separate allow rules for each operation, with ownership or tenant boundaries where appropriate.
  • Views and functions: Reviewed for functions configured with extra privileges and paths that bypass base-table policies.
  • Tests: Include positive and negative cases for anonymous users, two separate users, and important write operations.
  • Secrets: No service-role or other private credentials in frontend code, logs, screenshots, or public repositories; exposed secrets are rotated.
  • Deployed behavior: Rechecked against the live app with a clean session and separate test accounts.
  • Data minimization: Public responses contain only the fields intended for public display.
  • Review ownership: A named person is responsible for security approval, not just for getting the build to work.

The reported Lovable findings are a warning about the distance between app generation and production readiness. They do not establish that all generated apps leak data, and they do not show that a particular builder alone caused every misconfiguration. They do show why a release process needs an independent test of what the backend returns.

The practical standard is not "the AI said it added authentication" or "the preview looks private." It is: anonymous requests cannot retrieve private records, one user cannot cross into another user's data, and the deployed app enforces those rules on every route that matters.

I write practical guides on building and shipping software safely. Subscribe for more research-backed walkthroughs. Before your next release, which would catch more mistakes on your team: policy tests in CI or a manual deployed-app review? Save the checklist above and make the answer part of your release gate.

Sources