Files
strix/strix/skills/technologies/supabase.md

9.3 KiB
Raw Blame History

name, description
name description
supabase Supabase security testing covering Row Level Security, PostgREST, Edge Functions, and service key exposure

Supabase

Security testing for Supabase applications. Focus on mis-scoped Row Level Security (RLS), unsafe RPCs, leaked service_role keys, lax Storage policies, and Edge Functions trusting headers without binding to issuer/audience/tenant.

Attack Surface

Data Access

  • PostgREST: table CRUD, filters, embeddings, RPC (remote functions)
  • GraphQL: pg_graphql over Postgres schema with RLS interaction
  • Realtime: replication subscriptions, broadcast/presence channels

Storage

  • Buckets, objects, signed URLs, public/private policies

Authentication

  • Auth (GoTrue): JWTs, cookie/session, magic links, OAuth flows

Server-Side

  • Edge Functions (Deno): server-side code calling Supabase with secrets

Architecture

Endpoints

  • REST: https://<ref>.supabase.co/rest/v1/<table>
  • RPC: https://<ref>.supabase.co/rest/v1/rpc/<fn>
  • Storage: https://<ref>.supabase.co/storage/v1
  • GraphQL: https://<ref>.supabase.co/graphql/v1
  • Realtime: wss://<ref>.supabase.co/realtime/v1
  • Auth: https://<ref>.supabase.co/auth/v1
  • Functions: https://<ref>.functions.supabase.co/

Headers

  • apikey: <anon-or-service> — identifies project
  • Authorization: Bearer <JWT> — binds user context

Roles

  • anon, authenticated — standard roles
  • service_role — bypasses RLS, must never be client-exposed

Key Principle auth.uid() returns current user UUID from JWT. Policies must never trust client-supplied IDs over server context.

High-Value Targets

  • Tables with sensitive data (users, orders, payments, PII)
  • RPC functions (especially SECURITY DEFINER)
  • Storage buckets with private files
  • Edge Functions with service_role access
  • Export/report endpoints generating signed outputs
  • Admin/staff routes and privilege-granting endpoints

Reconnaissance

Enumerate Surfaces

/rest/v1/<table>
/rest/v1/rpc/<fn>
/storage/v1/object/public/<bucket>/
/storage/v1/object/list/<bucket>?prefix=
/graphql/v1
/auth/v1

Obtain Principals

  • Unauthenticated (anon key only)
  • Basic user A, user B
  • Admin/staff (if available)
  • Check if service_role key leaked in client bundle or Edge Function responses

Key Vulnerabilities

Row Level Security (RLS)

Enable RLS on every non-public table; absence or "permit-all" policies → bulk exposure.

Common Gaps

  • Policies check auth.uid() for SELECT but forget UPDATE/DELETE/INSERT
  • Missing tenant constraints (org_id/tenant_id) allow cross-tenant access
  • Policies rely on client-provided columns (user_id in payload) instead of JWT
  • Complex joins where policy is applied after filters, enabling inference via counts

Tests

# Compare row counts for two users
GET /rest/v1/<table>?select=*&Prefer=count=exact

# Cross-tenant probe
GET /rest/v1/<table>?org_id=eq.<other_org>
GET /rest/v1/<table>?or=(org_id.eq.other,org_id.is.null)

# Write-path
PATCH /rest/v1/<table>?id=eq.<foreign_id>
DELETE /rest/v1/<table>?id=eq.<foreign_id>
POST /rest/v1/<table> with foreign owner_id

PostgREST & REST

Filters

  • eq, neq, lt, gt, ilike, or, is, in
  • Embed relations: select=*,profile(*)—exploits overfetch if resolvers skip per-row checks
  • Search leaks: generous LIKE/ILIKE filters combined with missing RLS → mass disclosure via wildcard queries

Headers

  • Prefer: return=representation — echo writes
  • Prefer: count=exact — exposure via counts
  • Accept-Profile/Content-Profile — select schema

IDOR Patterns

/rest/v1/<table>?select=*&id=eq.<other_id>
/rest/v1/<table>?select=*&slug=eq.<other_slug>
/rest/v1/<table>?select=*&email=eq.<other_email>

Mass Assignment

  • If RPC not used, PATCH can update unintended columns
  • Verify restricted columns via database permissions/policies

RPC Functions

RPC endpoints map to SQL functions. SECURITY DEFINER bypasses RLS unless carefully coded; SECURITY INVOKER respects caller.

Anti-Patterns

  • SECURITY DEFINER + missing owner checks → vertical/horizontal bypass
  • set search_path left to public; function resolves unsafe objects
  • Trusting client-supplied user_id/tenant_id rather than auth.uid()

Tests

# Call as different users with foreign IDs
POST /rest/v1/rpc/<fn> {"user_id": "<foreign_id>"}

# Remove JWT entirely
Authorization: Bearer <anon_token>

Verify functions perform explicit ownership/tenant checks inside SQL.

Storage

Buckets

  • Public vs private; objects in storage.objects with RLS-like policies

Misconfigurations

# Public bucket with sensitive data
GET /storage/v1/object/public/<bucket>/<path>

# List prefixes without auth
GET /storage/v1/object/list/<bucket>?prefix=

# Signed URL reuse across tenants/paths

Content-Type Abuse

  • Upload HTML/SVG served as text/html or image/svg+xml
  • Verify X-Content-Type-Options: nosniff and Content-Disposition: attachment

Path Confusion

  • Mixed case, URL-encoding, .. segments may be rejected at UI but accepted by API
  • Test path normalization differences between client validation and server handling

Realtime

Endpoint: wss://<ref>.supabase.co/realtime/v1

Risks

  • Channel names derived from table/schema/filters leaking other users' updates when RLS or channel guards are weak
  • Broadcast/presence channels allowing cross-room join/publish without auth

Tests

  • Subscribe to public:realtime changes on protected tables; confirm visibility aligns with RLS
  • Attempt joining other users' channels: room:<user_id>, org:<org_id>

GraphQL

Endpoint: /graphql/v1 using pg_graphql with RLS

Risks

  • Introspection reveals schema relations
  • Overfetch via nested relations where resolvers skip per-row ownership checks
  • Global node IDs leaked and reusable via different viewers

Tests

  • Compare REST vs GraphQL responses for same principal and query shape
  • Query deep nested fields; verify RLS holds at each edge

Auth & Tokens

GoTrue issues JWTs with claims (sub=uid, role, aud=authenticated).

Verification Requirements

  • Issuer, audience, expiration, signature, tenant context

Pitfalls

  • Storing tokens in localStorage → XSS exfiltration
  • Treating apikey as identity (it's project-scoped, not user identity)
  • Exposing service_role key in client bundle or Edge Function responses
  • Refresh token mismanagement leading to long-lived sessions beyond intended TTL

Tests

  • Replay tokens across services; check audience/issuer pinning
  • Try downgraded tokens (expired/other audience) against custom endpoints

Edge Functions

Deno-based functions often initialize Supabase client with service_role.

Risks

  • Trusting Authorization/apikey headers without verifying JWT against issuer/audience
  • CORS: wildcard origins with credentials; reflected Authorization in responses
  • SSRF via fetch; secrets exposed via error traces or logs

Tests

  • Call functions with and without Authorization; compare behavior
  • Try foreign resource IDs in payloads; verify server re-derives user/tenant from JWT
  • Attempt to reach internal endpoints (metadata services) via function fetch

Tenant Isolation

Ensure every query joins or filters by tenant_id/org_id derived from JWT context, not client input.

Tests

  • Change subdomain/header/path tenant selectors while keeping JWT tenant constant
  • Export/report endpoints: confirm queries execute under caller scope

Bypass Techniques

  • Content-type switching: application/jsonapplication/x-www-form-urlencodedmultipart/form-data
  • Parameter pollution: duplicate keys in JSON/query (PostgREST chooses last/first depending on parser)
  • GraphQL+REST parity probing: protections often drift; fetch via the weaker path
  • Race windows: parallel writes to bypass post-insert ownership updates

Blind Enumeration

  • Use Prefer: count=exact and ETag/length diffs to infer unauthorized rows
  • Conditional requests (If-None-Match) to detect object existence
  • Storage signed URLs: timing/length deltas to map valid vs invalid tokens

Testing Methodology

  1. Inventory surfaces - Map REST, Storage, GraphQL, Realtime, Auth, Functions endpoints
  2. Obtain principals - Collect tokens for anon, user A/B, admin; check for service_role leaks
  3. Build matrix - Resource × Action × Principal
  4. REST vs GraphQL - Test both to find parity gaps
  5. Seed IDs - Start with list/search endpoints to gather IDs
  6. Cross-principal - Swap IDs, tenants, and transports across principals

Tooling

  • PostgREST: httpie/curl + jq; enumerate tables; fuzz filters (or=, ilike, neq, is.null)
  • GraphQL: graphql-inspector, voyager; deep queries for field-level enforcement
  • Realtime: custom ws client; subscribe to suspicious channels; diff payloads per principal
  • Storage: enumerate bucket listing APIs; script signed URL patterns
  • Auth/JWT: jwt-cli/jose to validate audience/issuer; replay against Edge Functions
  • Policy diffing: maintain request sets per role; compare results across releases

Validation Requirements

  • Owner vs non-owner requests for REST/GraphQL showing unauthorized access (content or metadata)
  • Mis-scoped RPC or Storage signed URL usable by another user/tenant
  • Realtime or GraphQL exposure matching missing policy checks
  • Minimal reproducible requests with role contexts documented