Extract and decode Supabase-related JWTs from client-side code, cookies, and local storage patterns.
Install
npx skillscat add yoanbernabeu/supabase-pentest-skills/supabase-extract-jwt Install via the SkillsCat registry.
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.jsonIMMEDIATELY after each discovery- Log to
.sb-pentest-audit.logBEFORE 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
1. API Keys (Static)
// Supabase API keys are JWTs
const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'2. Hardcoded User Tokens (Problem)
// โ Should never be hardcoded
const userToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIn0...'3. 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.comWith Claim Analysis
Extract and analyze all JWTs from https://myapp.example.comOutput 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 |
email |
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 requiredP1 - High
๐ User token hardcoded with PII (email, sub visible)
โ Remove from code, may need to notify userP2 - Medium
๐ก Expired test token in code
โ Clean up, potential information disclosureContext 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.jsonwith 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 updatedIf 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 keysupabase-extract-service-keyโ Checks for service key (critical)supabase-audit-auth-configโ Analyzes auth configuration