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:

  • usage on the net schema, for PUBLIC.
  • All privileges on the tables and sequences in net, for PUBLIC.
  • execute on the net functions, for PUBLIC on most projects. This is the Postgres default for functions. For the projects where it isn’t, see Which roles can use pg_net.
  • usage on net, for postgres, anon, authenticated, service_role, and supabase_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.

RoleCan use pg_netReason
anon, authenticated, service_roleNo, by defaultThese 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 loginYesThe 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 call net.http_get or net.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:

EventGrants applied
create extension pg_netThe 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 upgradeThe 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 WebhooksThe same usage grants again.
Enabling pg_net 0.11 or earlier on a projectexecute 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_type
from pg_namespace n
cross join lateral aclexplode(n.nspacl) as acl
where n.nspname = 'net'
union all
select
c.relname,
case acl.grantee when 0 then 'PUBLIC' else acl.grantee::regrole::text end,
acl.grantor::regrole,
acl.privilege_type
from pg_class c
cross join lateral aclexplode(c.relacl) as acl
where c.relnamespace = 'net'::regnamespace
order 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 usename
from pg_stat_activity
where 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 login attribute 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 net isn’t an exposed schema. If it is, anon and authenticated can read net.http_request_queue through the PUBLIC grants, including queued credentials. The Dashboard doesn’t let you add net to 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 endpoint GET /v1/projects/{ref}/postgrest, which returns them in db_schema. With the Supabase CLI, set schemas under [api] in supabase/config.toml, then run supabase 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.

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 login from. Replace my_role, then run select rolname, rolcanlogin from pg_roles where rolname = 'my_role'; with the SQL Editor or psql. rolcanlogin is false.
  • After Option 2, run select count(*) from pg_extension where extname = 'pg_net';. The result is 0.