Install
$ agentstack add skill-hlsitechio-claude-skills-security-supabase-security-audit ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Supabase Security Audit
Audit the security posture of a Supabase project across its three exposed surfaces: PostgREST (auto-API over Postgres), Storage, and Edge Functions. Defensive find-and-fix focus.
When this skill applies
- Reviewing whether RLS is enabled and policies are correct
- Auditing SECURITY DEFINER functions exposed to anon or authenticated roles
- Checking grants on
anonandauthenticatedroles - Looking for
service_rolekey exposure in client code - Reviewing edge function code for missing JWT verification
- Investigating suspicious activity in a Supabase project (post-incident)
Use other skills for: general app-code review (saas-code-security-review), multi-tenant patterns (saas-tenant-isolation).
Workflow
Follow ../_shared/audit-workflow.md. Skill-specific notes below.
Phase 1: Scope confirmation
Confirm with the user:
- Project ref (
xxxxx.supabase.co) - Do you have the service_role key available for read-only audits? (Some checks require it.)
- Are you the owner/admin of the project?
- Is this a production project or a staging/dev project?
Never request or store the service_role key in chat. Ask the user to run queries themselves using the SQL editor and paste results.
Phase 2: Inventory
Run the queries in scripts/inventory.sql against the project. They produce:
- Every table with RLS status
- Every policy per table
- Every function with
security definerstatus and grants - Every grant on
anonandauthenticated - Every storage bucket and its public/private status
- Edge function list (via CLI:
supabase functions list)
Phase 3: Detection — the checks
RLS coverage — see references/rls-patterns.md
- SUPA-RLS-1 Every user-data table has RLS enabled (
relrowsecurity = true). - SUPA-RLS-2 Every user-data table has at least one policy (RLS enabled with no policies = nothing is allowed; usually a config error).
- SUPA-RLS-3 Policies use
auth.uid()/auth.jwt()for identity, notcurrent_user(which is the Postgres role, not the app user). - SUPA-RLS-4 Policies cover all four operations (SELECT, INSERT, UPDATE, DELETE) — a permissive SELECT with no UPDATE policy means no UPDATE is allowed (often intended), but the audit should confirm intent.
- SUPA-RLS-5 Restrictive policies (
AS RESTRICTIVE) used where multiple permissive policies could otherwise OR together unsafely. - SUPA-RLS-6
USINGclauses don't have subqueries that bypass RLS on referenced tables (recursive RLS pitfall). - SUPA-RLS-7 Junction/audit/log tables also have RLS — not just the obvious user-facing tables.
Bypass paths — see references/security-definer.md
- SUPA-SD-1 Every
SECURITY DEFINERfunction inventoried with its grants. - SUPA-SD-2 SECURITY DEFINER functions called by anon or authenticated have explicit authorization checks at the top.
- SUPA-SD-3
search_pathset explicitly in every SECURITY DEFINER function (SET search_path = ''or a hardened path), to prevent function-resolution hijacking. - SUPA-SD-4 No SECURITY DEFINER function returns data from tables the caller couldn't otherwise reach via RLS, unless that's the explicit intent and authorization is enforced inside.
- SUPA-SD-5 SECURITY DEFINER functions revoked from
publicby default; grants only to specific roles.
Role grants — see references/anon-role-exposure.md
- SUPA-AR-1
anonhas SELECT only on tables intended for public access. Any table exposing PII to anon → finding. - SUPA-AR-2
anonhas no INSERT/UPDATE/DELETE on user-data tables (use functions or authenticated grants). - SUPA-AR-3
authenticatedgrants are minimal — RLS does the policy work, but grants must allow it. - SUPA-AR-4 No grants directly to
public(covers everyone including future roles). - SUPA-AR-5 Sequence usage grants align with table grants (else INSERTs fail oddly or succeed by accident).
Service role exposure
- SUPA-SR-1 No client code (browser bundle, mobile app, public repo) imports the service_role key. Grep the codebase for the key prefix.
- SUPA-SR-2 Service role used only in server-side code, edge functions with explicit auth, or admin tooling.
- SUPA-SR-3 Service role key rotated in last 12 months and after any suspected exposure.
- SUPA-SR-4 No service_role key in environment variables of client-facing services (Vercel/Netlify "preview" environments that may leak).
Edge functions — see references/edge-functions-auth.md
- SUPA-EF-1 Every edge function that handles authenticated user data calls
supabase.auth.getUser(jwt)to verify and identify the caller. - SUPA-EF-2 Functions don't trust client-supplied user IDs; always derive from JWT.
- SUPA-EF-3
Verify JWTflag enabled in function settings unless explicitly intended to be public (webhook receivers). - SUPA-EF-4 Webhook receivers verify signatures (Stripe, Resend, custom) — see
saas-api-security/references/webhook-security.md. - SUPA-EF-5 Service role key not used inside edge function for queries that should respect RLS — use the user's JWT with the supabase client.
- SUPA-EF-6 Outbound HTTP requests from edge functions follow SSRF protections (see
saas-code-security-review/references/ssrf-patterns.md).
Storage
- SUPA-ST-1 Buckets marked public only when intent is public; default to private.
- SUPA-ST-2 Storage policies (RLS-style) defined for private buckets.
- SUPA-ST-3 Signed URL TTLs reasonable for use case (short for one-time access, longer for embedded content).
- SUPA-ST-4 No path-traversal-style key construction from user input without sanitization.
Auth configuration
- SUPA-AUTH-1 Email confirmation required (don't allow unconfirmed accounts to act).
- SUPA-AUTH-2 Rate limits configured on auth endpoints.
- SUPA-AUTH-3 Redirect URLs allowlist explicit (no wildcards that could allow OAuth redirect to attacker).
- SUPA-AUTH-4 Custom SMTP configured for production (Supabase's shared SMTP has reputation limits).
- SUPA-AUTH-5 MFA enabled on Supabase dashboard for project owners.
Phase 4: Triage
Critical class examples:
- RLS disabled on any table containing user data
- service_role key in a public repo or browser bundle
- SECURITY DEFINER function exposed to anon that returns all rows from a sensitive table
- Edge function performing privileged operations without JWT verification
Phase 5: Report
Use ../_shared/findings-schema.md. Prefix IDs with SUPA-. Include the table/function name and grant context in evidence.
Scripts
scripts/audit_rls.sql— Lists every table with RLS status and policy countscripts/find_definer_funcs.sql— Lists SECURITY DEFINER functions and their grantsscripts/anon_grants_report.sql— Lists everything granted toanonandauthenticated
Run these in the Supabase SQL editor.
References
references/rls-patterns.md— Common RLS policies (good and bad), recursion pitfallsreferences/security-definer.md— When to use SECURITY DEFINER, how to harden itreferences/anon-role-exposure.md— Whatanonshould and shouldn't havereferences/edge-functions-auth.md— JWT verification patterns for Deno edge functions
Assets
assets/rls-template.sql— Default-deny policy template for new tables
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: hlsitechio
- Source: hlsitechio/claude-skills-security
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.