feat: Adding Supabase security prompt module
This commit is contained in:
189
strix/prompts/technologies/supabase.jinja
Normal file
189
strix/prompts/technologies/supabase.jinja
Normal file
@@ -0,0 +1,189 @@
|
||||
<supabase_security_guide>
|
||||
<title>SUPABASE — ADVERSARIAL TESTING AND EXPLOITATION</title>
|
||||
|
||||
<critical>Supabase exposes Postgres through PostgREST, Realtime, GraphQL, Storage, Auth (GoTrue), and Edge Functions. Most impactful findings come from mis-scoped Row Level Security (RLS), unsafe RPCs, leaked service_role keys, lax Storage policies, GraphQL overfetching, and Edge Functions trusting headers or tokens without binding to issuer/audience/tenant.</critical>
|
||||
|
||||
<scope>
|
||||
- PostgREST: table CRUD, filters, embeddings, RPC (remote functions)
|
||||
- RLS: row ownership/tenant isolation via policies and auth.uid()
|
||||
- Storage: buckets, objects, signed URLs, public/private policies
|
||||
- Realtime: replication subscriptions, broadcast/presence channels
|
||||
- GraphQL: pg_graphql over Postgres schema with RLS interaction
|
||||
- Auth (GoTrue): JWTs, cookie/session, magic links, OAuth flows
|
||||
- Edge Functions (Deno): server-side code calling Supabase with secrets
|
||||
</scope>
|
||||
|
||||
<methodology>
|
||||
1. Inventory surfaces: REST /rest/v1, Storage /storage/v1, GraphQL /graphql/v1, Realtime wss, Auth /auth/v1, Functions https://<project>.functions.supabase.co/.
|
||||
2. Obtain tokens for: unauth (anon), basic user, other user, and (if disclosed) admin/staff; enumerate anon key exposure and verify if service_role leaked anywhere.
|
||||
3. Build a Resource × Action × Principal matrix and test each via REST and GraphQL. Confirm parity across channels and content-types (json/form/multipart).
|
||||
4. Start with list/search/export endpoints to gather IDs, then attempt direct reads/writes across principals, tenants, and transports. Validate RLS and function guards.
|
||||
</methodology>
|
||||
|
||||
<architecture>
|
||||
- Project endpoints: https://<ref>.supabase.co; REST at /rest/v1/<table>, RPC at /rest/v1/rpc/<fn>.
|
||||
- Headers: apikey: <anon-or-service>, Authorization: Bearer <JWT>. Anon key only identifies the project; JWT binds user context.
|
||||
- Roles: anon, authenticated; service_role bypasses RLS and must never be client-exposed.
|
||||
- auth.uid(): current user UUID claim; policies must never trust client-supplied IDs over server context.
|
||||
</architecture>
|
||||
|
||||
<rls>
|
||||
- Enable RLS on every non-public table; absence or “permit-all” policies → bulk exposure.
|
||||
- Common gaps:
|
||||
- Policies check auth.uid() for read but forget UPDATE/DELETE/INSERT.
|
||||
- Missing tenant constraints (org_id/tenant_id) allow cross-tenant reads/writes.
|
||||
- Policies rely on client-provided columns (user_id in payload) instead of deriving from JWT.
|
||||
- Complex joins where the effective policy is applied after filters, enabling inference via counts or projections.
|
||||
- Tests:
|
||||
- Compare results for two users: GET /rest/v1/<table>?select=*&Prefer=count=exact; diff row counts and IDs.
|
||||
- Try cross-tenant: add &org_id=eq.<other_org> or use or=(org_id.eq.other,org_id.is.null).
|
||||
- Write-path: PATCH/DELETE single row with foreign id; INSERT with foreign owner_id then read.
|
||||
</rls>
|
||||
|
||||
<postgrest_and_rest>
|
||||
- Filters: eq, neq, lt, gt, ilike, or, is, in; embed relations with select=*,profile(*); exploit embeddings to overfetch linked rows if resolvers skip per-row checks.
|
||||
- Headers to know: Prefer: return=representation (echo writes), Prefer: count=exact (exposure via counts), Accept-Profile/Content-Profile to select schema.
|
||||
- IDOR patterns: /rest/v1/<table>?select=*&id=eq.<other_id>; query alternative keys (slug, email) and composite keys.
|
||||
- Search leaks: generous LIKE/ILIKE filters + lack of RLS → mass disclosure.
|
||||
- Mass assignment: if RPC not used, PATCH can update unintended columns; verify restricted columns via database permissions/policies.
|
||||
</postgrest_and_rest>
|
||||
|
||||
<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 /rest/v1/rpc/<fn> as different users with foreign ids in body.
|
||||
- Remove or alter JWT entirely (Authorization: Bearer <anon>) to see if function still executes.
|
||||
- Validate that functions perform explicit ownership/tenant checks inside SQL, not only in docs.
|
||||
</rpc_functions>
|
||||
|
||||
<storage>
|
||||
- Buckets: public vs private; objects live in storage.objects with RLS-like policies.
|
||||
- Find misconfigs:
|
||||
- Public buckets holding sensitive data: GET https://<ref>.supabase.co/storage/v1/object/public/<bucket>/<path>
|
||||
- Signed URLs with long TTL and no audience binding; reuse/guess tokens across tenants/paths.
|
||||
- Listing prefixes without auth: /storage/v1/object/list/<bucket>?prefix=
|
||||
- Path confusion: mixed case, URL-encoding, “..” segments rejected at UI but accepted by API.
|
||||
- Abuse vectors:
|
||||
- Content-type/XSS: upload HTML/SVG served as text/html or image/svg+xml; confirm X-Content-Type-Options: nosniff and Content-Disposition: attachment.
|
||||
- Signed URL replay across accounts/buckets if validation is lax.
|
||||
</storage>
|
||||
|
||||
<realtime>
|
||||
- Endpoint: wss://<ref>.supabase.co/realtime/v1. Join channels with apikey + Authorization.
|
||||
- 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 checks.
|
||||
- Tests:
|
||||
- Subscribe to public:realtime changes on protected tables; confirm row data visibility aligns with RLS.
|
||||
- Attempt joining other users’ presence/broadcast channels (e.g., room:<user_id>, org:<id>).
|
||||
</realtime>
|
||||
|
||||
<graphql>
|
||||
- Endpoint: /graphql/v1 using pg_graphql with RLS. Risks:
|
||||
- Introspection reveals schema relations; ensure it’s intentional.
|
||||
- Overfetch via nested relations where field resolvers fail to re-check ownership/tenant.
|
||||
- Global node IDs (if implemented) leaked and reusable via different viewers.
|
||||
- Tests:
|
||||
- Compare REST vs GraphQL responses for the same principal and query shape.
|
||||
- Query deep nested fields and connections; verify RLS holds at each edge.
|
||||
</graphql>
|
||||
|
||||
<auth_and_tokens>
|
||||
- GoTrue issues JWTs with claims (sub=uid, role, aud=authenticated). Validate on server: issuer, audience, exp, signature, and tenant context.
|
||||
- Pitfalls:
|
||||
- Storing tokens in localStorage → XSS exfiltration; refresh mismanagement leading to long-lived sessions.
|
||||
- Treating apikey as identity; it is project-scoped, not user identity.
|
||||
- Exposing service_role key in client bundle or Edge Function responses.
|
||||
- Tests:
|
||||
- Replay tokens across services; check audience/issuer pinning.
|
||||
- Try downgraded tokens (expired/other audience) against custom endpoints.
|
||||
</auth_and_tokens>
|
||||
|
||||
<edge_functions>
|
||||
- Deno-based functions often initialize server-side 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 function payloads; verify server re-derives user/tenant from JWT.
|
||||
- Attempt to reach internal endpoints (metadata services, project endpoints) via function fetch.
|
||||
</edge_functions>
|
||||
|
||||
<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; look for cross-tenant data.
|
||||
- Export/report endpoints: confirm queries execute under caller scope; signed outputs must encode tenant and short TTL.
|
||||
</tenant_isolation>
|
||||
|
||||
<bypass_techniques>
|
||||
- Content-type switching: application/json ↔ application/x-www-form-urlencoded ↔ multipart/form-data to hit different code paths.
|
||||
- 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.
|
||||
</bypass_techniques>
|
||||
|
||||
<blind_channels>
|
||||
- Use Prefer: count=exact and ETag/length diffs to infer unauthorized rows.
|
||||
- Conditional requests (If-None-Match) to detect object existence without content exposure.
|
||||
- Storage signed URLs: timing/length deltas to map valid vs invalid tokens.
|
||||
</blind_channels>
|
||||
|
||||
<tooling_and_automation>
|
||||
- PostgREST: httpie/curl + jq; enumerate tables with known names; fuzz filters (or=, ilike, neq, is.null).
|
||||
- GraphQL: graphql-inspector, voyager; build deep queries to test field-level enforcement; complexity/batching tests.
|
||||
- Realtime: custom ws client; subscribe to suspicious channels/tables; diff payloads per principal.
|
||||
- Storage: enumerate bucket listing APIs; script signed URL generation/use patterns.
|
||||
- Auth/JWT: jwt-cli/jose to validate audience/issuer; replay against Edge Functions.
|
||||
- Policy diffing: maintain request sets per role and compare results across releases.
|
||||
</tooling_and_automation>
|
||||
|
||||
<reviewer_checklist>
|
||||
- Are all non-public tables RLS-enabled with explicit SELECT/INSERT/UPDATE/DELETE policies?
|
||||
- Do policies derive subject/tenant from JWT (auth.uid(), tenant claim) rather than client payload?
|
||||
- Do RPC functions run as SECURITY INVOKER, or if DEFINER, do they enforce ownership/tenant inside?
|
||||
- Are Storage buckets private by default, with short-lived signed URLs bound to tenant/context?
|
||||
- Does Realtime enforce RLS-equivalent filtering for subscriptions and block cross-room joins?
|
||||
- Is GraphQL parity verified with REST; are nested resolvers guarded per field?
|
||||
- Are Edge Functions verifying JWT (issuer/audience) and never exposing service_role to clients?
|
||||
- Are CDN/cache keys bound to Authorization/tenant to prevent cache leaks?
|
||||
</reviewer_checklist>
|
||||
|
||||
<validation>
|
||||
1. Provide owner vs non-owner requests for REST/GraphQL showing unauthorized access (content or metadata).
|
||||
2. Demonstrate a mis-scoped RPC or Storage signed URL usable by another user/tenant.
|
||||
3. Confirm Realtime or GraphQL exposure matches missing policy checks.
|
||||
4. Document minimal reproducible requests and role contexts used.
|
||||
</validation>
|
||||
|
||||
<false_positives>
|
||||
- Tables intentionally public (documented) with non-sensitive content.
|
||||
- RLS-enabled tables returning only caller-owned rows; mismatched UI not backed by API responses.
|
||||
- Signed URLs with very short TTL and audience binding.
|
||||
- Edge Functions verifying tokens and re-deriving context before acting.
|
||||
</false_positives>
|
||||
|
||||
<impact>
|
||||
- Cross-account/tenant data exposure and unauthorized state changes.
|
||||
- Exfiltration of PII/PHI/PCI, financial and billing artifacts, private files.
|
||||
- Privilege escalation via RPC and Edge Functions; durable access via long-lived tokens.
|
||||
- Regulatory and contractual violations stemming from tenant isolation failures.
|
||||
</impact>
|
||||
|
||||
<pro_tips>
|
||||
1. Start with /rest/v1 list/search; counts and embeddings reveal policy drift fast.
|
||||
2. Treat UUIDs and signed URLs as untrusted; validate binding to subject/tenant and TTL.
|
||||
3. Focus on RPC and Edge Functions—they often centralize business logic and skip RLS.
|
||||
4. Test GraphQL and Realtime parity with REST; differences are where vulnerabilities hide.
|
||||
5. Keep role-separated request corpora and diff responses across deployments.
|
||||
6. Never assume apikey == identity; only JWT binds subject. Prove it.
|
||||
7. Prefer concise PoCs: one request per role that clearly shows the unauthorized delta.
|
||||
</pro_tips>
|
||||
|
||||
<remember>RLS must bind subject and tenant on every path, and server-side code (RPC/Edge) must re-derive identity from a verified token. Any gap in binding, audience/issuer verification, or per-field enforcement becomes a cross-account or cross-tenant vulnerability.</remember>
|
||||
</supabase_security_guide>
|
||||
Reference in New Issue
Block a user