Bolt.new app with Supabase RLS missing: how to fix it
A Bolt.new app that uses Supabase is safe only if each table has RLS on and a correct policy, so open each table in Supabase and check it. Bolt writes the app code and the database setup from your prompts. The Bolt help center does not say that Bolt enables RLS for you. Do not assume that it did.
This guide shows how to find the tables, how to test them as an attacker, and how to fix them. It also shows how to tell Bolt to keep RLS on in later changes.
What does “RLS missing” mean in a Bolt app?
Row Level Security (RLS) is a Postgres feature. It checks each request against policies that you write. A policy decides which rows a caller can read, insert, update or delete.
Supabase exposes the public schema through an automatic REST API. If RLS is off for a table, no rule limits the callers. The Supabase RLS documentation states that a table in an exposed schema without RLS is readable and writable by any role with a grant on it.
A Bolt app has the Supabase URL and the anon key in its browser code. The browser needs both values to talk to the database. Anyone can open the app, read the values, and send their own requests. The key is public by design. See what the anon key can access.
For this reason, RLS is the only control between the public key and your data. If it is missing, an attacker can read, change and delete your rows. The Supabase linter reports this state as lint 0013_rls_disabled_in_public. The lint description gives the level as ERROR.
A Bolt project can reach this state for simple reasons:
- You asked Bolt for a feature, and Bolt created a table. The table works in the preview, so you did not look at its security.
- A permission error stopped a feature, and a prompt fixed it with a broad policy.
- You connected an old Supabase project that already had open tables.
Bolt documentation does not describe how it handles RLS. For this reason, this guide does not claim what Bolt does by default. The fix works for any table, no matter which tool created it.
Which Supabase project does my Bolt app use?
You must open the correct project before you can check the tables. The Bolt help center page for Supabase describes two cases.
You connected your own Supabase project. The help center says that connect applies when you already have a Supabase database that you want to use in your Bolt project. Open that project in the Supabase dashboard.
You use a Bolt database. The help center says that claiming moves a Bolt database into Supabase. After the claim, the database appears as a Supabase project. You must be a Supabase organization owner to claim it. Permissions come from your Supabase account, not from Bolt.
Caution: The help center warns that connecting a Supabase database replaces the existing connection, and this can cause data loss. Do not connect a different project to fix this problem.
If you are not sure, find the project URL in your deployed app. Open the browser DevTools, select the Network tab, and use a feature that loads data. Look for requests to a host that ends in .supabase.co. The first part of the host is the project reference. Open the project with this reference in the dashboard.
How do I check which tables have RLS missing?
Use two checks. The first reads the settings. The second sends real requests.
Check the Security Advisor
- Open the Supabase project in the dashboard.
- Select Advisors, then Security Advisor.
- Find each finding with the name “Table publicly accessible”.
- Write down the table names.
The CLI also has the command supabase db advisors. Some findings can be intentional. A table of public data can stay open for reading, but nobody must be able to write to it.
Check the table list
Open Database, then Policies. The page shows each table and its RLS state. Check each table in the public schema. Look at the tables that Bolt added for features, such as profiles, orders, messages and files.
Test with the anon key
The setting can look correct while the policy is wrong. Send a real request with the same key that your deployed app ships. Do not use the service_role key, because it bypasses RLS and hides the problem.
curl "https://<project-ref>.supabase.co/rest/v1/<table>?select=*&limit=1" \
-H "apikey: <anon-key>" \
-H "Authorization: Bearer <anon-key>"
Read the result:
- A JSON row means that anonymous callers can read the table.
- An empty array
[]means that RLS removed every row, or the table is empty. - A permission error means that the role has no grant on the table.
Warning: Do not test update or delete requests on real user data. Create a test row and use only its id.
For the write test, create a row in the SQL Editor with the value rls-test. Send a PATCH request for that row with the anon key. Then read the row again in the SQL Editor. If the value changed, an anonymous caller can write to the table.
How do I fix the missing RLS now?
Do the steps in this order. Steps 1 to 3 close the hole. The other steps repair the app.
Caution: After you enable RLS, the API returns no rows until a policy allows the request. Your Bolt app can show empty lists. Do steps 1 to 4 in one session.
-
Enable RLS on each open table. Use Database, then Policies, then Enable RLS. Or run SQL for each table:
alter table public.profiles enable row level security;The Supabase API security guide describes both methods.
-
Remove each policy that uses
true. Read each policy in the Policies page. Ausing (true)policy lets every caller in the named role read every row. Drop it:drop policy "Allow all" on public.profiles; -
Add owner policies. A policy that checks the owner works for most tables. The table needs a
user_idcolumn that holds the owner. The syntax follows the examples in the RLS documentation:create policy "owner can read" on public.profiles for select to authenticated using ((select auth.uid()) = user_id); create policy "owner can insert" on public.profiles for insert to authenticated with check ((select auth.uid()) = user_id); create policy "owner can update" on public.profiles for update to authenticated using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id); create policy "owner can delete" on public.profiles for delete to authenticated using ((select auth.uid()) = user_id);Anonymous callers use the
anonrole, so they match none of these policies. For other designs, such as teams and roles, read the Supabase RLS guide. -
Test with the anon key again. Run the read test. The result must be
[]or an error for any data that an anonymous user must not see. -
Test the app as a normal user. Sign in, and use each feature. Create, read, change and delete a row. Fix each feature that now fails.
-
Test as a second user. Try to read and change a row that belongs to the first user. The request must return nothing or fail.
-
Save the SQL as a migration. Keep the file in your repository, so that you can repeat the setup in another project.
If a table has no user_id column, add the column and fill it for the old rows before you rely on the policy. A row with null as owner matches no user. Nobody can read it through the API.
If a table needs no browser access at all, enable RLS and add no policy. The API then returns nothing. Your server code can use the service_role key. Never put that key in the browser. Read the service_role key guide for the rules.
Can I ask Bolt to write the policies?
You can, but you must check the result. A policy is a security rule, and a wrong rule can leave the table open. Use a prompt that is exact:
For every table in the public schema, enable RLS. Add policies so that an authenticated user can read, insert, update and delete only rows where user_id equals auth.uid(). Do not add any policy that uses true. Do not give the anon role access to any table.
After Bolt applies the change, do not trust the preview. Open Database, then Policies, and read each policy. Then run the anon-key test.
Remember that the Bolt help center states that restoring a project version does not change your current Supabase databases. A restore in Bolt does not undo a database change, and it does not repair a policy. The database lives in Supabase, so repair it there.
How do I check whether someone used the open tables?
The warning does not prove abuse. It shows that abuse was possible. Check the logs and the data.
- Open the logs. In the dashboard, open the Logs page. The Supabase logs documentation describes the API Gateway and Postgres sources.
- Filter by method. Use the Method filter for
DELETE,PATCHandPOST. Use the Pathname filter for the REST path of each table. - Look for unusual callers. Compare the IP addresses and user agents with your own traffic. Many requests from one source need a closer look.
- Check the retention. The retention depends on your plan. Old requests may be gone.
- Check the data. Look for rows that you do not recognize and for missing rows. Check the tables for users, orders and messages first.
The logs do not always show data changes inside Postgres. If you cannot find a trace, you cannot prove that nobody read the table. Treat the data as read.
If rows are lost, see the Supabase backups documentation. Pro projects keep 7 days of daily backups, and Team projects keep 14 days. A restore returns the whole database to the state at the backup time, and the project is not available during the restore. For Free projects, the page recommends regular exports with supabase db dump.
Warning: If personal data was readable, you may have a duty to tell your users or a regulator. Ask a lawyer if you are not sure.
How do I keep RLS on in later Bolt changes?
Each new prompt can add a table. Use these controls so that new tables are not open.
- Put the rule in the first prompt of each task. Write: “Every new table needs RLS and owner policies in the same migration.” Repeat it when you ask for a new feature that stores data.
- Check the Security Advisor after each feature. The advisor reads the live configuration. It shows a new open table at once.
- Create tables in the dashboard when you can. The API security guide states that tables created in the dashboard have RLS on by default. Tables created in the SQL Editor or with other tools do not.
- Grant only what each role needs. Do not give
deleteorupdatetoanon. - Search for
using (true). Search the migrations and the policies after each change. Replace each match with an owner check. - Test as a different user before each release. Try to read and delete a row of another user. The request must fail.
- Scan the deployed app. A scan sends real requests with the public key. See the Bolt security guide for the full checklist, and the Bolt security scanner for an automatic test.
When should I get help?
Get help in these cases:
- The logs show deletes or updates that you did not make. Restore a backup, and ask an engineer to review the access.
- The open tables held personal data, payment data or private messages. You may have legal duties.
- You cannot write a policy for your data model, for example for teams or shared rows. A wrong policy can block real users or leave the hole open.
- You find a
service_rolekey in the browser code of the app. Treat the database as fully exposed, and rotate the key. - You do not know which Supabase project the app uses. Find the project reference in the network tab before you change anything.
After the fix, run the anon-key test one more time on each table. No table may return data that an anonymous user must not see.
Common questions
Does Bolt turn on RLS for me?
Where do I see my Bolt database?
Does the undo or version restore in Bolt fix the database?
Can I ask Bolt to write the RLS policies?
Is it safe to keep the Supabase URL and anon key in the app?
Check your bolt app for open tables
A VibeEval scan reads the public Supabase key from your deployed Bolt app and tests each table for anonymous read and write access.
14-day free trial · No credit card · Cancel anytime