Skip to content

service_role treated as anon, by GraphiQL #642

Description

@jeremybradbury

Describe the bug

If you try to run queries as the service_role, it ends up mapping to anon, because the query only checks for explicit SELECT allows, and doesn't also check if it's the service user, which only has BYPASSRLS, and no specific table grants.

I'm surprised this hasn't been reported or fixed, in the right repo yet (prob in studio or main, even if the issue is here). I'll try to follow this quickly with a PR, but it's gonna take some testing. Below is a breakdown of what's wrong and how.

To Reproduce
Steps to reproduce the behavior:

  1. ensure analytics is true on first run or the settings tab breaks (this grabs logflare and vector images, that the maintainers probably always have on).
  2. run supabase@latest locally
  3. add some data to a table
  4. query it as the service_user and it's identical to anon
  5. same query but impersonated (and proper RLS for SELECT setup), will work.

Expected behavior
GraphiQL should return data from any and all tables, show full public schema introspection.

Screenshots
I'll try to come back with these, along with the solution.

Versions:

  • PostgreSQL: 15.8
  • pg_graphql: 1.5.11
  • pg_graphql commit ref: ea2ab60
    nodejs deps:
    @supabase/supabase-js@2.105.4
    @supabase/auth-js@2.105.4
    @supabase/storage-js@2.105.4
    @supabase/realtime-js@2.105.4
    @supabase/functions-js@2.105.4
    @supabase/postgrest-js@2.105.4

Additional Info
The pg_graphql extension generates the entire GraphiQL schema view by executing an internal SQL function that scans Postgres catalogs. By default, the code inside the extension determines whether a table should be exposed to the GraphiQL panel by checking if the active role has explicit table-level SELECT grants. This is how the pg_graphql C/SQL compiler filters table visibility:
WHERE has_table_privilege(current_setting('role'), table_id, 'SELECT')

Why service_role Treats You as Unauthenticated

  • When you Impersonate a User the GraphiQL panel sets current_setting('role') to 'authenticated'. In Supabase's default template, the authenticated role possesses explicit SELECT privileges on the public schema. Therefore, the function returns true, and pg_graphql compiles the schema docs cleanly.
  • When you switch to service_role, current_setting('role') switches to 'service_role'.
  • Because the service_role is a specialized system token built to bypass Row Level Security (RLS), it doesn't have standard, explicit relational table SELECT grants applied to individual tables by default. It reads data during runtime purely because its BYPASSRLS superuser attribute overrides access limits during raw execution.

Because has_table_privilege('service_role', table_id, 'SELECT') checks for explicit relational grants rather than bypass attributes, it evaluates to false. The extension concludes that the service_role string has no visible entities, returns an entirely empty schema map, and causes GraphiQL to drop you into an unauthenticated fallback state.

Metadata

Metadata

Assignees

Labels

triage-requiredPending triage from maintainers

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions