Revoking access to pg_net objects has no effect: no privileges could be revoked
When you run revoke on the net schema or its objects as the postgres role, the statement completes but access stays the same. Postgres prints a warning instead of an error:
WARNING: no privileges could be revoked for "net"The same warning appears for http_request_queue, _http_response, and the net.http_* functions. A grant on these objects prints no privileges were granted. The SQL Editor doesn’t display warnings, so the statement looks successful there.
This page explains why the revoke has no effect, how to check which roles can use pg_net, and how to limit what pg_net exposes.
Overview
Supabase creates the pg_net extension as the supabase_admin role, so supabase_admin owns the net schema and everything in it. In Postgres, a role can revoke only the privileges that it granted. For more information, see the Postgres documentation on REVOKE.
The net objects have the following default grants:
usageon thenetschema, forPUBLIC.- All privileges on the tables and sequences in
net, forPUBLIC. executeon thenetfunctions, forPUBLICon most projects. This is the Postgres default for functions. For the projects where it isn’t, see Which roles can use pg_net.usageonnet, forpostgres,anon,authenticated,service_role, andsupabase_functions_admin. Supabase grants it when the extension is created.
The postgres role made none of these grants, so its revoke finds nothing to remove. It holds the privileges without the grant option, so its grant to another role does nothing either, and Postgres reports no privileges were granted.
A grant to PUBLIC applies to every role, including roles you create later. Postgres has no way to exclude a single role from a PUBLIC grant.
Which roles can use pg_net
The following table shows how each kind of role can reach the net schema.
| Role | Can use pg_net | Reason |
|---|---|---|
anon, authenticated, service_role | No, by default | These roles can’t connect to the database directly, and the Data API doesn’t expose net by default. |
postgres and any role you create with login | Yes | The role can read and change rows in net.http_request_queue and net._http_response. The worker sends each row in the queue. |
On some projects, only postgres, anon, authenticated, service_role, and supabase_functions_admin can call net.http_get and net.http_post. For the events that revoke execute from PUBLIC, see When the default grants are applied. A role you create can still add rows to the queue, and the worker sends them. To check a role, run the query in Check what one role can do.
PUBLIC can also call net.worker_restart(), so any role that can connect to your database can restart the pg_net worker.
A row in net.http_request_queue holds the request’s URL, headers, and body until the pg_net worker sends it. To check what a queued request exposes, see Database roles can read request headers queued by pg_net.
Which roles depend on the default grants
Apart from the owner, supabase_admin, the tables in net have grants only for PUBLIC. By default, net.http_get and net.http_post run as the role that calls them, so the following roles reach the tables through the PUBLIC grants:
postgres, for your functions and pg_cron jobs that callnet.http_getornet.http_post.supabase_functions_admin, for Database Webhooks.
The pg_net worker also reads and writes the tables. It connects as the role in the pg_net.username setting. When that role is postgres, the worker depends on the PUBLIC grants too. On some projects the setting is empty, and the worker connects as the bootstrap superuser, supabase_admin, which doesn’t need them. To check which role the worker uses, see Check which role the pg_net worker uses.
Removing the PUBLIC grants on the net tables breaks pg_net, because these roles reach the tables only through them. Without the grants, a request function called as postgres fails with permission denied for table http_request_queue, which also breaks pg_cron jobs that call pg_net.
When the default grants are applied
Dropping and re-creating the extension doesn’t remove the grants, because create extension pg_net applies all of them again.
The following events apply the default grants:
| Event | Grants applied |
|---|---|
create extension pg_net | The extension’s install script makes the PUBLIC grants. The issue_pg_net_access event trigger then runs extensions.grant_pg_net_access(), which grants usage on net to the roles listed in Overview. |
| A major Postgres version upgrade | The same usage grants again. The upgrade also revokes execute on net.http_get and net.http_post from PUBLIC, and grants it to postgres, anon, authenticated, service_role, and supabase_functions_admin. |
| Turning on Database Webhooks | The same usage grants again. |
| Enabling pg_net 0.11 or earlier on a project | execute on net.http_get and net.http_post is revoked from PUBLIC and granted to the same five roles as after a major upgrade. |
Diagnose the problem
List the grants on the net schema and its tables
The following query lists the privileges on the net schema and its tables, and the role that granted each one.
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.
select 'schema net' as object, case acl.grantee when 0 then 'PUBLIC' else acl.grantee::regrole::text end as grantee, acl.grantor::regrole as grantor, acl.privilege_typefrom pg_namespace ncross join lateral aclexplode(n.nspacl) as aclwhere n.nspname = 'net'union allselect c.relname, case acl.grantee when 0 then 'PUBLIC' else acl.grantee::regrole::text end, acl.grantor::regrole, acl.privilege_typefrom pg_class ccross join lateral aclexplode(c.relacl) as aclwhere c.relnamespace = 'net'::regnamespaceorder by 1, 2, 4;Every row has supabase_admin as the grantor.
Check what one role can do
Replace my_role with the name of the role.
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.
select has_schema_privilege('my_role', 'net', 'usage') as schema_usage, has_table_privilege('my_role', 'net.http_request_queue', 'select') as read_queue, has_function_privilege( 'my_role', 'net.http_post(text, jsonb, jsonb, jsonb, integer)', 'execute' ) as call_http_post;Check which role the pg_net worker uses
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.
select usenamefrom pg_stat_activitywhere backend_type like 'pg_net%';If the query returns no rows when you run it as postgres, the worker isn’t running. Roles without the pg_read_all_stats privilege can’t see the worker, so the query returns no rows for them either.
Solve the problem
Supabase doesn’t support changing the grants on pg_net objects for individual projects, so treat the default grants as a platform constraint. Instead, limit which roles can reach pg_net, or drop pg_net if you don’t use it.
Option 1: Limit which roles can reach pg_net
To reduce what pg_net exposes:
- Give the
loginattribute only to roles for services that you trust to send HTTP requests from your database. - Connect services that only read or write your data through the Data API. Don’t give them a database role that can connect directly.
- Treat any credential you send in request headers as readable by every role that can connect to your database.
- Check that
netisn’t an exposed schema. If it is,anonandauthenticatedcan readnet.http_request_queuethrough thePUBLICgrants, including queued credentials. The Dashboard doesn’t let you addnetto the exposed schemas in your Data API settings, and it reports a protected schema that is already exposed. To check the exposed schemas from a terminal, call the Management API endpointGET /v1/projects/{ref}/postgrest, which returns them indb_schema. With the Supabase CLI, setschemasunder[api]insupabase/config.toml, then runsupabase config push.
Option 2: Drop pg_net if you don’t use it
Dropping the extension removes the net schema and all of its grants.
Dropping pg_net deletes unsent requests and stored responses, and stops Database Webhooks, which send their requests through pg_net. Before you drop it, check that:
net.http_request_queueis empty.- Your project has no Database Webhooks.
- None of your functions or pg_cron jobs call
net.http_getornet.http_post.
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.
drop extension pg_net;Confirm the problem is solved
- After Option 1, check each role that you removed
loginfrom. Replacemy_role, then runselect rolname, rolcanlogin from pg_roles where rolname = 'my_role';with the SQL Editor orpsql.rolcanloginisfalse. - After Option 2, run
select count(*) from pg_extension where extname = 'pg_net';. The result is0.
Related topics
- Database Webhooks
- Postgres roles
- Permission denied for table http_request_queue
- Revoking privileges on Storage, pg_cron, Vault, and extension objects has no effect