Skip to content
Blog

9 min read

Lovable and Supabase – check RLS before launch

Login does not automatically protect your data. Here is how to review tables, files and access rules in a Lovable app using Supabase.

By Written with AI assistance from the source

Mist over a dark, rocky coast with a calm sea
Photo: Leslie Cross on Unsplash

What the review actually shows

A wide-ranging security review of Lovable projects found apps where personal data and other sensitive information could be read without logging in. The content included chats, identity details, internal messages, contracts and documents. The findings have been reported by Breakit, while the technical method and scope are described in the original review.

This does not mean that all Lovable apps are insecure or that Lovable’s own systems have been hacked. The review mainly concerns apps connected to Supabase whose access rules allowed unauthorised requests. The researchers used email addresses as an indication that tables might contain personal data, but noted that some findings may have been public contact details or test data. Nor could they establish whether the data had actually been misused.

This is therefore not a single new vulnerability that can be fixed by updating a package. Instead, the review shows how a recurring configuration error can have serious consequences when apps are built and published quickly. The interface may look finished, login may work and the user may be sent to the right page, while the database still accepts requests that the interface never exposes.

The most important thing for anyone building an app is to distinguish between what a user sees in the app and what the user can retrieve from the database.

Login is not the same as authorisation

Authentication answers the question: Who is the user? Supabase Auth can, for example, verify that someone has logged in and provide the browser with a signed identity.

Authorisation answers a different question: What is this user allowed to do? A logged-in customer may be allowed to read their own profile, but not other customers’ profiles. A project member may be allowed to read the project’s messages, but not messages from other projects.

This is where Row Level Security, or RLS, comes in. RLS consists of rules in the Postgres database that determine which database rows a request may read, create, update or delete. The rule runs in the database and still applies if someone bypasses the interface and calls Supabase directly. Supabase recommends enabling RLS on all tables exposed through its Data API.

A common mistake is a policy that effectively allows all logged-in users to read every row. The authenticated role only means that the person has a valid session. It does not mean that the person owns the row, belongs to the right organisation or participates in the right conversation.

Another misconception is that the Supabase key in the frontend must be hidden. The public key is intended to be available in the browser. Security should not depend on nobody finding it, as it can be read in the app’s network traffic and compiled JavaScript. RLS and the database’s other permissions should restrict what the key can be used for. A secret key or older service_role key, however, is sensitive and must not be sent to the browser because it bypasses RLS.

Review the tables and their policies

Start by taking an inventory of the tables used by the app. Do not look only at obvious tables such as profiles and messages. Also check tables for form responses, support cases, bookings, payment metadata, AI-generated content, waiting lists and administrative notes.

In the Supabase Dashboard, you can open the tables and see whether RLS is enabled. You can also use the SQL Editor to get a complete list:

sql
select
  n.nspname as schema_name,
  c.relname as table_name,
  c.relrowsecurity as rls_enabled,
  c.relforcerowsecurity as rls_forced
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r'
  and n.nspname = 'public'
order by c.relname;

Having rls_enabled enabled is necessary, but not sufficient. You must also read the policies and understand what they allow. Look in particular for conditions that are always true, broad access for anon or authenticated, and rules that check whether a user is logged in but not whether they own the data.

For a simple table where each row is owned by a user, the principle may be that the row’s user_id must match the logged-in user’s identity. Chats, teams and organisations often require a membership table instead. A read policy for messages could, for example, be based on the user appearing in the membership list for the relevant conversation:

sql
alter table public.messages enable row level security;
​
create policy "members can read conversation messages"
on public.messages
for select
to authenticated
using (
  exists (
    select true
    from public.conversation_members cm
    where cm.conversation_id = messages.conversation_id
      and cm.user_id = auth.uid()
  )
);

Do not copy the example directly without adapting it to your data model. The policy only controls reading. Creating, updating and deleting require separate decisions. For each operation, ask who should be allowed to perform it and under what conditions.

Also check the database grants. Grants determine whether a role can access a table or operation at all, while RLS determines which rows the role can work with. A table that never needs to be read directly from the frontend should not have broader permissions than necessary.

Do not forget Storage, functions and administrative flows

Private documents are often stored in Supabase Storage rather than in a standard table. In that case, protecting the document metadata table is not enough. The file itself must also be private.

Check every bucket in Storage. A public bucket makes files available through public URLs and is therefore only suitable for content that genuinely needs to be displayed publicly, such as public profile pictures or images on a public website. Passport copies, contracts, attachments, receipts and private photos should be stored in a private bucket. Access to private files is controlled through policies on storage.objects or through signed links created after an authorisation check.

Test more than uploads. Check who can:

  • list files in a bucket
  • download a file with a known filename
  • upload files to someone else’s folder
  • replace or delete an existing file
  • create a signed link

Also review Edge Functions, server routes and RPC functions. Code that uses a secret Supabase key can bypass RLS. This may be intentional for administrative tasks, but the function must then check the user’s identity and authorisation before reading or changing data.

A common risk pattern is a function that receives a user_id, project_id or filename from the browser and trusts the value. The user can change such values. Instead, the function should derive the identity from the verified session and check membership or role on the server side.

Test without relying on the interface

A security check is more reliable if you test the app as different types of user. Clicking through the intended flow with your administrator account is not enough.

Create separate test accounts, such as user A and user B. Put them in different organisations or projects and create clearly labelled test records. Then try to access user A’s data from user B’s session.

Use the browser’s Network panel to see which requests the app makes to Supabase. Then test the same types of requests while logged out and while logged in with the wrong account. Check reading, creating, updating and deleting separately. Blocking read access does not automatically mean that updates or deletions are also blocked.

Go through the following before launch and after every database change:

  • RLS is enabled on all exposed tables
  • Policies check ownership, membership or an explicit role
  • Logged-out requests can only access public data
  • One account cannot read or change another account’s private data
  • Administrator access is verified in the database or on the server side
  • Private files are stored in private buckets
  • Edge Functions and server routes perform their own authorisation checks
  • No secret key appears in the frontend, logs or a public source code repository
  • Old test tables, buckets and functions have been removed or locked down
  • Testing is repeated after schema changes and new Lovable prompts

Lovable’s and Supabase’s automated checks can find common problems, but they cannot fully determine your business logic. A tool does not automatically know whether a support worker should be allowed to see all cases, only assigned cases or no attachments at all. You must define that authorisation model and then prove it through testing.

If data may already have been public

If you discover an open table or bucket, do not simply add a policy and move on. Close the access, retain relevant logs and document which data could be accessed, during what period, and by which roles or keys.

If a secret key has appeared in the frontend or a public source code repository, it must be replaced. Also review Edge Functions, environment variables, build logs and previous deployments. Removing a line from the latest code does not automatically invalidate older keys.

Then investigate whether there are signs of unauthorised requests and which people may be affected. Sensitive personal data, identity documents and private messages require a more cautious incident assessment than public content or test records. If necessary, seek help with the technical investigation and with assessing any notification or reporting requirements.

The aim is not simply to make the security warnings disappear. You should be able to describe who may read each type of data, where the check takes place and how you have tested that others are denied access. When the prototype’s temporary rules need to be replaced with a sustainable authorisation model, the next step is to move from prototype to product.


Source: Breakit

Want to know what your build would take?

Three questions, then you get a clickable sketch for free.

← All articles