Skip to main content
skill CX Codex

supabase-audit-functions — Claude Code skill: Edge Functions audit

A skill that finds exposed Supabase Edge Functions and tests them for missing auth, IDOR and open admin endpoints.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-functions -a claude-code
Open on GitHub

What supabase-audit-functions does

The skill enumerates Edge Functions in a Supabase project and runs four checks on each: discovery, JWT requirements, input validation, error handling. Findings go into .sb-pentest-context.json right after a function is tested, and the run log lands in .sb-pentest-audit.log before and after every test. Interrupt the skill and the results are still on disk.

The report walks through functions one at a time. A public hello-world with no auth is fine. A get-user-data that returns another user's records from a user_id parameter is not, and neither is an admin-panel reachable by any logged-in user. Both are P0. Each finding comes with a TypeScript fix: verify ownership against the JWT, check the role in the profiles table.

You need the Supabase project URL and a completed detection step before running this. Webhook signature checks can't be tested without a valid signature, so a webhook-handler gets a partial verdict at best.

What it helps with

  • find Edge Functions exposed without authentication
  • catch IDOR where a function returns any user's data from user_id
  • check whether an admin endpoint verifies the caller's role
  • see if error responses leak stack traces or signature format
  • probe input validation: missing amount, negative amount, string instead of number

Example prompts for the agent

  • Audit the Edge Functions on my Supabase project
  • Test the process-payment function for security issues
  • Check whether admin-panel requires an admin role

How to install supabase-audit-functions

1Skills CLI (Vercel)

npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-functions -a claude-code

2Manually: put the files in place

~/.claude/skills/supabase-audit-functions/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/supabase-audit-functions/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about supabase-audit-functions

How do I install supabase-audit-functions in Claude Code?

Run npx skills add yoanbernabeu/supabase-pentest-skills --skill supabase-audit-functions -a claude-code. The skill lands in ~/.claude/skills/supabase-audit-functions/SKILL.md.

What does supabase-audit-functions do?

It discovers Edge Functions in a Supabase project and checks each one for auth, access control, input validation and leaks in error messages. Findings are written to .sb-pentest-context.json as the audit runs.

What do I need before running it?

A Supabase project URL and a finished detection step. Webhook checks need a valid signature, so those functions get only a partial result.

Source text

SKILL.md · MIT
---
name: supabase-audit-functions
description: Discover and test Supabase Edge Functions for security vulnerabilities and misconfigurations.
---

# Edge Functions Audit

> 🔴 **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 function tested**
> - Log to `.sb-pentest-audit.log` **BEFORE and AFTER each function test**
> - **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 discovers and tests Supabase Edge Functions for security issues.

## When to Use This Skill

- To discover exposed Edge Functions
- To test function authentication requirements
- To check for input validation issues
- As part of comprehensive security audit

## Prerequisites

- Supabase URL available
- Detection completed

## Understanding Edge Functions

Supabase Edge Functions are Deno-based serverless functions:

```
https://[project].supabase.co/functions/v1/[function-name]
```

| Security Aspect | Consideration |
|-----------------|---------------|
| Authentication | Functions can require JWT or be public |
| CORS | Cross-origin access control |
| Input Validation | User input handling |
| Secrets | Environment variable exposure |

## Tests Performed

| Test | Purpose |
|------|---------|
| Function discovery | Find exposed functions |
| Auth requirements | Check if JWT required |
| Input validation | Test for injection |
| Error handling | Check for information disclosure |

## Usage

### Basic Function Audit

```
Audit Edge Functions on my Supabase project
```

### Test Specific Function

```
Test the process-payment Edge Function for security issues
```

## Output Format

```
═══════════════════════════════════════════════════════════
 EDGE FUNCTIONS AUDIT
═══════════════════════════════════════════════════════════

 Project: abc123def.supabase.co
 Endpoint: https://abc123def.supabase.co/functions/v1/

 ─────────────────────────────────────────────────────────
 Function Discovery
 ─────────────────────────────────────────────────────────

 Discovery Method: Common name enumeration + client code analysis

 Functions Found: 5

 ─────────────────────────────────────────────────────────
 1. hello-world
 ─────────────────────────────────────────────────────────

 Endpoint: /functions/v1/hello-world
 Method: GET, POST

 Authentication Test:
 ├── Without JWT: ✅ 200 OK
 └── Status: ℹ️ Public function (no auth required)

 Response:
 ```json
 {"message": "Hello, World!"}
 ```

 Assessment: ✅ APPROPRIATE
 Simple public endpoint, no sensitive operations.

 ─────────────────────────────────────────────────────────
 2. process-payment
 ─────────────────────────────────────────────────────────

 Endpoint: /functions/v1/process-payment
 Method: POST

 Authentication Test:
 ├── Without JWT: ❌ 401 Unauthorized
 ├── With valid JWT: ✅ 200 OK
 └── Status: ✅ Authentication required

 Input Validation Test:
 ├── Missing amount: ❌ 400 Bad Request (good)
 ├── Negative amount: ❌ 400 Bad Request (good)
 ├── String amount: ❌ 400 Bad Request (good)
 └── Valid input: ✅ 200 OK

 Error Response Test:
 ├── Error format: Generic message (good)
 └── Stack trace: ❌ Not exposed (good)

 Assessment: ✅ PROPERLY SECURED
 Requires auth, validates input, safe error handling.

 ─────────────────────────────────────────────────────────
 3. get-user-data
 ─────────────────────────────────────────────────────────

 Endpoint: /functions/v1/get-user-data
 Method: GET

 Authentication Test:
 ├── Without JWT: ❌ 401 Unauthorized
 └── Status: ✅ Authentication required

 Authorization Test:
 ├── Request own data: ✅ 200 OK
 ├── Request other user's data: ✅ 200 OK ← 🔴 P0!
 └── Status: 🔴 BROKEN ACCESS CONTROL

 Test:
 ```bash
 # As user A, request user B's data
 curl https://abc123def.supabase.co/functions/v1/get-user-data?user_id=user-b-id \
   -H "Authorization: Bearer [user-a-token]"

 # Returns user B's data!
 ```

 Finding: 🔴 P0 - IDOR VULNERABILITY
 Function accepts user_id parameter without verifying
 that the authenticated user is requesting their own data.

 Fix:
 ```typescript
 // In Edge Function
 const { user_id } = await req.json();
 const jwt_user = getUser(req); // From JWT

 // Verify ownership
 if (user_id !== jwt_user.id) {
   return new Response('Forbidden', { status: 403 });
 }
 ```

 ─────────────────────────────────────────────────────────
 4. admin-panel
 ─────────────────────────────────────────────────────────

 Endpoint: /functions/v1/admin-panel
 Method: GET, POST

 Authentication Test:
 ├── Without JWT: ❌ 401 Unauthorized
 ├── With regular user JWT: ✅ 200 OK ← 🔴 P0!
 └── Status: 🔴 MISSING ROLE CHECK

 Finding: 🔴 P0 - PRIVILEGE ESCALATION
 Admin function accessible to any authenticated user.
 No role verification in function code.

 Fix:
 ```typescript
 // Verify admin role
 const user = getUser(req);
 const { data: profile } = await supabase
   .from('profiles')
   .select('is_admin')
   .eq('id', user.id)
   .single();

 if (!profile?.is_admin) {
   return new Response('Forbidden', { status: 403 });
 }
 ```

 ─────────────────────────────────────────────────────────
 5. webhook-handler
 ─────────────────────────────────────────────────────────

 Endpoint: /functions/v1/webhook-handler
 Method: POST

 Authentication Test:
 ├── Without JWT: ✅ 200 OK (expected for webhooks)
 └── Status: ℹ️ Public (webhook endpoints are typically public)

 Webhook Security Test:
 ├── Signature validation: ⚠️ Unable to test (need valid signature)
 └── Rate limiting: Unknown

 Error Response Test:
 ```json
 {
   "error": "Invalid signature",
   "expected": "sha256=abc123...",
   "received": "sha256=xyz789..."
 }
 ```

 Finding: 🟠 P1 - INFORMATION DISCLOSURE
 Error response reveals expected signature format.
 Could help attacker understand validation mechanism.

 Fix:
 ```typescript
 // Generic error, log details server-side
 if (!validSignature) {
   console.error(`Invalid signature: expected ${expected}, got ${received}`);
   return new Response('Unauthorized', { status: 401 });
 }
 ```

 ─────────────────────────────────────────────────────────
 Summary
 ─────────────────────────────────────────────────────────

 Functions Found: 5

 Security Assessment:
 ├── ✅ Secure: 2 (hello-world, process-payment)
 ├── 🔴 P0: 2 (get-user-data IDOR, admin-panel privilege escalation)
 └── 🟠 P1: 1 (webhook-handler info disclosure)

 Critical Findings:
 1. IDOR in get-user-data - any user can access any user's data
 2. Missing role check in admin-panel - any user is admin

 Priority Actions:
 1. Fix get-user-data to verify user owns requested data
 2. Add admin role verification to admin-panel
 3. Fix webhook-handler error messages

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

## Common Function Vulnerabilities

| Vulnerability | Description | Severity |
|---------------|-------------|----------|
| No auth | Function accessible without JWT | P0-P2 |
| IDOR | User can access other users' data | P0 |
| Missing role check | Regular user accesses admin functions | P0 |
| Input injection | User input not validated | P0-P1 |
| Info disclosure | Errors reveal internal details | P1-P2 |
| CORS misconfigured | Accessible from unintended origins | P1-P2 |

## Function Discovery Methods

### 1. Client Code Analysis

```javascript
// Look for function invocations in client code
supabase.functions.invoke('function-name', {...})
fetch('/functions/v1/function-name', {...})
```

### 2. Common Name Enumeration

Tested function names:
- hello-world, hello, test
- process-payment, payment, checkout
- get-user-data, user, profile
- admin, admin-panel, dashboard
- webhook, webhook-handler, stripe-webhook
- send-email, notify, notification

### 3. Error Response Analysis

```
404 Not Found → Function doesn't exist
401 Unauthorized → Function exists, needs auth
200 OK → Function exists, accessible
```

## Context Output

```json
{
  "functions_audit": {
    "timestamp": "2025-01-31T14:30:00Z",
    "functions_found": 5,
    "findings": [
      {
        "function": "get-user-data",
        "severity": "P0",
        "vulnerability": "IDOR",
        "description": "Any authenticated user can access any user's data",
        "remediation": "Verify user owns requested resource"
      },
      {
        "function": "admin-panel",
        "severity": "P0",
        "vulnerability": "Privilege Escalation",
        "description": "No role check, any authenticated user is admin",
        "remediation": "Add admin role verification"
      }
    ]
  }
}
```

## Secure Function Patterns

### Authentication Check

```typescript
import { createClient } from '@supabase/supabase-js'

Deno.serve(async (req) => {
  // Get JWT from header
  const authHeader = req.headers.get('Authorization');
  if (!authHeader) {
    return new Response('Unauthorized', { status: 401 });
  }

  // Verify JWT with Supabase
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    { global: { headers: { Authorization: authHeader } } }
  );

  const { data: { user }, error } = await supabase.auth.getUser();
  if (error || !user) {
    return new Response('Unauthorized', { status: 401 });
  }

  // User is authenticated
  // ...
});
```

### Authorization Check (IDOR Prevention)

```typescript
// For user-specific resources
const requestedUserId = body.user_id;
const authenticatedUserId = user.id;

if (requestedUserId !== authenticatedUserId) {
  return new Response('Forbidden', { status: 403 });
}
```

### Role Check (Admin)

```typescript
// Check admin role
const { data: profile } = await supabase
  .from('profiles')
  .select('role')
  .eq('id', user.id)
  .single();

if (profile?.role !== 'admin') {
  return new Response('Forbidden', { status: 403 });
}
```

### Input Validation

```typescript
import { z } from 'zod';

const PaymentSchema = z.object({
  amount: z.number().positive().max(10000),
  currency: z.enum(['usd', 'eur', 'gbp']),
  description: z.string().max(200).optional()
});

// Validate input
const result = PaymentSchema.safeParse(body);
if (!result.success) {
  return new Response(
    JSON.stringify({ error: 'Invalid input' }),
    { status: 400 }
  );
}
```

## 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 testing each function** → Log the action to `.sb-pentest-audit.log`
2. **After each vulnerability found** → Immediately update `.sb-pentest-context.json`
3. **After each function test completes** → Log the result immediately

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 results:
   ```json
   {
     "functions_audit": {
       "timestamp": "...",
       "functions_found": 5,
       "findings": [ ... ]
     }
   }
   ```

2. **Log to `.sb-pentest-audit.log`**:
   ```
   [TIMESTAMP] [supabase-audit-functions] [START] Auditing Edge Functions
   [TIMESTAMP] [supabase-audit-functions] [FINDING] P0: IDOR in get-user-data
   [TIMESTAMP] [supabase-audit-functions] [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/07-functions-audit/`

### Evidence Files to Create

| File | Content |
|------|---------|
| `discovered-functions.json` | List of discovered Edge Functions |
| `function-tests/[name].json` | Test results per function |

### Evidence Format (IDOR Vulnerability)

```json
{
  "evidence_id": "FN-001",
  "timestamp": "2025-01-31T11:10:00Z",
  "category": "functions-audit",
  "type": "idor_vulnerability",
  "severity": "P0",

  "function": "get-user-data",
  "endpoint": "https://abc123def.supabase.co/functions/v1/get-user-data",

  "tests": [
    {
      "test_name": "auth_required",
      "request": {
        "method": "GET",
        "headers": {},
        "curl_command": "curl '$URL/functions/v1/get-user-data'"
      },
      "response": {"status": 401},
      "result": "PASS"
    },
    {
      "test_name": "idor_test",
      "description": "As user A, request user B's data",
      "request": {
        "method": "GET",
        "url": "$URL/functions/v1/get-user-data?user_id=user-b-id",
        "headers": {"Authorization": "Bearer [USER_A_TOKEN]"},
        "curl_command": "curl '$URL/functions/v1/get-user-data?user_id=user-b-id' -H 'Authorization: Bearer [USER_A_TOKEN]'"
      },
      "response": {
        "status": 200,
        "body": {"id": "user-b-id", "email": "[REDACTED]", "data": "[REDACTED]"}
      },
      "result": "VULNERABLE",
      "impact": "Any authenticated user can access any other user's data"
    }
  ],

  "remediation": "Add ownership check: if (user_id !== jwt_user.id) return 403"
}
```

### Evidence Format (Privilege Escalation)

```json
{
  "evidence_id": "FN-002",
  "timestamp": "2025-01-31T11:15:00Z",
  "category": "functions-audit",
  "type": "privilege_escalation",
  "severity": "P0",

  "function": "admin-panel",

  "test": {
    "description": "Regular user accessing admin function",
    "request": {
      "method": "GET",
      "headers": {"Authorization": "Bearer [REGULAR_USER_TOKEN]"},
      "curl_command": "curl '$URL/functions/v1/admin-panel' -H 'Authorization: Bearer [REGULAR_USER_TOKEN]'"
    },
    "response": {
      "status": 200,
      "body": {"admin_data": "[REDACTED]"}
    },
    "result": "VULNERABLE",
    "impact": "Any authenticated user has admin access"
  }
}
```

## Related Skills

- `supabase-audit-rpc` — Database functions (different from Edge Functions)
- `supabase-audit-auth-config` — Auth configuration
- `supabase-report` — Include in final report

© yoanbernabeu · MIT · yoanbernabeu/supabase-pentest-skills

Similar skills

All skills for Security →

From the same set19

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor