home / skills / yoanbernabeu / supabase-pentest-skills / supabase-extract-service-key

supabase-extract-service-key skill

/skills/extraction/supabase-extract-service-key

This skill detects leaked service_role keys in client-side code during security audits, ensuring immediate remediation and safe Supabase usage.

npx playbooks add skill yoanbernabeu/supabase-pentest-skills --skill supabase-extract-service-key

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
11.7 KB
---
name: supabase-extract-service-key
description: CRITICAL - Detect if the Supabase service_role key is leaked in client-side code. This is a P0 severity issue.
---

# 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-context.json`** with findings:
   ```json
   {
     "supabase": {
       "service_key_exposed": true/false,
       "service_key_location": "path:line"
     },
     "findings": [
       {
         "id": "SERVICE_KEY_EXPOSED",
         "severity": "P0",
         "title": "Service Role Key Exposed",
         ...
       }
     ]
   }
   ```

2. **Log to `.sb-pentest-audit.log`**:
   ```
   [TIMESTAMP] [supabase-extract-service-key] [START] Checking for service key exposure
   [TIMESTAMP] [supabase-extract-service-key] [CRITICAL] Service key EXPOSED at path:line
   [TIMESTAMP] [supabase-extract-service-key] [CONTEXT_UPDATED] .sb-pentest-context.json updated
   ```

3. **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/service-key-exposure/`

### Evidence Files to Create (if service key found)

| File | Content |
|------|---------|
| `service-key-exposure/location.txt` | File path and line number |
| `service-key-exposure/decoded-payload.json` | Decoded JWT proving it's service_role |
| `service-key-exposure/code-snippet.txt` | Code context (redacted) |

### Evidence Format (P0 Finding)

```json
{
  "evidence_id": "EXT-SVC-001",
  "timestamp": "2025-01-31T10:10:00Z",
  "category": "extraction",
  "type": "service_key_exposure",
  "severity": "P0",
  "finding_id": "P0-001",

  "key_data": {
    "key_prefix": "eyJhbGciOiJIUzI1NiI...",
    "key_suffix": "...xxxx",
    "role": "service_role"
  },

  "decoded_payload": {
    "iss": "supabase",
    "ref": "abc123def",
    "role": "service_role",
    "iat": "2021-12-20T00:00:00Z",
    "exp": "2031-12-20T00:00:00Z"
  },

  "location": {
    "file": "/static/js/admin.chunk.js",
    "line": 89,
    "context": "const SUPABASE_KEY = 'eyJhbG...' // [REDACTED]"
  },

  "impact": {
    "rls_bypass": true,
    "full_db_access": true,
    "auth_users_access": true,
    "storage_access": true
  },

  "curl_command": "curl -X GET 'https://abc123def.supabase.co/rest/v1/users' -H 'apikey: [SERVICE_KEY]' -H 'Authorization: Bearer [SERVICE_KEY]'"
}
```

### Add to timeline.md (P0)

```markdown
## [TIMESTAMP] - šŸ”“ P0 CRITICAL: Service Role Key Exposed
- Service role key found in client-side code
- Location: [file]:[line]
- Impact: Full database access, RLS bypass
- Evidence: `02-extraction/service-key-exposure/`
- **IMMEDIATE ACTION REQUIRED**
```

## Related Skills

- `supabase-extract-anon-key` — Extract the (expected) anon key
- `supabase-audit-tables-read` — Test what data is accessible
- `supabase-report` — Generate full report including this finding

Overview

This skill detects whether a Supabase service_role (admin) key is leaked in client-side code and flags it as a P0 (critical) finding. It focuses on identifying JWTs or environment variables exposing a role of "service_role", unsafe variable names, and exposed source maps that may reveal secrets. The skill produces actionable evidence, remediation steps, and must record findings progressively during the scan.

How this skill works

The skill inspects HTML, JavaScript bundles, inline scripts, and exposed source maps for patterns matching service_role JWTs and common service-key variable names. It decodes any discovered JWT payload to confirm the role claim and captures file path, line number, and redacted code snippets as evidence. When a leak is found the skill outputs a P0 finding with immediate remediation, creates evidence files, and updates audit/context logs incrementally.

When to use it

  • As part of every Supabase security audit (mandatory critical check)
  • Before production deployment or public releases
  • After detecting Supabase usage on a target app or domain
  • When reviewing build or CI configurations for environment variable leaks
  • If source maps are publicly accessible and may contain secrets

Best practices

  • Treat any client-side service_role key as an immediate P0 incident and rotate the key immediately
  • Write findings and audit logs progressively as discoveries are made to avoid data loss
  • Disable or remove source maps in production builds to prevent revealing source and secrets
  • Use NEXT_PUBLIC_ only for anon keys; never expose service_role in client env vars
  • Move privileged operations to server-side code or Edge Functions and restrict service keys to server environments

Example use cases

  • Scan a public web app to verify no service_role key appears in JS bundles or inline scripts
  • Deep-scan deployed assets and associated .map files to find keys leaked through source maps
  • Validate a pre-deploy build to ensure environment variables were not bundled into client code
  • After discovering a Supabase project ref, extract keys and produce P0 evidence for incident response
  • Generate remediation advice and a timeline entry for an emergency security report

FAQ

Why is a service_role key exposure P0 critical?

The service_role key bypasses all Row Level Security and grants full read/write access to the database and storage, enabling user impersonation and total data compromise.

What immediate actions should be taken if a service_role key is found?

Rotate the service_role key in the Supabase dashboard, remove the key from client code, audit logs for abuse, and move privileged operations to server-side or Edge Functions.