sandbox_exec: The Lovable Cloud Database Role You Didn't Create
Three different security audits of Lovable Cloud projects have landed in the community with the same finding: a database role they didn't recognize, called sandbox_exec, with permission to skip every Row Level Security rule, and with read and insert access on their most sensitive tables.
Nobody on their teams created that role. It isn't in the docs. And the question they all asked was the right one: what is this, and can I lock it down without breaking Lovable?
I didn't want to answer that with a guess, so I tested it on a Cloud project of my own, four separate times as the questions got sharper. I'm writing it down because it helped the first person ship a fix the same morning, and because the next person who looks at their database roles will have the same question.
Why this matters (RLS, BYPASSRLS, and why it scares people)
Before the findings, a quick bit of context, because the part that gets flagged is really one word.
RLS (Row Level Security) is the set of rules that decides who can see and change which rows in your database. When a user opens your app, RLS is what makes sure they see their orders and not everyone's. It is the main thing protecting your users' data from each other, and on Supabase it is the line between "my app is fine" and "my app is leaking". A role with BYPASSRLS simply walks past those rules. Every row, every table, no questions asked.
That is why the finding lands the way it does. You open your database for the first time with a security hat on, you see a role you never created, it can skip the one protection you were counting on, and it already has access to the table you care about most. If you are getting ready to hold customer data, or you are the person who has to sign the compliance form, that is a genuinely scary moment, and it should be taken seriously.
It also helps to know what the finding does and does not mean. It does not mean your data is exposed to the internet: sandbox_exec is an internal database role, it only matters to things that can already log into your database. And it does not mean someone broke in: the role comes with every Lovable Cloud project. What it does mean is that there is one more key to your data than you knew about, and you deserve to know what it opens and whether you can take it away. That is the whole point of this post.
Who actually touches your Lovable Cloud database
The first thing I wanted to know was which role each tool really uses when it talks to your database. Postgres will tell you if you ask it with select current_user;, so I asked from every place I could reach.
| Where the SQL comes from | Runs as |
|---|---|
| Migrations the agent writes and applies | postgres |
| The agent changing data (inserts, updates, deletes) | postgres |
| The agent reading data | supabase_read_only_user (read only) |
| The Cloud SQL editor | postgres |
| The Lovable MCP | postgres |
The agent running psql from its code sandbox (shell) |
sandbox_exec |
That last row took me three rounds to find. The structured tools never use sandbox_exec, and for two weeks I thought nothing did: no open connections, no tables owned, no sign of it. Then I asked Postgres who had actually run queries (pg_stat_statements keeps that per role) and there it was: a handful of queries on the exact days the agent had dropped into its code sandbox and run psql to check tables and sizes. One of them was psql's own \d lookup. So the name is literal. sandbox_exec is the database credential sitting inside the agent's sandbox, used when it executes code there instead of calling a tool.
What Lovable says (written answer, November 4, 2026)
While I was writing this, the third auditor got a written answer from Lovable Support and shared it in the Lovable Discord (the thread, login required). They had asked the same question on Reddit the day before, so if you read it there, same person, same answer. It matches what I measured and fills in the rest:
sandbox_execis a restricted role Lovable creates on every Lovable Cloud database, never on an external Supabase project you connect yourself. It lets the agent run database commands, psql included, from its isolated command environment without the full admin credentials.- By design it gets CONNECT, USAGE on
public,auth,storageandextensions, SELECT and INSERT on all tables in those schemas, and USAGE and SELECT on sequences. UPDATE on tables and EXECUTE on functions are explicitly revoked. Default privileges hand the same access to new tables. - Privileges coming back is intentional. The platform re-applies the role and its grants when the agent starts working on the project if the stored setup is missing or out of date, for example after Lovable updates the privilege template. A separate step re-applies the login credentials if the agent's connection fails.
- There is no supported way to exclude specific tables. Manual revokes through a migration are not a durable change and can be reverted by the platform.
So the two open questions in the earlier version of this post are closed: what uses it (the agent's command sandbox) and who puts the grants back (Lovable, on purpose). Read the tests below with that in mind.
What sandbox_exec can and can't do
Knowing that it is the shell's key, the next question is what that key opens. Here is what Postgres reports:
| Can | Can't |
|---|---|
| Log in | Act as a superuser |
Skip RLS (BYPASSRLS) |
Create or change tables (no DDL) |
Read rows (SELECT) on every table |
Update rows |
Add rows (INSERT) on every table |
Delete rows |
| Use sequences | Run your functions |
Read and insert storage metadata (storage.buckets, storage.objects) |
Read your secrets (vault) |
Reach the auth schema on my project (the SELECT grants on the auth tables are there, the schema usage isn't; Lovable's answer says usage is intended, so treat auth.users as readable) |
And here is the part that explains the audit finding. Nobody gave sandbox_exec access to that sensitive table on purpose. Postgres has a feature called default privileges: a rule that says "every time a new table is created, give these roles these permissions." On Lovable Cloud, that rule hands sandbox_exec read and insert on every new table. I created a brand new table during the test and it got exactly that, automatically.
Think of it as a spare key that gets copied for every new room. It can look around and drop things off, but it can't move the furniture or throw anything away.
Can you take the key away from one table?
This is what the community member really needed to know, so I tested it end to end:
- Created a new table.
sandbox_exechad read and insert, as expected. - Revoked both from that one table:
revoke select, insert on public.my_table from sandbox_exec; - Asked the Lovable agent to run a migration on that same table (add a column), insert a row with its data tool, and read it back.
- Checked the permissions again.
The migration, the insert and the read all worked. And the revoke was still in place afterwards, because default privileges only fire when a table is created. Your existing table stays the way you left it.
They applied the same narrow revoke in their staging project, their privilege checks and automated tests passed, and they added a check to every deploy so they'd notice if it ever came back. That check turned out to be the important part. With Lovable's answer above, the narrow revoke is best understood as temporary: it survives your migrations, but the platform can put the grant back the next time it refreshes the role, and it is not a supported configuration.
Can you change the rule itself?
The third person to ask about this was running a formal audit and wanted to go one step further: instead of revoking table by table, can you change the default privileges rule so new tables stop handing sandbox_exec the key in the first place? And does Lovable put the rule back?
I tested that too. In the SQL editor, as postgres:
alter default privileges for role postgres in schema public
revoke select, insert on tables from sandbox_exec;
alter default privileges for role postgres in schema public
revoke select, usage on sequences from sandbox_exec;
Then I created a table by hand (no sandbox_exec access, as expected) and, more importantly, asked the Lovable agent to create another table through its own migration tool. The migration applied without complaint, the new table came out with no sandbox_exec grants at all, and the default privileges rule was still the way I left it afterwards.
So on the migration path, Lovable does not re-apply the grant. That is the path you hit every day. The path that does put it back, per Lovable's written answer, is the role refresh that runs when the agent starts working on the project and finds the stored setup missing or out of date. My test did not trip it, which is consistent with the setup being current at the time. The morning after the answer came in I tried harder: three more agent turns, one of them running psql from the sandbox, a pause and wake-up (on Lovable Cloud a wake-up is a restore from backup, the UI says so while it runs), and an instance resize up and back down. None of it put the grants back. So whatever marks the stored setup as out of date is rarer than any of those, but Lovable says it exists, for instance when they update the privilege template. Treat this as a change you monitor, not a change you make once. If you change the rule, add it to the same per-deploy check you'd use for the revoke, and look at it again after any backend operation. I'd also keep the change scoped to public, not the whole database. And revoke only from sandbox_exec, exactly as written above: that same default privileges entry (the one set by postgres) is also where anon, authenticated and service_role get their access to new tables. Dropping the whole entry would leave every new table unreachable from your app.
Check your own project in five minutes
All of this runs in the Cloud SQL editor (More > Database > Manage > SQL editor). None of these queries change anything.
Who am I right now?
select current_user;
Which roles can skip RLS?
select rolname, rolbypassrls, rolsuper, rolcanlogin
from pg_roles
where rolname in ('postgres', 'sandbox_exec', 'service_role',
'supabase_read_only_user', 'authenticated', 'anon');
Who can do what on a specific table? Swap in your table name:
select r.rolname,
has_table_privilege(r.rolname, 'public.my_table', 'SELECT') as can_read,
has_table_privilege(r.rolname, 'public.my_table', 'INSERT') as can_insert,
has_table_privilege(r.rolname, 'public.my_table', 'UPDATE') as can_update,
has_table_privilege(r.rolname, 'public.my_table', 'DELETE') as can_delete
from pg_roles r
where r.rolname in ('sandbox_exec', 'service_role', 'authenticated', 'anon');
What will new tables hand out automatically?
select pg_get_userbyid(defaclrole) as set_by,
defaclnamespace::regnamespace as schema,
defaclobjtype as object_type,
defaclacl as grants
from pg_default_acl;
Who has actually run queries, and as which role? This is how I caught sandbox_exec in the act:
select pg_get_userbyid(userid) as role, count(*) as distinct_queries, sum(calls) as calls
from extensions.pg_stat_statements
group by 1 order by 3 desc;
If authenticated or anon show up with access on a table that has no RLS policies, that matters a lot more than sandbox_exec, and it's the first thing I'd fix.
If you want to lock down a sensitive table
Once you've looked, here are the three things I'd consider for a table like account approvals, from lightest to strongest.
1. The narrow revoke. Tested above, and now officially unsupported: it works until the platform refreshes the role. Only on the table you care about, never on the role itself, and only with a check that tells you when it comes back.
revoke select, insert on public.my_table from sandbox_exec;
2. A trigger guard (the one that holds). RLS can be skipped by roles with BYPASSRLS, but triggers can't. A trigger that refuses writes unless your approval function raised a flag first will stop accidental or automated writes from any role, including postgres:
create or replace function public.guard_my_table()
returns trigger language plpgsql as $$
begin
if coalesce(current_setting('app.approval_ctx', true), '') <> 'on' then
raise exception 'writes to % only go through the approval function', tg_table_name;
end if;
return coalesce(new, old);
end $$;
create trigger guard_my_table
before insert or update or delete on public.my_table
for each row execute function public.guard_my_table();
Inside your approval function, right before it writes: perform set_config('app.approval_ctx', 'on', true);
To be clear about what this is: it's a guardrail against writes that didn't come through your approval flow, like an agent "helpfully" fixing data. Someone with full SQL access could still set the flag themselves, so it isn't a wall against a person who has your admin access.
3. Ask before the agent writes. In Account settings > Preferences > Agent permissions > Cloud, set "Change data" to "Ask each time." Fun side discovery while testing: when I asked the agent to drop my test table, it refused, because "Execute backward incompatible database migrations" was blocked in those same settings. Good to know it's there.
One thing to be clear about, because someone asked this week whether "Never allow" on those toggles isolates the agent from production data: it doesn't, and it isn't meant to. The toggles decide which tools the agent can see. They don't change what postgres, sandbox_exec or service_role are allowed to do in the database, so anything that still runs with those roles gets in. Think of it as hiding the keys, not changing the locks. If you need the agent kept away from production, the real answer is a separate backend: build on a remix with seed data, and run production on a project the agent is not connected to.
What I still don't know
I'd rather tell you where my testing ends than pretend it doesn't.
sandbox_exec still isn't in the public docs, but the written answer above closes most of the gaps: what uses it, and why revokes come back. What I can't tell you is how often that refresh runs, or whether the privilege template changes without notice. One detail did not match on my project: Lovable lists USAGE on the auth schema, and mine doesn't have it (the table grants are there, the schema usage isn't). That could simply be an older template, which is exactly the situation the refresh exists to fix, so don't count on the dormant state either. The auditor who saw a revoke come back in their test environment most likely hit that refresh; a backup restore of the old Test and Live beta (retired October 23, 2026) is the runner-up explanation.
If you need the official answer on your own paperwork for a compliance review, email support@lovable.dev with your project ID. The answer quoted above was given to another builder and shared in the community, so get your own copy. I have asked the Lovable team to publish it in the docs.
If you ran into this too
If your audit turned up the same role, or something else in your database you don't recognize, come say hi in the Lovable Discord. Bring the query output and we'll look at it together.
And if you have more questions, or you have information I don't (especially if your written answer from Lovable differs from the one above, or you catch the role refresh in the act), email me at hello@carolmonroe.com or leave a comment below. I'll fold what I learn back into this post. Knowing who can touch your data is just a good habit.
Reader margin
What stayed with you?
Leave one small mark in the margin.
Notes from readers
The margin is open. You can leave the first note.
Stuck with something like this? I fix it daily → Services