You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When you try to remove the default privileges that the supabase_admin role has in the public schema, Postgres rejects the statement. For example:
alter default privileges for role supabase_admin in schema public
revoke all on tables from anon, authenticated;
ERROR: 42501: permission denied to change default privileges
This page explains why the statement fails, and how to control which objects get these grants.
Overview
The pg_default_acl catalog of a Supabase project has default privileges that belong to the supabase_admin role in the public schema. They give anon, authenticated, and service_role all privileges on tables, sequences, and functions that supabase_admin creates in public.
These default privileges apply only to objects that supabase_admin creates. Tables and functions that you create get the default privileges of postgres.
You can't change the supabase_admin default privileges, but you can control which objects they apply to:
In Postgres, a role can change default privileges only for itself and for roles that it's a member of. On Postgres 15, the statement fails with must be member of role "supabase_admin" instead. For more information about which roles can change default privileges, see the Postgres documentation on ALTER DEFAULT PRIVILEGES.
The postgres role isn't a member of supabase_admin and can't become one, because Supabase reserves memberships in its internal roles. Supabase sets the supabase_admin default privileges when it creates the database. They give the Data API roles grants on the objects that supabase_admin creates in public.
The following approaches don't work:
grant supabase_admin to postgres fails with "supabase_admin" role memberships are reserved, only superusers can grant them.
set role supabase_admin fails with permission denied to set role "supabase_admin".
Revoking privileges on objects that supabase_admin owns completes, and the grants stay. Postgres prints a warning such as WARNING: no privileges could be revoked for "spatial_ref_sys", because a role can revoke only the privileges that it granted. The SQL Editor doesn't show warnings, so the statement looks successful there. For more information, see the Postgres documentation on REVOKE.
Which objects supabase_admin creates in public
supabase_admin is the internal role that Supabase uses for administrative tasks, such as upgrades. The objects that it owns in public come from extensions that you create there:
Supabase services don't create tables, views, or functions in public as supabase_admin. Auth, Storage, and Realtime keep their objects in their own schemas.
Supabase creates most extensions as supabase_admin, even when you run create extension as postgres. When one of those extensions is in public, its objects are owned by supabase_admin and get its default privileges.
Upgrades don't change the owner or grants of objects in public, or the default privileges of postgres and supabase_admin.
create extension without a schema puts the extension in public, unless the extension sets its own schema. public is the first schema in the default search path that exists. The Database Extensions page in the Supabase Dashboard installs extensions in the extensions schema, unless the extension requires its own schema. To use another schema, run create extension with a schema clause in the SQL Editor or with psql.
The Automatically enable RLS for new tables option in the Supabase Dashboard, and other event triggers that enable Row Level Security (RLS) on new tables, don't enable RLS on tables that an extension creates. Only a table's owner can enable RLS on it, so alter table public.spatial_ref_sys enable row level security fails with must be owner of table spatial_ref_sys. For more information about who can enable RLS on a table, see the Postgres documentation on ALTER TABLE.
What the Data API setting changes
The Automatically expose new tables setting in the Data API settings changes only the default privileges of postgres in public. It doesn't change the supabase_admin rows.
Turning the setting off revokes select, insert, update, and delete on tables, usage and select on sequences, and execute on functions. The following grants stay:
truncate, references, trigger, and maintain on tables
update on sequences
execute on functions for PUBLIC, which lets anon and authenticated call new functions
The setting also revokes execute from PUBLIC in the public schema, but that revoke has no effect. A default privilege for one schema can't remove the PUBLIC default for functions.
Creating a project with Automatically expose new tables disabled has the same scope, and also revokes update on sequences. For more information about the Data API setting, see Revoke default privileges.
What the extra privileges allow
The supabase_admin default privileges grant more than the Data API uses:
Through the Data API, clients read, insert, update, and delete rows and call functions.
truncate, references, trigger, and maintain need a SQL session that runs as the role. RLS policies don't apply to truncate.
The anon and authenticated roles can't connect to the database directly.
Diagnose the problem
List the default privileges
The following query lists the default privileges that postgres and supabase_admin grant to other roles in public.
Run the following SQL using the SQL Editor in the Supabase Dashboard or with psql over a direct connection. To connect with psql, see Direct connection.
selectd.defaclrole::regrole as creator,
case d.defaclobjtype
when 'r' then 'tables'
when 'S' then 'sequences'
when 'f' then 'functions'
end as object_type,
case acl.grantee when 0 then 'PUBLIC' else acl.grantee::regrole::text end as grantee,
string_agg(acl.privilege_type, ', 'order byacl.privilege_type) as privileges
from pg_default_acl d
cross join lateral aclexplode(d.defaclacl) as acl
whered.defaclnamespace='public'::regnamespace
andd.defaclrolein ('postgres'::regrole, 'supabase_admin'::regrole)
andacl.grantee<>d.defaclrolegroup by1, 2, 3order by1, 2, 3;
Read the default privileges
The creator column names the role whose new objects get the grants. The rows for postgres apply to the objects that you create, and they change when you follow Control the default privileges of your own objects.
On most projects, the rows for supabase_admin look like this:
Functions that supabase_admin creates are also executable by PUBLIC, because that's the Postgres default for functions. supabase_admin has no default privilege that removes it.
The graphql and graphql_public schemas, which the pg_graphql extension uses, have the same supabase_admin entries.
If some of these rows are missing on your project, the objects that supabase_admin creates in public don't get the grants in those rows.
Find objects in public that supabase_admin owns
The supabase_admin default privileges matter only if supabase_admin creates objects in public. The following query counts the objects in public that supabase_admin owns, grouped by the extension they belong to.
Run the following SQL using the SQL Editor in the Supabase Dashboard or with psql over a direct connection. To connect with psql, see Direct connection.
with objects as (
select'pg_class'::regclass as classid, c.oidas objid,
case c.relkind
when 'S' then 'sequence'
when 'v' then 'view'
when 'm' then 'materialized view'
else 'table'
end as kind
from pg_class c
wherec.relnamespace='public'::regnamespace
andc.relowner='supabase_admin'::regrole
andc.relkindin ('r', 'p', 'v', 'm', 'f', 'S')
union allselect'pg_proc'::regclass, p.oid, 'function'from pg_proc p
wherep.pronamespace='public'::regnamespace
andp.proowner='supabase_admin'::regrole
)
select
coalesce(e.extname, '(none)') as extension,
o.kind,
count(*) as objects
from objects o
left join pg_depend d
ond.classid=o.classidandd.objid=o.objidandd.deptype='e'left join pg_extension e one.oid=d.refobjidgroup by1, 2order by1, 2;
Read the object list
No rows:supabase_admin owns no tables, views, sequences, or functions in public.
Control the default privileges of your own objects
The default privileges of postgres decide which grants your new tables, sequences, and functions get.
The revoke execute on functions from public statement in the following SQL doesn't name a schema, so it applies to functions that postgres creates in every schema. After you run it, grant execute on new functions to the roles that call them, including helper functions in other schemas that RLS policies call.
The following SQL removes all default grants to the API roles for objects that postgres creates in public.
Run the following SQL using the SQL Editor in the Supabase Dashboard or with psql over a direct connection. To connect with psql, see Direct connection.
alter default privileges for role postgres in schema public
revoke all on tables from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke all on sequences from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke all on functions from anon, authenticated, service_role;
alter default privileges for role postgres
revoke execute on functions from public;
These statements change only objects that postgres creates after you run them. Existing objects keep their grants.
After the change, the API roles can't access a new table until you grant access to it. For the grants and RLS steps, see Grant access explicitly. If you create objects as another role, such as a role your migration tool uses, change the default privileges for that role too.
To give new objects grants again, grant the privileges that you want them to get. For example, to give new tables select, insert, update, and delete for the API roles, and to let PUBLIC execute new functions again, run:
alter default privileges for role postgres in schema public
grantselect, insert, update, deleteon tables to anon, authenticated, service_role;
alter default privileges for role postgres
grant execute on functions to public;
Move extensions out of the public schema
The search path of the postgres role and the default extra search path of the Data API both include extensions, so queries that call extension functions without a schema name keep working after the move. Roles that you create don't get extensions in their search path unless you set it.
Run the following SQL using the SQL Editor in the Supabase Dashboard or with psql over a direct connection. To connect with psql, see Direct connection.
To move the extensions that are already in public:
List the extensions in public, and whether each one can move to another schema:
select extname, extversion, extrelocatable as can_move
from pg_extension
where extnamespace ='public'::regnamespace;
List your functions that set their own search_path without extensions:
selectp.oid::regprocedure as function_name, p.proconfigas settings
from pg_proc p
wherep.proowner='postgres'::regrole
andp.prorettype<>'event_trigger'::regtype
and not exists (
select1from pg_depend d
whered.classid='pg_proc'::regclass andd.objid=p.oidandd.deptype='e'
)
and exists (
select1from unnest(p.proconfig) as setting
where setting like'search_path=%'and setting not like'%extensions%'
);
If a function in the result calls the extension's functions without a schema name, it can't find them after the move. Functions that don't call the extension need no change. For each function that does, add extensions to the end of its existing search path. For example, for a function whose settings value is {search_path=public}, run:
alterfunctionpublic.my_function(integer)
set search_path = public, extensions;
In code that calls an extension function with the public. prefix, such as public.similarity(a, b), replace the prefix with extensions..
For each extension where can_move is true, run:
alter extension pg_trgm set schema extensions;
The extension's objects keep their grants after the move. The Data API doesn't expose extensions by default, and the Dashboard doesn't let you add it to the exposed schemas.
An extension where can_move is false, such as PostGIS, can't move with alter extension. Dropping and re-creating it also drops the columns, indexes, and functions that depend on it. To move PostGIS without dropping it, contact Supabase Support.
Confirm the problem is solved
If PostGIS is still in public, the object query and the Security Advisor still show its objects and its Extension in Public finding.
Open the Security Advisor and click Rerun linter. From a terminal, run supabase db advisors --linked --type security with the Supabase CLI. The Extension in Public finding no longer appears.
Run the query in List the default privileges. If you removed all default grants for postgres, the query returns rows only for supabase_admin.
If you ran the revoke execute statement in Control the default privileges of your own objects, check it. Run the following SQL using the SQL Editor in the Supabase Dashboard or with psql over a direct connection. To connect with psql, see Direct connection. It returns {postgres=X/postgres}, which means that only postgres can execute new functions by default:
select defaclacl
from pg_default_acl
where defaclrole ='postgres'::regrole
and defaclnamespace =0and defaclobjtype ='f';
Prevent the problem
To keep supabase_admin from creating objects in public, create extensions in the extensions schema. In your migrations, always name the schema:
create extension if not exists pg_trgm with schema extensions;
Run the queries in Diagnose the problem again after you enable an extension, and after a restore or an upgrade.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
When you try to remove the default privileges that the
supabase_adminrole has in thepublicschema, Postgres rejects the statement. For example:This page explains why the statement fails, and how to control which objects get these grants.
Overview
The
pg_default_aclcatalog of a Supabase project has default privileges that belong to thesupabase_adminrole in thepublicschema. They giveanon,authenticated, andservice_roleall privileges on tables, sequences, and functions thatsupabase_admincreates inpublic.These default privileges apply only to objects that
supabase_admincreates. Tables and functions that you create get the default privileges ofpostgres.You can't change the
supabase_admindefault privileges, but you can control which objects they apply to:supabase_adminfrom creating objects inpublic, move extensions out of the public schema.Why postgres can't change them
In Postgres, a role can change default privileges only for itself and for roles that it's a member of. On Postgres 15, the statement fails with
must be member of role "supabase_admin"instead. For more information about which roles can change default privileges, see the Postgres documentation onALTER DEFAULT PRIVILEGES.The
postgresrole isn't a member ofsupabase_adminand can't become one, because Supabase reserves memberships in its internal roles. Supabase sets thesupabase_admindefault privileges when it creates the database. They give the Data API roles grants on the objects thatsupabase_admincreates inpublic.The following approaches don't work:
grant supabase_admin to postgresfails with"supabase_admin" role memberships are reserved, only superusers can grant them.set role supabase_adminfails withpermission denied to set role "supabase_admin".supabase_adminowns completes, and the grants stay. Postgres prints a warning such asWARNING: no privileges could be revoked for "spatial_ref_sys", because a role can revoke only the privileges that it granted. The SQL Editor doesn't show warnings, so the statement looks successful there. For more information, see the Postgres documentation onREVOKE.Which objects supabase_admin creates in public
supabase_adminis the internal role that Supabase uses for administrative tasks, such as upgrades. The objects that it owns inpubliccome from extensions that you create there:publicassupabase_admin. Auth, Storage, and Realtime keep their objects in their own schemas.supabase_admin, even when you runcreate extensionaspostgres. When one of those extensions is inpublic, its objects are owned bysupabase_adminand get its default privileges.public, or the default privileges ofpostgresandsupabase_admin.create extensionwithout a schema puts the extension inpublic, unless the extension sets its own schema.publicis the first schema in the default search path that exists. The Database Extensions page in the Supabase Dashboard installs extensions in theextensionsschema, unless the extension requires its own schema. To use another schema, runcreate extensionwith aschemaclause in the SQL Editor or withpsql.The Automatically enable RLS for new tables option in the Supabase Dashboard, and other event triggers that enable Row Level Security (RLS) on new tables, don't enable RLS on tables that an extension creates. Only a table's owner can enable RLS on it, so
alter table public.spatial_ref_sys enable row level securityfails withmust be owner of table spatial_ref_sys. For more information about who can enable RLS on a table, see the Postgres documentation onALTER TABLE.What the Data API setting changes
The Automatically expose new tables setting in the Data API settings changes only the default privileges of
postgresinpublic. It doesn't change thesupabase_adminrows.Turning the setting off revokes
select,insert,update, anddeleteon tables,usageandselecton sequences, andexecuteon functions. The following grants stay:truncate,references,trigger, andmaintainon tablesupdateon sequencesexecuteon functions forPUBLIC, which letsanonandauthenticatedcall new functionsThe setting also revokes
executefromPUBLICin thepublicschema, but that revoke has no effect. A default privilege for one schema can't remove thePUBLICdefault for functions.Creating a project with Automatically expose new tables disabled has the same scope, and also revokes
updateon sequences. For more information about the Data API setting, see Revoke default privileges.What the extra privileges allow
The
supabase_admindefault privileges grant more than the Data API uses:truncate,references,trigger, andmaintainneed a SQL session that runs as the role. RLS policies don't apply totruncate.anonandauthenticatedroles can't connect to the database directly.Diagnose the problem
List the default privileges
The following query lists the default privileges that
postgresandsupabase_admingrant to other roles inpublic.Run the following SQL using the SQL Editor in the Supabase Dashboard or with
psqlover a direct connection. To connect withpsql, see Direct connection.Read the default privileges
The
creatorcolumn names the role whose new objects get the grants. The rows forpostgresapply to the objects that you create, and they change when you follow Control the default privileges of your own objects.On most projects, the rows for
supabase_adminlook like this:postgres,anon,authenticated,service_roleSELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER,MAINTAINpostgres,anon,authenticated,service_roleUSAGE,SELECT,UPDATEpostgres,anon,authenticated,service_roleEXECUTEMAINTAINexists only on Postgres 17 or later.supabase_admincreates are also executable byPUBLIC, because that's the Postgres default for functions.supabase_adminhas no default privilege that removes it.graphqlandgraphql_publicschemas, which the pg_graphql extension uses, have the samesupabase_adminentries.supabase_admincreates inpublicdon't get the grants in those rows.Find objects in public that supabase_admin owns
The
supabase_admindefault privileges matter only ifsupabase_admincreates objects inpublic. The following query counts the objects inpublicthatsupabase_adminowns, grouped by the extension they belong to.Run the following SQL using the SQL Editor in the Supabase Dashboard or with
psqlover a direct connection. To connect withpsql, see Direct connection.Read the object list
supabase_adminowns no tables, views, sequences, or functions inpublic.public. To move them, see Move extensions out of the public schema.(none)as the extension: contact Supabase Support with the query result.Solve the problem
Control the default privileges of your own objects
The default privileges of
postgresdecide which grants your new tables, sequences, and functions get.The
revoke execute on functions from publicstatement in the following SQL doesn't name a schema, so it applies to functions thatpostgrescreates in every schema. After you run it, grantexecuteon new functions to the roles that call them, including helper functions in other schemas that RLS policies call.The following SQL removes all default grants to the API roles for objects that
postgrescreates inpublic.Run the following SQL using the SQL Editor in the Supabase Dashboard or with
psqlover a direct connection. To connect withpsql, see Direct connection.These statements change only objects that
postgrescreates after you run them. Existing objects keep their grants.After the change, the API roles can't access a new table until you grant access to it. For the grants and RLS steps, see Grant access explicitly. If you create objects as another role, such as a role your migration tool uses, change the default privileges for that role too.
To give new objects grants again, grant the privileges that you want them to get. For example, to give new tables
select,insert,update, anddeletefor the API roles, and to letPUBLICexecute new functions again, run:Move extensions out of the public schema
The search path of the
postgresrole and the default extra search path of the Data API both includeextensions, so queries that call extension functions without a schema name keep working after the move. Roles that you create don't getextensionsin their search path unless you set it.Run the following SQL using the SQL Editor in the Supabase Dashboard or with
psqlover a direct connection. To connect withpsql, see Direct connection.To move the extensions that are already in
public:List the extensions in
public, and whether each one can move to another schema:List your functions that set their own
search_pathwithoutextensions:If a function in the result calls the extension's functions without a schema name, it can't find them after the move. Functions that don't call the extension need no change. For each function that does, add
extensionsto the end of its existing search path. For example, for a function whosesettingsvalue is{search_path=public}, run:In code that calls an extension function with the
public.prefix, such aspublic.similarity(a, b), replace the prefix withextensions..For each extension where
can_moveistrue, run:alter extension pg_trgm set schema extensions;The extension's objects keep their grants after the move. The Data API doesn't expose
extensionsby default, and the Dashboard doesn't let you add it to the exposed schemas.An extension where
can_moveisfalse, such as PostGIS, can't move withalter extension. Dropping and re-creating it also drops the columns, indexes, and functions that depend on it. To move PostGIS without dropping it, contact Supabase Support.Confirm the problem is solved
If PostGIS is still in
public, the object query and the Security Advisor still show its objects and its Extension in Public finding.Run the query in Find objects in public that supabase_admin owns. It returns no rows.
Open the Security Advisor and click Rerun linter. From a terminal, run
supabase db advisors --linked --type securitywith the Supabase CLI. The Extension in Public finding no longer appears.Run the query in List the default privileges. If you removed all default grants for
postgres, the query returns rows only forsupabase_admin.If you ran the
revoke executestatement in Control the default privileges of your own objects, check it. Run the following SQL using the SQL Editor in the Supabase Dashboard or withpsqlover a direct connection. To connect withpsql, see Direct connection. It returns{postgres=X/postgres}, which means that onlypostgrescan execute new functions by default:Prevent the problem
To keep
supabase_adminfrom creating objects inpublic, create extensions in theextensionsschema. In your migrations, always name the schema:Run the queries in Diagnose the problem again after you enable an extension, and after a restore or an upgrade.
Related topics
All reactions