Hosted Supabase: supported way to remove unnecessary TRUNCATE privileges from managed Storage tables and default privileges #51387
Replies: 3 comments 3 replies
|
@yasumatsucoating-hash , Your analysis looks right to me, so mostly I'll just confirm the mechanics and flag the one part that isn't really a community-answerable thing. First, where these grants even come from: they're not deliberate TRUNCATE grants, they're fallout from the blanket grant all the project bootstrap does for convenience. It runs something like grant all privileges on all tables in schema public to anon, authenticated, service_role plus the matching alter default privileges ... grant all on tables. all on a table includes TRUNCATE, TRIGGER and REFERENCES, so those three tag along everywhere. The storage.* ones are the same thing, just applied by the Storage service's own migrations (which run as supabase_storage_admin). And yeah, you already worked out the important bit: it's not reachable. anon/authenticated are NOLOGIN, you only get to them via authenticator doing a SET ROLE through PostgREST/Storage, and PostgREST only ever turns HTTP verbs into SELECT/INSERT/UPDATE/DELETE. Nothing emits a TRUNCATE. So stripping it is fine as defense-in-depth, it's just not closing an actual hole. On your actual questions: 1 & 2 : whether you can revoke it off the storage tables comes down to one thing: is your postgres a member of supabase_storage_admin? To revoke you need to own the object, be a member of the owner, or be superuser, and on hosted projects postgres isn't superuser. Check with:
If that's true: If it's false, leave it — don't go changing owners or escalating, that's what the support ticket is for.
Two gotchas: it only applies to tables created after you run it, and only ones created by postgres — anything created by another role follows that role's defaults. Existing tables need a one-off revoke truncate on all tables in schema public from anon, authenticated. And if you actually want least privilege, I'd revoke the whole all default and grant back just select/insert/update/delete, otherwise TRIGGER and REFERENCES are still coming through the exact same way TRUNCATE was — it's just a bigger departure from the defaults, so your call.
|
|
@H1manshu01's steps for your First, Supabase Storage is already removing these grants. A migration merged on Sept 16 revokes Second, the Two small additions for your audit trail:
To confirm the end state: select n.nspname, c.relname
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind in ('r', 'p')
and n.nspname not in ('pg_catalog', 'information_schema')
and (has_table_privilege('anon', c.oid, 'TRUNCATE')
or has_table_privilege('authenticated', c.oid, 'TRUNCATE'));Anything that still shows up after that is worth adding to SU-498042. |
|
Short split on this one. Your own tables in public are yours to harden. The three storage tables and the supabase_admin default rows are not, and for the storage tables you do not need to win that fight. On storage.buckets, storage.objects and storage.buckets_analytics, Supabase Storage already removes exactly this. Migration 0073 in supabase/storage (PR 1395, first released in v1.77.7) revokes TRUNCATE, REFERENCES and TRIGGER from anon and authenticated on those three tables, and it also adds a default privilege rule for future storage tables. So the supported path is that Storage upgrade reaching your project, not a manual revoke from postgres. For SU-498042 I would cite PR 1395 and migration 0073 and ask when it lands on your project, with your pg_has_role result and the check query below attached. Why postgres cannot just revoke them: the tables are owned by supabase_storage_admin, and Postgres only lets the owner, a member of the owner role, a grant option holder, or a superuser revoke. An April 2025 platform migration removed postgres membership in supabase_storage_admin, so on a current project this should come back false: select pg_has_role('postgres', 'supabase_storage_admin', 'member'); If it is false, do not change ownership or look for a side door. Leave those three tables to Storage and support. Removing TRUNCATE alone cannot change SELECT, INSERT, UPDATE or DELETE behavior, because Postgres checks each command against its own privilege, and the owner keeps its privileges as owner. That is also why this is defense in depth rather than a live hole: PostgREST has no TRUNCATE operation, so there is nothing for anon or authenticated to call. For default privileges, Postgres lets you change only your own defaults and the defaults of roles you are a member of. So the postgres rows for public and storage are yours to change, and the supabase_admin rows for public, graphql and graphql_public are platform managed. Leave those alone. For your future tables, this is the self serve part: alter default privileges for role postgres in schema public revoke truncate on tables from anon, authenticated; That only affects tables created later by postgres. Existing tables still need a one off revoke, and tables created by another role follow that role defaults instead. The same ALL grant is also where TRIGGER and REFERENCES came from, so decide if you want to trim those too. To confirm the end state: select n.nspname, c.relname from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind in ('r', 'p') and n.nspname not in ('pg_catalog', 'information_schema') and (has_table_privilege('anon', c.oid, 'TRUNCATE') or has_table_privilege('authenticated', c.oid, 'TRUNCATE')); Anything still listed there after the revoke and after the Storage upgrade is worth adding to the ticket. |
Uh oh!
There was an error while loading. Please reload this page.
Title:
Hosted Supabase: supported way to remove unnecessary TRUNCATE privileges from managed Storage tables and default privileges
Hi,
We are performing a security hardening review on a hosted Supabase project and would like guidance on the supported way to handle unnecessary TRUNCATE privileges.
We found two separate issues.
Existing Storage-managed tables with TRUNCATE granted to both
anonandauthenticated:storage.bucketsstorage.objectsstorage.buckets_analyticsThese tables are owned by
supabase_storage_admin.Across the project,
anon/authenticatedhave TRUNCATE on 50 tables total:publicstorageWe also found TRUNCATE in default privileges for:
postgres / publicpostgres / storagesupabase_admin / publicsupabase_admin / graphqlsupabase_admin / graphql_publicImportant:
We did NOT find a
supabase_storage_admin / storagedefault TRUNCATE grant.What we verified:
Local validation:
However, the local Storage test is NOT proven fully equivalent to hosted Supabase Storage or S3-backed behavior.
Hosted limitation:
postgrescannot revoke some Storage-owner-derived grantssupabase_admincannot be altered by our normal project roleQuestions:
Is it supported on hosted Supabase to revoke TRUNCATE from
anon/authenticatedon:storage.bucketsstorage.objectsstorage.buckets_analyticsIf yes, what is the supported execution path or role?
Could this interfere with Storage runtime, future Storage migrations, hosted maintenance, or S3-backed behavior?
If customers should not modify these managed Storage grants, should we leave them unchanged and harden only application-owned
publictables?Is there a supported way to remove TRUNCATE from the creator-specific default privileges listed above?
Is there a supported way to prevent future application-owned tables from receiving TRUNCATE while leaving Supabase-managed defaults untouched?
Our goal is to:
We have also opened Support ticket:
SU-498042
Thanks.
All reactions