Supabase security testing
Skill xAmirHamza77/PenKit51/skills/supabase-security-testing
PenKit51 — Open-source AI penetration testing platform with 63 deep exploitation skills, multi-agent orchestration, PoC-validated findings, and native assistant skills for Claude, ChatGPT, and Grok. Authorized testing only.
npx -y skills add xAmirHamza77/PenKit51 --skill supabase-security-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Supabase security testing covering Row Level Security, PostgREST, Edge Functions, and service key exposure
SKILL.md
19.5 KB, ~4.8k tokens by cl100k_base, as published. Nobody here has run it
Supabase Security Testing
penkit51 AI — professional penetration testing skill pack. Authorized testing only.
Deep Exploitation Guide
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 projectAuthorization: Bearer <JWT>— binds user context
Roles
anon,authenticated— standard rolesservice_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_roleaccess - 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_rolekey 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_idin 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/ILIKEfilters combined with missing RLS → mass disclosure via wildcard queries
Headers
Prefer: return=representation— echo writesPrefer: count=exact— exposure via countsAccept-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 bypassset search_pathleft to public; function resolves unsafe objects- Trusting client-supplied
user_id/tenant_idrather thanauth.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.objectswith 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/htmlorimage/svg+xml - Verify
X-Content-Type-Options: nosniffandContent-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:realtimechanges 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
apikeyas identity (it's project-scoped, not user identity) - Exposing
service_rolekey 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/json↔application/x-www-form-urlencoded↔multipart/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=exactand 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
- Inventory surfaces - Map REST, Storage, GraphQL, Realtime, Auth, Functions endpoints
- Obtain principals - Collect tokens for anon, user A/B, admin; check for
service_roleleaks - Build matrix - Resource × Action × Principal
- REST vs GraphQL - Test both to find parity gaps
- Seed IDs - Start with list/search endpoints to gather IDs
- 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
Platform Methodology
Supabase Security Testing
penkit51 AI — professional penetration testing skill pack. Authorized testing only.
Deep Exploitation Guide
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 projectAuthorization: Bearer <JWT>— binds user context
Roles
anon,authenticated— standard rolesservice_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_roleaccess - 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_rolekey 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_idin 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/ILIKEfilters combined with missing RLS → mass disclosure via wildcard queries
Headers
Prefer: return=representation— echo writesPrefer: count=exact— exposure via countsAccept-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 bypassset search_pathleft to public; function resolves unsafe objects- Trusting client-supplied
user_id/tenant_idrather thanauth.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.objectswith 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/htmlorimage/svg+xml - Verify
X-Content-Type-Options: nosniffandContent-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:realtimechanges 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
apikeyas identity (it's project-scoped, not user identity) - Exposing
service_rolekey 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/json↔application/x-www-form-urlencoded↔multipart/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=exactand 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
- Inventory surfaces - Map REST, Storage, GraphQL, Realtime, Auth, Functions endpoints
- Obtain principals - Collect tokens for anon, user A/B, admin; check for
service_roleleaks - Build matrix - Resource × Action × Principal
- REST vs GraphQL - Test both to find parity gaps
- Seed IDs - Start with list/search endpoints to gather IDs
- 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
Validation & Reporting
- Confirm every finding with reproducible PoC before reporting
- Document: severity (CVSS), affected asset, steps, evidence, remediation
- Use
record_vulnerabilitywhen running inside the penkit51 platform - Chain low-severity findings into higher-impact attack paths
- Never report without evidence — distinguish hypothesis from confirmed vuln
Validation & Reporting
- Confirm every finding with reproducible PoC before reporting
- Document: severity (CVSS), affected asset, steps, evidence, remediation
- Use
record_vulnerabilitywhen running inside the penkit51 platform - Chain low-severity findings into higher-impact attack paths
- Never report without evidence — distinguish hypothesis from confirmed vuln
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.