Claude
Skills
← Back

supabase-extract-service-key

CRITICAL - Detect if the Supabase service_role key is leaked in client-side code. This is a P0 severity issue.

General

What this skill does


# Supabase Service Key Detection

> šŸ”“ **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 detects if the **service_role key** (admin key) is accidentally exposed in client-side code.

## When to Use This Skill

- As part of every security audit (this is critical)
- When reviewing code before production deployment
- After detecting Supabase usage to check for this common mistake

## Prerequisites

- Target application accessible
- Supabase detection completed (auto-invokes if needed)

## Why This Is Critical

The **service_role key** bypasses ALL Row Level Security (RLS) policies. If exposed:

| Impact | Description |
|--------|-------------|
| šŸ”“ Full DB Access | Read/write/delete all data in all tables |
| šŸ”“ Auth Bypass | Access all user data without authentication |
| šŸ”“ Storage Access | Read/write all files in all buckets |
| šŸ”“ User Impersonation | Generate tokens for any user |

**This is a P0 (Critical) finding that requires immediate action.**

## Service Key vs Anon Key

| Aspect | Anon Key | Service Key |
|--------|----------|-------------|
| Role claim | `"role": "anon"` | `"role": "service_role"` |
| RLS | āœ… Respects RLS | āŒ Bypasses RLS |
| Client-side | āœ… Expected | āŒ NEVER |
| Server-side | āœ… Can use | āœ… Should use |

## Detection Patterns

The skill searches for:

### 1. Key with service_role Claim

```javascript
// Decoded JWT payload contains:
{
  "role": "service_role",  // āŒ CRITICAL if in client code
  "iss": "supabase",
  "ref": "abc123def"
}
```

### 2. Variable Names

```javascript
// Common naming patterns
SUPABASE_SERVICE_KEY
SUPABASE_SERVICE_ROLE_KEY
SUPABASE_ADMIN_KEY
SUPABASE_SECRET_KEY
SERVICE_ROLE_KEY
```

### 3. Accidental Exposure

```javascript
// Sometimes exposed alongside anon key
const keys = {
  anon: 'eyJ...',
  service: 'eyJ...'  // āŒ Should not be here
}
```

## Usage

### Basic Check

```
Check for service key leak on https://myapp.example.com
```

### Deep Scan

```
Deep scan for service key exposure on https://myapp.example.com
```

## Output Format

### No Service Key Found (Good)

```
═══════════════════════════════════════════════════════════
 SERVICE KEY CHECK
═══════════════════════════════════════════════════════════

 Status: āœ… No service_role key detected in client code

 Scanned:
 ā”œā”€ā”€ HTML source: Clean
 ā”œā”€ā”€ JavaScript bundles: 5 files, 2.3MB analyzed
 ā”œā”€ā”€ Inline scripts: 12 blocks checked
 └── Source maps: Not exposed (good)

 JWT Analysis:
 └── 1 key found, confirmed role=anon (safe)

 Result: PASS - No critical key exposure
═══════════════════════════════════════════════════════════
```

### Service Key FOUND (Critical)

```
═══════════════════════════════════════════════════════════
 šŸ”“ CRITICAL: SERVICE KEY EXPOSED
═══════════════════════════════════════════════════════════

 Severity: P0 - CRITICAL
 Status: āŒ service_role key found in client-side code!

 āš ļø  IMMEDIATE ACTION REQUIRED āš ļø

 Exposed Key:
 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBh
 YmFzZSIsInJlZiI6ImFiYzEyM2RlZiIsInJvbGUiOiJzZXJ2aWNlX3
 JvbGUiLCJpYXQiOjE2NDAwMDAwMDAsImV4cCI6MTk1NTM2MDAwMH0
 .xxxxxxxxxxxxx

 Location:
 └── /static/js/admin.chunk.js (line 89)
     const SUPABASE_KEY = 'eyJhbG...'  // Used in createClient()

 Decoded Payload:
 ā”œā”€ā”€ role: service_role ← CRITICAL
 ā”œā”€ā”€ ref: abc123def
 └── exp: 2031-12-20

 Impact Assessment:
 ā”œā”€ā”€ šŸ”“ Full database access possible
 ā”œā”€ā”€ šŸ”“ All RLS policies bypassed
 ā”œā”€ā”€ šŸ”“ All user data exposed
 └── šŸ”“ All storage buckets accessible

 ═══════════════════════════════════════════════════════════
 IMMEDIATE REMEDIATION STEPS
 ═══════════════════════════════════════════════════════════

 1. ROTATE THE KEY NOW
    → Supabase Dashboard > Settings > API > Regenerate service_role key

 2. REMOVE FROM CLIENT CODE
    → Delete the key from your source code
    → Redeploy your application

 3. AUDIT FOR ABUSE
    → Check Supabase logs for unauthorized access
    → Review database for unexpected changes

 4. USE EDGE FUNCTIONS
    → Move privileged operations to Edge Functions
    → Client calls Edge Function, which uses service key server-side

 Documentation:
 → https://supabase.com/docs/guides/api/api-keys
 → https://supabase.com/docs/guides/functions

═══════════════════════════════════════════════════════════
```

## Context Output

Saved to `.sb-pentest-context.json`:

```json
{
  "findings": [
    {
      "id": "SERVICE_KEY_EXPOSED",
      "severity": "P0",
      "title": "Service Role Key Exposed in Client Code",
      "description": "The service_role key was found in client-side JavaScript",
      "location": {
        "file": "/static/js/admin.chunk.js",
        "line": 89
      },
      "evidence": {
        "key_prefix": "eyJhbGciOiJIUzI1NiI...",
        "role": "service_role",
        "project_ref": "abc123def"
      },
      "remediation": {
        "immediate": "Rotate key in Supabase Dashboard",
        "long_term": "Move to Edge Functions",
        "docs": "https://supabase.com/docs/guides/api/api-keys"
      }
    }
  ],
  "supabase": {
    "service_key_exposed": true,
    "service_key_location": "/static/js/admin.chunk.js:89"
  }
}
```

## Source Maps Check

The skill also checks for exposed source maps that might reveal keys:

```
Source Maps Analysis:
ā”œā”€ā”€ main.js.map: āŒ Exposed (may contain secrets)
ā”œā”€ā”€ vendor.js.map: āŒ Exposed
└── Recommendation: Disable source maps in production

To check source maps content:
→ Add .map to JS URLs: /static/js/main.js.map
```

## Common Causes

| Cause | Solution |
|-------|----------|
| Wrong env variable | Use `NEXT_PUBLIC_` only for anon key |
| Copy-paste error | Double-check which key you're using |
| Debug code left in | Remove before production build |
| Misconfigured bundler | Ensure service key env vars are not included |

## Remediation Code Examples

### Before (Wrong)

```javascript
// āŒ WRONG - Service key in client
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_SERVICE_KEY  // āŒ NEVER DO THIS
)
```

### After (Correct)

```javascript
// āœ… CORRECT - Only anon key in client
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY  // āœ… Safe for client
)

// For privileged operations, call an Edge Function:
const { data } = await supabase.functions.invoke('admin-action', {
  body: { action: 'delete-user', userId: '123' }
})
```

### Edge Function (Server-Side)

```typescript
// supabase/functions/admin-action/index.ts
import { createClient } from '@supabase/supabase-js'

Deno.serve(async (req) => {
  // āœ… Service key only on server
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL'),
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')  // āœ… Safe on server
  )

  // Perform privileged operation
  // ...
})
```

## 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:

1. **Before starting any action** → Log the action to `.sb-pentest-audit.log`
2. **After each discovery** → Immediately update `.sb-pentest-context.json`
3. **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)

1. **Update `.sb-pentest-con

Related in General