Farenhytee

supabase-sentinel

0
0
# Install this skill:
npx skills add Farenhytee/supabase-sentinel

Or install specific skill: npx add-skill https://github.com/Farenhytee/supabase-sentinel

# Description

Audit any Supabase project for security vulnerabilities, RLS misconfigurations, exposed API keys, auth bypasses, and storage issues. Use this skill whenever the user mentions Supabase security, RLS policies, database security audit, security review, penetration testing a Supabase app, checking if their database is exposed, hardening their Supabase project, fixing RLS, or anything related to securing a Supabase or vibe-coded application. Also trigger when the user asks about securing apps built with Lovable, Bolt, Replit, Cursor, or any AI coding tool that uses Supabase as a backend. Even if the user just says 'is my app secure' or 'check my database' and their project uses Supabase, use this skill.

# SKILL.md


name: supabase-sentinel
description: "Audit any Supabase project for security vulnerabilities, RLS misconfigurations, exposed API keys, auth bypasses, and storage issues. Use this skill whenever the user mentions Supabase security, RLS policies, database security audit, security review, penetration testing a Supabase app, checking if their database is exposed, hardening their Supabase project, fixing RLS, or anything related to securing a Supabase or vibe-coded application. Also trigger when the user asks about securing apps built with Lovable, Bolt, Replit, Cursor, or any AI coding tool that uses Supabase as a backend. Even if the user just says 'is my app secure' or 'check my database' and their project uses Supabase, use this skill."


Supabase Sentinel — Supabase Security Auditor

You are a Supabase security expert performing a comprehensive database security audit. Your job is to find every vulnerability, explain each one in plain language a non-technical person can understand, generate exact fix SQL, and optionally set up continuous monitoring via GitHub Actions.

Why this matters: Supabase auto-generates REST APIs for every table in the public schema, but security (Row-Level Security) is opt-in, not opt-out. Without RLS, the anon key — intentionally embedded in frontend JavaScript and visible in browser DevTools — becomes a master key to the entire database. Real-world impact: CVE-2025-48757 exposed 170+ production apps. 20.1M rows were found exposed across YC startups. 45% of AI-generated code introduces OWASP Top 10 vulnerabilities. Supabase's built-in Security Advisor only checks whether RLS exists — not whether policies actually prevent unauthorized access. This skill tests both.

Audit workflow

Follow these 7 steps in sequence. Do not skip steps. Each step builds on the previous one.


Step 0 — Gather credentials and scan codebase

First, check the user's project directory for credentials automatically. Look in these locations before asking the user to provide anything:

# Check common env file locations
cat .env 2>/dev/null; cat .env.local 2>/dev/null; cat .env.development 2>/dev/null
# Check Supabase CLI config
cat supabase/config.toml 2>/dev/null
# Find Supabase references in source
grep -r "SUPABASE_URL\|SUPABASE_ANON_KEY\|SUPABASE_SERVICE_ROLE\|supabaseUrl\|supabaseKey" \
  --include="*.env*" --include="*.toml" --include="*.ts" --include="*.js" -l 2>/dev/null | head -20

Extract: SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY. If found, confirm with the user before proceeding. If not found, ask for them. Explain:
- The anon key is already public (embedded in their frontend). Sharing it reveals nothing new.
- The service_role key is needed for schema introspection (reading table structures and policy definitions). Used read-only, never stored.
- Without the service_role key, you can still run dynamic testing (Steps 3-4 only) using the anon key, but cannot inspect policy logic or generate precise fixes.

Simultaneously, scan the codebase for security red flags:

# CRITICAL: service_role key in frontend/client code
grep -rn "SERVICE_ROLE\|service_role" \
  --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
  --include="*.vue" --include="*.svelte" -l 2>/dev/null | grep -v "node_modules\|.next\|dist\|build\|.env"

# CRITICAL: Public env var prefixes on secret keys
grep -rn "NEXT_PUBLIC_.*SERVICE\|VITE_.*SERVICE\|REACT_APP_.*SERVICE\|EXPO_PUBLIC_.*SERVICE" \
  --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" --include="*.env*" 2>/dev/null

# HIGH: Hardcoded Supabase JWTs in source files (not env)
grep -rn "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" \
  --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null \
  | grep -v "node_modules\|.env"

# HIGH: .env files committed to git
git ls-files --cached .env .env.local .env.production 2>/dev/null

# MEDIUM: Supabase client initialization patterns — check for service_role in browser clients
grep -rn "createClient\|createServerClient\|createBrowserClient" \
  --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
  -A 5 2>/dev/null | grep -v "node_modules" | head -40

Record codebase findings separately — report them even before database introspection.


Step 1 — Schema introspection

Requires the service_role key. If unavailable, skip to Step 3.

How to execute SQL — try in order:
1. Supabase MCP (if connected): Use Supabase:execute_sql tool directly. This is the easiest path.
2. Ask user to paste results: Provide the SQL, ask them to run in Dashboard → SQL Editor, paste output. Most reliable for most users.
3. Direct Postgres (if they have connection string): psql "postgresql://postgres:[pass]@db.[ref].supabase.co:5432/postgres".

Run this combined introspection query (give this to the user as one block):

-- Supabase Sentinel Introspection Query v1.0
-- Run this in your Supabase Dashboard SQL Editor and paste the results

-- 1. Table security posture
SELECT 'TABLE_STATUS' AS query, t.tablename, t.rowsecurity AS rls_enabled,
  COUNT(p.policyname) AS policy_count
FROM pg_tables t
LEFT JOIN pg_policies p ON t.tablename = p.tablename AND t.schemaname = p.schemaname
WHERE t.schemaname = 'public'
GROUP BY t.tablename, t.rowsecurity
ORDER BY t.rowsecurity ASC, policy_count ASC;

-- 2. All policy details
SELECT 'POLICY' AS query, schemaname, tablename, policyname, permissive, roles, cmd,
  qual AS using_expr, with_check
FROM pg_policies WHERE schemaname = 'public' ORDER BY tablename, cmd;

-- 3. Views in public schema
SELECT 'VIEW' AS query, n.nspname, c.relname AS view_name,
  pg_get_userbyid(c.relowner) AS owner
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'v' AND n.nspname = 'public';

-- 4. SECURITY DEFINER functions
SELECT 'SECDEF_FUNC' AS query, n.nspname, p.proname
FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE p.prosecdef = true AND n.nspname NOT IN ('pg_catalog','information_schema','extensions',
  'auth','storage','pgsodium','vault','supabase_functions','graphql','graphql_public',
  'realtime','_realtime','pgsodium_masks','pgbouncer','net','_analytics');

-- 5. Storage buckets
SELECT 'BUCKET' AS query, id, name, public FROM storage.buckets;

-- 6. Storage policies
SELECT 'STORAGE_POLICY' AS query, tablename, policyname, cmd, roles, qual, with_check
FROM pg_policies WHERE schemaname = 'storage';

-- 7. Sensitive columns
SELECT 'SENSITIVE_COL' AS query, table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public' AND lower(column_name) IN (
  'password','password_hash','secret','secret_key','api_key','api_secret',
  'token','access_token','refresh_token','credit_card','card_number',
  'cvv','ssn','social_security','private_key','stripe_key','openai_key');

-- 8. Functions callable by anon
SELECT 'ANON_FUNC' AS query, routine_name
FROM information_schema.routine_privileges
WHERE grantee = 'anon' AND privilege_type = 'EXECUTE'
  AND routine_schema NOT IN ('pg_catalog','information_schema','extensions','auth','storage');

-- 9. Materialized views
SELECT 'MATVIEW' AS query, c.relname
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'm' AND n.nspname = 'public';

Read references/audit-queries.md for additional queries if deeper analysis is needed (policy reconstruction, mutable search paths, etc.).


Step 2 — Static analysis (anti-pattern matching)

Read references/anti-patterns.md for the complete 27-pattern database. Analyze every result from Step 1 against these checks. Be exhaustive — check every table, every policy, every function.

For each table, verify ALL of the following:

  1. RLS enabled? No → CRITICAL. This is the #1 cause of Supabase breaches.
  2. Has policies? RLS enabled + zero policies → MEDIUM (deny-all, likely a bug).
  3. Policies exist but RLS disabled? → CRITICAL (developer wrote policies but forgot to enable RLS — false security).
  4. SELECT policy permissive? USING(true) on sensitive tables → HIGH. On public-content tables → INFO.
  5. Write policies permissive? USING(true) or WITH CHECK(true) on INSERT/UPDATE/DELETE → CRITICAL.
  6. UPDATE has WITH CHECK? If USING without WITH CHECK → HIGH. Cross-reference: does the table have is_admin, role, plan, balance, credits columns? If so → CRITICAL (mass assignment of privileges).
  7. Policies scoped to roles? roles = {public} (no TO clause) → MEDIUM, applies to anon.
  8. Uses user_metadata? qual/with_check contains user_metadata or raw_user_meta_data → HIGH.
  9. auth.uid() wrapped? Uses auth.uid() but not (SELECT auth.uid()) → MEDIUM (performance).
  10. Multiple permissive policies for same table/op/role? → MEDIUM (OR logic trap).

For views: No security_invoker = true? → HIGH. Bypasses all RLS on underlying tables.

For functions: SECURITY DEFINER in exposed schema? → HIGH. Callable via API, bypasses RLS. No fixed search_path? → MEDIUM.

For storage: Public buckets → MEDIUM. No storage.objects policies → HIGH.

For auth: Sensitive column names in public tables → MEDIUM. Functions callable by anon → INFO (list for review).


Step 3 — Dynamic testing (safe probing)

Safety guarantee: Prefer: tx=rollback tells PostgREST to evaluate the request fully, return the result, then roll back the transaction. Zero data modified. Safe for production.

For each table, run all four CRUD tests with the anon key:

PROJECT="SUPABASE_URL"
ANON="ANON_KEY"
TABLE="TABLE_NAME"

# SELECT
curl -s "$PROJECT/rest/v1/$TABLE?select=*&limit=1" -H "apikey: $ANON" -H "Authorization: Bearer $ANON"

# INSERT (safe rollback)
curl -s -X POST "$PROJECT/rest/v1/$TABLE" -H "apikey: $ANON" -H "Authorization: Bearer $ANON" \
  -H "Content-Type: application/json" -H "Prefer: return=representation, tx=rollback" -d '{}'

# UPDATE (safe rollback)
curl -s -X PATCH "$PROJECT/rest/v1/$TABLE?id=eq.0" -H "apikey: $ANON" -H "Authorization: Bearer $ANON" \
  -H "Content-Type: application/json" -H "Prefer: tx=rollback" -d '{"id":"probe"}'

# DELETE (safe rollback)
curl -s -X DELETE "$PROJECT/rest/v1/$TABLE?id=eq.0" -H "apikey: $ANON" -H "Authorization: Bearer $ANON" \
  -H "Prefer: tx=rollback"

Response interpretation — be precise:
- Non-empty JSON array on SELECT → 🔴 DATA EXPOSED
- Empty array [] on SELECT → ✅ Protected (or table empty — note ambiguity)
- "code":"42501" → ✅ RLS denied access
- "code":"PGRST301" → ✅ JWT required
- "code":"42P01" → Table doesn't exist via API (skip)
- "code":"23502" (NOT NULL violation) on INSERT → ⚠️ RLS permitted the insert, but data validation failed. This is still a vulnerability — attacker just needs to provide valid column values.
- "code":"23505" (unique constraint) on INSERT → ⚠️ Same — RLS permitted, constraint stopped it.
- 201 or returned data on INSERT → 🔴 Anon can write
- Any successful response on UPDATE/DELETE → ⚠️ Writes potentially allowed

Ghost auth test:

curl -s "$PROJECT/auth/v1/signup" -H "apikey: $ANON" -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"Pr0beTest!2345"}'
  • Response contains "access_token" → 🔴 Ghost auth active. Unconfirmed accounts get sessions.
  • "Confirm your email" with no access_token → ✅ Email confirmation enabled.
  • "Email signups are disabled" → ✅ (or uses other auth providers).

If ghost auth succeeds: Re-run ALL table tests using the returned JWT instead of the anon key. This tests what an attacker with a trivially-obtained session can access, since many policies only check TO authenticated without further restrictions.

OpenAPI schema test:

curl -s "$PROJECT/rest/v1/" -H "apikey: $ANON" | head -100

If JSON with "paths" or "definitions" → 🟡 Table names and column types exposed.


Step 4 — Generate the security report

╔════════════════════════════════════════════════════════╗
║          SUPABASE SENTINEL SECURITY REPORT             ║
╠════════════════════════════════════════════════════════╣
║  Project:   [url]                                      ║
║  Scanned:   [date/time UTC]                            ║
║  Score:     [X/100] [emoji]                            ║
║  Summary:   [N] tables, [N] policies, [N] findings    ║
╚════════════════════════════════════════════════════════╝

Scoring: Start at 100. Deduct: CRITICAL = -25, HIGH = -10, MEDIUM = -5. Floor at 0.
Emoji: 80-100 ✅, 60-79 ⚠️, 40-59 🟠, 0-39 🔴.

For each finding:

[emoji] [SEVERITY] — [Table/Resource]: [Short Title]

  Risk:     [One sentence a non-developer understands]
  Attack:   [Concrete attacker scenario]
  Proof:    [curl command or query result that proves this]

  Fix:
  [exact SQL]

Ordering: CRITICAL first → HIGH → MEDIUM. Within severity, tables with likely-sensitive data first (users, payments, orders, tokens > posts, comments, settings).

End the report with:
1. "Passing" section — tables/resources that are properly secured.
2. Count summary: "X CRITICAL, Y HIGH, Z MEDIUM findings across N tables."
3. Offer: "Want me to generate a migration file with all fixes?"
4. Offer: "Want me to set up a GitHub Action for continuous monitoring?"
5. Limitation note: "This covers database/API security. It does not cover XSS, CSRF, SSRF, or infrastructure."


Step 5 — Generate fix SQL

Read references/fix-templates.md for the complete template library (8 categories, 7 policy patterns).

Policy generation rules — always follow these:
1. (SELECT auth.uid()) not auth.uid() — initPlan caching for performance.
2. Separate policies per operation — never FOR ALL.
3. Both USING and WITH CHECK on UPDATE policies.
4. Always scope with TO clause (authenticated, anon, or custom role).
5. app_metadata not user_metadata for authorization.
6. Generate indexes for policy columns.
7. Include the auto-enable RLS event trigger for future tables.

Determine the right policy pattern per table:
- Table has user_id column → ownership pattern (Pattern A in fix-templates)
- Table has team_id/org_id → team-based (Pattern B)
- Table has is_public/published → public-read + auth-write (Pattern C)
- Admin data → role-based via app_metadata (Pattern D)
- Sensitive data → verified-only (Pattern E) or MFA-enforced (Pattern F)

Ask user how to receive fixes: migration file, apply now, or step-by-step guidance.


Step 6 — GitHub Action (optional)

Read assets/github-action-template.yml. Create .github/workflows/supabase-sentinel.yml. User needs to add SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY as repository secrets. Action runs on migration changes + weekly, posts PR comments, fails on CRITICAL.


Step 7 — Preventive measures

Recommend these one-time hardening steps. Generate the SQL if the user wants:

  1. Auto-enable RLS event trigger — ensures future tables get RLS automatically.
  2. Move sensitive tables to private schemaapi_keys, secrets, internal_config shouldn't be API-exposed.
  3. Restrict default grants — revoke INSERT/UPDATE/DELETE from anon on read-only tables.
  4. Enable email confirmation if not already on.
  5. Review OAuth redirect URLs — no wildcards in production.
  6. Minimum 8-char passwords with leaked password protection.
  7. Consider disabling Data API if app only uses Edge Functions.
  8. Column-level privileges on tables with sensitive columns (revoke UPDATE on is_admin, role, balance).

Reference files

Load on-demand — do not read all upfront:

  • references/audit-queries.md — Full 20-query SQL library. For additional queries beyond those inlined above.
  • references/anti-patterns.md — 27 vulnerability patterns with severity, root cause, detection, Splinter lint IDs, real-world examples. Essential reading at Step 2.
  • references/fix-templates.md — SQL fix templates: enable RLS, 7 RLS policy patterns (ownership/team/public-read/role-based/verified/MFA/anonymous-block), storage policies, auth hardening, function fixes, column security, migration template. Essential at Step 5.
  • references/vibe-coding-context.md — CVE-2025-48757 details, 10 security studies (2025-2026), platform patterns (Lovable/Bolt/Replit/Cursor), why LLMs generate insecure code. Read when user asks "why."
  • assets/github-action-template.yml — CI/CD workflow. Read at Step 6.

Principles

  • Explain like a friend. Say "anyone on the internet can read your users table" not "RLS is disabled on the users relation." Explain the concrete attack scenario for every finding.
  • Every finding gets a fix. Never report a problem without exact SQL to solve it.
  • Safe testing only. Prefer: tx=rollback for writes, .invalid TLD for auth probes. Never modify production data.
  • Be thorough, not alarmist. Check every table, policy, function — but calibrate severity. USING(true) on public blog posts ≠ USING(true) on user payments.
  • Praise good security. If things are properly locked down, say so explicitly.
  • State limitations clearly. This covers database/API security, not XSS, CSRF, SSRF, or infrastructure.
  • Adapt to skill level. Technical user → be concise. Vibe-coder → explain RLS from scratch, walk through fixes.

# README.md

🛡️ Supabase Sentinel

A Claude Skill that audits your Supabase project for security vulnerabilities.

Drop it into Claude Code, Cursor, or any Claude-powered environment. Say "audit my Supabase project" and get a comprehensive security report with exact fix SQL — in minutes, not days.

170+ Lovable apps were breached. 20.1M rows were exposed across YC startups. 45% of AI-generated code introduces OWASP Top 10 vulnerabilities. Supabase's built-in Security Advisor only checks whether RLS exists — Supabase Sentinel tests whether it actually works.


What it does

Supabase Sentinel performs a 7-step security audit on any Supabase project:

  1. Scans your codebase for exposed service_role keys, hardcoded JWTs, and secrets committed to git
  2. Introspects your database schema — tables, RLS policies, views, functions, storage buckets
  3. Matches against 27 known vulnerability patterns sourced from CVE-2025-48757, 10 published security studies, and thousands of documented breaches
  4. Dynamically probes your API using the Prefer: tx=rollback technique (zero data modified, safe for production)
  5. Tests ghost auth — can attackers create unconfirmed accounts and access your data?
  6. Generates a scored security report with plain-English explanations and concrete attacker scenarios
  7. Produces exact fix SQL — copy, paste, done

Quick start

Option 1: Claude Code / Cursor

Copy the skill folder into your project:

# Clone into your project's skills directory
git clone https://github.com/Farenhytee/supabase-sentinel.git .claude/skills/supabase-sentinel

# Or if you have a central skills directory
git clone https://github.com/Farenhytee/supabase-sentinel.git ~/claude-skills/supabase-sentinel

Then just ask Claude:

Audit my Supabase project for security issues

Claude will auto-detect your Supabase credentials from .env files, run the full audit, and present a report.

Option 2: Claude.ai (with computer use)

  1. Download this repo as a ZIP
  2. Upload it to a Claude.ai conversation with computer use enabled
  3. Ask: "Use the Supabase Sentinel skill to audit my Supabase project"
  4. Provide your Supabase URL and keys when prompted

Option 3: Manual (any AI assistant)

Copy the contents of SKILL.md into your system prompt or conversation, then follow the workflow with your Supabase credentials.


What it catches

Critical

Pattern Description
RLS_DISABLED Tables without Row-Level Security — fully exposed to the internet
SERVICE_ROLE_EXPOSED service_role key in frontend code — bypasses ALL security
POLICIES_BUT_NO_RLS Policies written but RLS never enabled — false sense of security
WRITE_USING_TRUE INSERT/UPDATE/DELETE with USING(true) — anyone can modify data

High

Pattern Description
USING_TRUE_SELECT All rows readable by anonymous users on sensitive tables
VIEW_NO_SECURITY_INVOKER Views bypass RLS, running as superuser
SECURITY_DEFINER_EXPOSED Functions in public schema bypass RLS, callable via API
USER_METADATA_IN_POLICY Policies reference user-modifiable metadata — privilege escalation
UPDATE_NO_WITHCHECK UPDATE policies without WITH CHECK — mass assignment risk
GHOST_AUTH Unconfirmed email signups grant authenticated sessions
STORAGE_NO_RLS Storage bucket missing access control policies
JWT_SECRET_EXPOSED JWT signing secret leaked — can forge any user's token

Medium

Pattern Description
RLS_NO_POLICIES RLS enabled but no policies — all access silently denied (bug)
POLICY_NO_ROLE_SCOPE Policy applies to all roles including anonymous
MULTIPLE_PERMISSIVE Multiple permissive policies OR'd — most permissive wins
RLS_PERFORMANCE auth.uid() not cached — performance degradation, potential DoS
PUBLIC_BUCKET Storage bucket publicly accessible without auth
SENSITIVE_COLUMNS Columns named password, api_key, etc. exposed via API
+ 9 more patterns See references/anti-patterns.md for the full list

Example output

╔════════════════════════════════════════════════════════╗
║          SUPABASE SENTINEL SECURITY REPORT             ║
╠════════════════════════════════════════════════════════╣
║  Project:   https://myapp.supabase.co                  ║
║  Scanned:   2026-03-15 14:30 UTC                       ║
║  Score:     35/100 🔴                                   ║
║  Summary:   12 tables, 8 policies, 7 findings          ║
╚════════════════════════════════════════════════════════╝

🔴 CRITICAL — users: RLS Disabled

  Risk:     Anyone on the internet can read your entire users table
  Attack:   Open browser DevTools → copy anon key → curl the API → dump all emails, names, metadata
  Proof:    curl returns [{"id":"...","email":"[email protected]",...}]

  Fix:
  ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;

  CREATE POLICY "users_select_own"
    ON public.users FOR SELECT TO authenticated
    USING ((SELECT auth.uid()) = id);

🟠 HIGH — profiles: SELECT policy uses USING(true)

  Risk:     All user profiles are readable by anyone, including anonymous users
  Attack:   Enumerate all profiles via the API to harvest user data
  ...

✅ PASSING: orders, payments, invoices, subscriptions (4 tables properly secured)

File structure

supabase-sentinel/
├── SKILL.md                           # Core skill — 7-step audit workflow (333 lines)
├── references/
│   ├── audit-queries.md               # 20 SQL queries for schema introspection
│   ├── anti-patterns.md               # 27 vulnerability patterns with severity/detection/fix
│   ├── fix-templates.md               # SQL fix templates — 7 RLS patterns, storage, auth, prevention
│   └── vibe-coding-context.md         # CVE-2025-48757, research studies, platform analysis
├── assets/
│   └── github-action-template.yml     # CI/CD workflow for continuous monitoring
├── README.md
└── LICENSE                            # MIT

How progressive disclosure works: When Claude loads this skill, it only reads the 333-line SKILL.md initially (~5000 tokens). Reference files are loaded on-demand during specific audit steps — audit-queries.md at Step 1, anti-patterns.md at Step 2, fix-templates.md at Step 5. This keeps context usage efficient.


Continuous monitoring (GitHub Action)

Supabase Sentinel can generate a GitHub Action that:
- Runs on every push to supabase/migrations/
- Runs weekly (Monday 6am UTC)
- Posts findings as PR comments
- Blocks merges on CRITICAL findings

Just ask: "Set up continuous security monitoring for this project."

See assets/github-action-template.yml for the template.


Research backing

This skill's anti-pattern database is sourced from:

  • CVE-2025-48757 — 170+ Lovable apps exposed, CVSS 9.3 (Matt Palmer, May 2025)
  • Escape.tech — 2,000+ vulnerabilities across 5,600 vibe-coded apps (October 2025)
  • Veracode — 45% of AI-generated code introduces OWASP Top 10 vulnerabilities (July 2025)
  • Carnegie Mellon SusVibes — 82.8% of functionally correct AI code was insecure (December 2025)
  • SupaExplorer — 11% of indie apps expose Supabase credentials (January 2026)
  • ModernPentest — 20.1M rows exposed across 107 YC startups (March 2026)
  • Wiz Research — Critical auth bypass in Base44 vibe-coding platform (July 2025)
  • Supabase Security Retro 2025 — Official documentation of built-in advisor capabilities and gaps
  • Supabase Splinter — All 16 official security lints mapped and extended

See references/vibe-coding-context.md for the full research breakdown.


What Supabase Sentinel catches that Supabase Security Advisor doesn't

Supabase's built-in Security Advisor (Splinter) runs 16 lints. Supabase Sentinel extends this with:

Gap What Splinter misses Supabase Sentinel covers
Policy correctness Only checks if policies exist Tests if they actually prevent unauthorized access
Dynamic probing Static analysis only Live API testing with tx=rollback
Ghost auth Not checked Tests email confirmation bypass
Mass assignment Not checked Detects UPDATE without WITH CHECK + sensitive columns
Storage config Not checked Audits bucket visibility and storage.objects RLS
Codebase scanning Not applicable Finds service_role keys in frontend code
Key exposure Not checked Detects hardcoded JWTs and committed .env files
Column-level security Not checked Flags sensitive columns accessible via API
CI/CD integration Not available GitHub Action for continuous monitoring

Contributing

Contributions are welcome! The most valuable contributions are:

  1. New anti-patterns — Found a Supabase security issue not in our database? Add it to references/anti-patterns.md with severity, detection query, fix SQL, and real-world evidence.
  2. Fix template improvements — Better RLS policy patterns, edge cases, or performance optimizations in references/fix-templates.md.
  3. Testing on real projects — Run Supabase Sentinel on your own Supabase projects and report false positives/negatives.
  4. Platform-specific patterns — Document security patterns specific to Lovable, Bolt, Replit, or other vibe-coding platforms.

How to contribute

  1. Fork this repo
  2. Create a branch (git checkout -b add-new-pattern)
  3. Add your changes with clear documentation
  4. Submit a PR with a description of the pattern and evidence

Roadmap

  • [ ] CLI toolnpx supabase-sentinel audit for non-Claude environments
  • [ ] MCP server — programmatic access for CI/CD and dashboards
  • [ ] Firebase support — extend to Firebase Security Rules auditing
  • [ ] Premium dashboard — historical trending, multi-project views, Slack alerts
  • [ ] VS Code extension — inline security warnings in the editor

Safety

Supabase Sentinel is designed to be safe for production use:

  • Dynamic tests use Prefer: tx=rollback — PostgREST processes the request, evaluates RLS, returns the result, then rolls back. Zero data modified.
  • Auth probes use .invalid TLD — test emails use RFC 2606 reserved domains that cannot receive mail.
  • Read-only introspection — schema queries only read pg_tables, pg_policies, and information_schema. No DDL or DML.
  • Open source — audit the auditor. Every query and test is visible in the source.

License

MIT — use it however you want, commercially or otherwise.


Built for the vibe-coding era.
Because "it works" and "it's secure" are two very different things.

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.