Skip to content
Integration / Supabase

Connect Supabase to CYBRET.

CYBRET reads your Supabase project schema, the RLS policies on every table, the auth schema, and the edge function code. The result is a reachability model that can answer the only question that matters: "Can the anon role read this row?"

What CYBRET sees on Supabase

Schema, policy, and the functions that run alongside.

Supabase is Postgres plus a thin layer that exposes it through PostgREST and the auth schema. Most security failures in Supabase apps happen because that thin layer is misunderstood. CYBRET reads both layers in one pass.

  • Database schema: tables, columns, foreign keys, views, materialized views, types, and the publication tables that PostgREST exposes.
  • Row Level Security: the existence and content of every RLS policy, including the USING and WITH CHECK expressions, the role they apply to, and the tables they secure (or don't).
  • Roles and grants: the anon, authenticated, and service_role defaults plus any custom roles, and the table / column grants attached to each.
  • Auth schema: identity providers, multi-factor configuration, JWT settings, the auth.users shape, hooks (signup, before-token, after-token), session lifetime.
  • Edge Functions: source code, env-var schema (names only), invocation triggers, allowed JWT verifiers.
  • Storage: buckets, public / private setting, RLS policies on storage.objects and storage.buckets.
  • Telemetry: Postgres audit log via the pgaudit extension (where enabled), Supabase log drains for runtime correlation.
Three real attack-path examples

What we find in the first week.

Supabase findings cluster around RLS misconfiguration and service-role misuse. The platform is correct by default in most places; the failures we see are almost always in custom paths the team added.

1. RLS not enabled on a table that PostgREST exposes

Table profiles has ENABLE ROW LEVEL SECURITY off → anon role has SELECT grant via the default GRANT SELECT TO authenticated chain → PostgREST returns every row to any client.

The team added the table after the initial setup and forgot to enable RLS. CYBRET reads the publication membership, the RLS state per table, and the role grants. It surfaces the table as a public read endpoint and shows the curl that confirms it. The fix is one statement: ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; followed by an explicit policy.

2. Edge function uses service_role key client-side

Edge function bundle imports SUPABASE_SERVICE_ROLE_KEY → gets shipped in the client bundle of a Next.js app → service role bypasses every RLS policy.

The function reads the service-role key for a privileged maintenance path. Somewhere in the build, the same key ends up in a client-side chunk. CYBRET reads the function source, the bundle config, and the deployed bundle to confirm exposure. Even the optimistic case (key only in server bundle) is flagged because the blast radius of any server-side request smuggling is the entire database.

3. Permissive USING ( true ) RLS policy

Policy orders_select on orders has USING ( true ) → intended as a temporary unblock during development → shipped to prod.

Anyone authenticated can read every order. CYBRET reads the policy expression as text, classifies true /1=1 / equivalent boolean tautologies, and surfaces them across the schema. It also flags the asymmetric case whereUSING is restrictive but WITH CHECK is permissive (or vice versa).

How the integration works

One read-only Postgres role plus the management API.

Supabase exposes a project-level management API and a Postgres connection. CYBRET uses both: the management API for project metadata and edge function code, and a read-only Postgres role for schema and policy introspection.

  1. Open the CYBRET console and go to Integrations → Supabase.
  2. Click Connect Supabase. Enter your project ref.
  3. Run the supplied SQL to create a read-only role: cybret_readonly with SELECT on the system catalogs and USAGE on the relevant schemas. The script is idempotent.
  4. Generate a personal access token in the Supabase dashboard with project-scoped read access; paste it into the CYBRET console. The token is held in your CYBRET vault.
  5. Optional: configure a log drain to forward auth, storage, and edge-function logs to the CYBRET runtime endpoint.
  6. The first inventory pass typically completes inside ten minutes for projects of typical size. Findings appear in Exposure Intelligence as schema indexes.

Disconnect by dropping the read-only role and revoking the management token. Both are in your project; CYBRET cannot retain access if you revoke them.

Permissions and data scope

Read-only at the database; metadata-only at the management API.

The Postgres role is SELECT on system catalogs (pg_class, pg_policy, pg_attribute, etc.) and USAGE on user schemas. It does not have read access to user tables' data; CYBRET reasons about RLS without reading row contents. The management API token has project-scoped read access; we never request write to functions, secrets, or buckets.

For runtime detection, log drains forward auth, edge-function, and storage logs to the customer-tenancy CYBRET deployment. Customers who run Supabase self-hosted (rather than the Supabase cloud) connect via a private Postgres connection from the CYBRET tenant; the same role model applies.

How Supabase maps to the three CYBRET products

Where Supabase data shows up in the platform.

Exposure Intelligence is the product that benefits most. Supabase paths are unusual: the database and the API are tightly coupled, and most exploitable findings live in the join. CYBRET treats RLS policies as code, the auth schema as identity, and the edge functions as application code in one graph.

Validation uses Supabase context to confirm RLS bypass paths with non-destructive reads from the anon and authenticated roles. We never run a destructive query against production; validation runs against the live RLS layer using role-correct tokens that prove the read is permitted.

Runtime Detection consumes log drains to alert on identity events tied to surfaced paths. Service-role key invocations from unexpected origins, sudden bursts of reads on a previously low-traffic table, or authenticator changes become runtime events linked to the affected paths.

FAQ

Questions security engineers actually ask.

How does authentication work?

A read-only Postgres role for schema introspection plus a project-scoped Supabase personal access token for the management API. Both are stored in a per-tenant CYBRET vault. We do not hold the service_role key.

Where does the data go?

Inside the CYBRET deployment in your VPC. EU customers run an EU-resident control plane; US customers run a US one. We do not move Supabase data to a multi-tenant SaaS backend.

Do you read user data?

No. The role we use has access to system catalogs and metadata; it does not have read on user tables. We reason about RLS without reading rows. Edge function source is read; environment values are not.

Self-hosted Supabase?

Supported. The same role model applies. CYBRET reaches your self-hosted Postgres over your private network from the customer-tenancy CYBRET deployment.

Connection pooler (PgBouncer / Supavisor)?

CYBRET reads through the same pooler your app uses. Schema and policy introspection runs on a low-frequency cadence so it does not contend with application traffic.

Multiple Supabase projects?

Each Supabase project connects as a separate integration. Cross-project trust (a function in project A calling project B) is modelled as a cross-tenant edge in the graph.

Next step

Connect Supabase →

Read-only role, ten-minute first build. The first "is RLS actually enforcing this?" question gets answered in the first hour.

Connect SupabaseSee Exposure Intelligence