Supabase JWT Extraction
🔴 CRITICAL: PROGRESSIVE FILE UPDATES REQUIRED
You MUST write to context files AS YOU GO, not just at the end.
-
Write to .sb-pentest-context.json IMMEDIATELY after each discovery
-
Log to .sb-pentest-audit.log BEFORE and AFTER each action
-
DO NOT wait until the skill completes to update files
-
If the skill crashes or is interrupted, all prior findings must already be saved
This is not optional. Failure to write progressively is a critical error.
This skill extracts and analyzes JSON Web Tokens (JWTs) related to Supabase from client-side code.
When to Use This Skill
-
To find all JWT tokens exposed in client code
-
To analyze token claims and expiration
-
To detect hardcoded user tokens (security issue)
-
To understand the authentication flow
Prerequisites
-
Target application accessible
-
Supabase detection completed (auto-invokes if needed)
Types of JWTs in Supabase
Type Purpose Client Exposure
Anon Key API authentication ✅ Expected
Service Role Key Admin access ❌ Never
Access Token User session ⚠️ Dynamic only
Refresh Token Token renewal ⚠️ Dynamic only
Detection Patterns
- API Keys (Static)
// Supabase API keys are JWTs const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
- Hardcoded User Tokens (Problem)
// ❌ Should never be hardcoded const userToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIn0...'
- Storage Key Patterns
// Code referencing where JWTs are stored localStorage.getItem('supabase.auth.token') localStorage.getItem('sb-abc123-auth-token') sessionStorage.getItem('supabase_session')
Usage
Basic Extraction
Extract JWTs from https://myapp.example.com
With Claim Analysis
Extract and analyze all JWTs from https://myapp.example.com
Output Format
═══════════════════════════════════════════════════════════ JWT EXTRACTION RESULTS ═══════════════════════════════════════════════════════════
Found: 3 JWTs
───────────────────────────────────────────────────────── JWT #1: Supabase Anon Key ───────────────────────────────────────────────────────── Type: API Key (anon) Status: ✅ Expected in client code
Header: ├── alg: HS256 └── typ: JWT
Payload: ├── iss: supabase ├── ref: abc123def ├── role: anon ├── iat: 2021-12-20T00:00:00Z └── exp: 2031-12-20T00:00:00Z
Location: /static/js/main.js:1247
───────────────────────────────────────────────────────── JWT #2: Hardcoded User Token ⚠️ ───────────────────────────────────────────────────────── Type: User Access Token Status: ⚠️ P1 - Should not be hardcoded
Header: ├── alg: HS256 └── typ: JWT
Payload: ├── sub: 12345678-1234-1234-1234-123456789012 ├── email: developer@company.com ├── role: authenticated ├── iat: 2025-01-15T10:00:00Z └── exp: 2025-01-15T11:00:00Z (EXPIRED)
Location: /static/js/debug.js:45
Risk: This token may belong to a real user account. Even if expired, it reveals user information.
───────────────────────────────────────────────────────── JWT #3: Storage Reference ───────────────────────────────────────────────────────── Type: Storage Key Pattern Status: ℹ️ Informational
Pattern: localStorage.getItem('sb-abc123def-auth-token') Location: /static/js/auth.js:89
Note: This is the expected storage key for user sessions. Actual token value is set at runtime.
═══════════════════════════════════════════════════════════
JWT Claim Analysis
The skill identifies key claims:
Standard Claims
Claim Description Security Impact
sub
User ID Identifies specific user
User email PII exposure if hardcoded
role
Permission level service_role is critical
exp
Expiration Expired tokens less risky
iat
Issued at Indicates when created
Supabase-Specific Claims
Claim Description
ref
Project reference
iss
Should be "supabase"
aal
Authenticator assurance level
amr
Authentication methods used
Security Findings
P0 - Critical
🔴 Service role key exposed (role: service_role) → Immediate key rotation required
P1 - High
🟠 User token hardcoded with PII (email, sub visible) → Remove from code, may need to notify user
P2 - Medium
🟡 Expired test token in code → Clean up, potential information disclosure
Context Output
Saved to .sb-pentest-context.json :
{ "jwts": { "found": 3, "api_keys": [ { "type": "anon", "project_ref": "abc123def", "location": "/static/js/main.js:1247" } ], "user_tokens": [ { "type": "access_token", "hardcoded": true, "severity": "P1", "claims": { "sub": "12345678-1234-1234-1234-123456789012", "email": "developer@company.com", "expired": true }, "location": "/static/js/debug.js:45" } ], "storage_patterns": [ { "pattern": "sb-abc123def-auth-token", "storage": "localStorage", "location": "/static/js/auth.js:89" } ] } }
Common Issues
❌ Problem: JWT appears truncated ✅ Solution: May span multiple lines. The skill attempts to reassemble.
❌ Problem: JWT won't decode ✅ Solution: May be encrypted (JWE) or custom format. Noted as undecodable.
❌ Problem: Many false positives ✅ Solution: Base64 strings that look like JWTs. Skill validates structure.
Remediation for Hardcoded Tokens
Before (Wrong)
// ❌ Never hardcode user tokens
const adminToken = 'eyJhbGciOiJIUzI1NiI...'
fetch('/api/admin', {
headers: { Authorization: Bearer ${adminToken} }
})
After (Correct)
// ✅ Get token from Supabase session
const { data: { session } } = await supabase.auth.getSession()
fetch('/api/admin', {
headers: { Authorization: Bearer ${session.access_token} }
})
MANDATORY: Progressive Context File Updates
⚠️ This skill MUST update tracking files PROGRESSIVELY during execution, NOT just at the end.
Critical Rule: Write As You Go
DO NOT batch all writes at the end. Instead:
-
Before starting any action → Log the action to .sb-pentest-audit.log
-
After each discovery → Immediately update .sb-pentest-context.json
-
After each significant step → Log completion to .sb-pentest-audit.log
This ensures that if the skill is interrupted, crashes, or times out, all findings up to that point are preserved.
Required Actions (Progressive)
Update .sb-pentest-context.json with extracted data:
{ "jwts": { "found": 3, "api_keys": [ ... ], "user_tokens": [ ... ], "storage_patterns": [ ... ] } }
Log to .sb-pentest-audit.log :
[TIMESTAMP] [supabase-extract-jwt] [START] Beginning JWT extraction [TIMESTAMP] [supabase-extract-jwt] [SUCCESS] Found 3 JWTs [TIMESTAMP] [supabase-extract-jwt] [CONTEXT_UPDATED] .sb-pentest-context.json updated
If files don't exist, create them before writing.
FAILURE TO UPDATE CONTEXT FILES IS NOT ACCEPTABLE.
MANDATORY: Evidence Collection
📁 Evidence Directory: .sb-pentest-evidence/02-extraction/
Evidence Files to Create
File Content
extracted-jwts.json
All JWTs found with analysis
Evidence Format
{ "evidence_id": "EXT-JWT-001", "timestamp": "2025-01-31T10:08:00Z", "category": "extraction", "type": "jwt_extraction",
"jwts_found": [ { "type": "anon_key", "severity": "info", "location": "/static/js/main.js:1247", "decoded_payload": { "iss": "supabase", "ref": "abc123def", "role": "anon" } }, { "type": "hardcoded_user_token", "severity": "P1", "location": "/static/js/debug.js:45", "decoded_payload": { "sub": "[REDACTED]", "email": "[REDACTED]@example.com", "role": "authenticated", "exp": "2025-01-15T11:00:00Z" }, "expired": true, "issue": "Hardcoded user token with PII" } ],
"storage_patterns_found": [ { "pattern": "localStorage.getItem('sb-abc123def-auth-token')", "location": "/static/js/auth.js:89" } ] }
Related Skills
-
supabase-extract-anon-key — Specifically extracts the anon key
-
supabase-extract-service-key — Checks for service key (critical)
-
supabase-audit-auth-config — Analyzes auth configuration