supabase-audit-functions について
Supabase Edge Functionsを発見し、セキュリティ上の問題を検査するスキルです。Supabaseのセキュリティ監査向けに、関数の発見、認証、入力検証、エラー情報の漏えいを調べ、監査記録と証拠を逐次保存します。
主な機能
- 01公開されたEdge Functionsの発見
- 02JWT認証要件の確認
- 03入力検証とインジェクションリスクのテスト
- 04エラー情報漏えいの確認
- 05監査コンテキストと証拠の逐次保存
活用シーン
- 01アクセス可能なEdge Functionsの一覧化
- 02関数のJWT必須設定の確認
- 03入力処理とアクセス制御のテスト
- 04関数エラーによる情報漏えいの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yoanbernabeu/ supabase-pentest-skills --skill supabase-audit-functionsインストール後、Claude Code で/supabase-audit-functionsを入力して読み込まれたか確認します。
詳細
Edge Functions の監査
🔴 重要:段階的なファイル更新が必須です
コンテキストファイルには、最後だけでなく、作業を進めながら書き込む必要があります。
- 各関数のテスト直後に、
.sb-pentest-context.jsonに直ちに書き込んでください- 各関数のテストの前後に、
.sb-pentest-audit.logに記録してください- このスキルが完了するまでファイルの更新を待たないでください
- スキルがクラッシュまたは中断された場合でも、それまでの調査結果がすべて保存されている必要があります
これは任意ではありません。段階的に書き込まないことは重大なエラーです。
このスキルでは、Supabase Edge Functions を検出し、セキュリティ上の問題をテストします。
このスキルを使用するタイミング
- 公開されている Edge Functions を検出するとき
- 関数に認証要件があるかテストするとき
- 入力検証の問題を確認するとき
- 包括的なセキュリティ監査の一環として
前提条件
- Supabase の URL を利用できること
- 検出が完了していること
Edge Functions について
Supabase Edge Functions は Deno ベースのサーバーレス関数です。
https://[project].supabase.co/functions/v1/[function-name]
| セキュリティの観点 | 考慮事項 |
|---|---|
| 認証 | 関数では JWT を必須にすることも、公開することもできます |
| CORS | クロスオリジンアクセス制御 |
| 入力検証 | ユーザー入力の処理 |
| シークレット | 環境変数の露出 |
実施するテスト
| テスト | 目的 |
|---|---|
| 関数の検出 | 公開されている関数を見つける |
| 認証要件 | JWT が必須か確認する |
| 入力検証 | インジェクションをテストする |
| エラー処理 | 情報漏えいを確認する |
使用方法
基本的な関数監査
Audit Edge Functions on my Supabase project
特定の関数をテストする
Test the process-payment Edge Function for security issues
出力形式
═══════════════════════════════════════════════════════════
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!"}
評価:✅ 適切 シンプルな公開エンドポイントで、機密性の高い操作はありません。
───────────────────────────────────────────────────────── 2. process-payment ─────────────────────────────────────────────────────────
エンドポイント:/functions/v1/process-payment メソッド:POST
認証テスト: ├── JWT なし:❌ 401 Unauthorized ├── 有効な JWT あり:✅ 200 OK └── ステータス:✅ 認証が必要
入力検証テスト: ├── amount がない:❌ 400 Bad Request(良好) ├── amount が負の値:❌ 400 Bad Request(良好) ├── amount が文字列:❌ 400 Bad Request(良好) └── 有効な入力:✅ 200 OK
エラーレスポンステスト: ├── エラー形式:汎用メッセージ(良好) └── スタックトレース:❌ 公開されていない(良好)
評価:✅ 適切に保護されています 認証を要求し、入力を検証し、安全にエラーを処理します。
───────────────────────────────────────────────────────── 3. get-user-data ─────────────────────────────────────────────────────────
エンドポイント:/functions/v1/get-user-data メソッド:GET
認証テスト: ├── JWT なし:❌ 401 Unauthorized └── ステータス:✅ 認証が必要
認可テスト: ├── 自分のデータをリクエスト:✅ 200 OK ├── 他のユーザーのデータをリクエスト:✅ 200 OK ← 🔴 P0! └── ステータス:🔴 アクセス制御の不備
テスト:
# 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!
検出事項:🔴 P0 - IDOR 脆弱性 この関数は user_id パラメーターを受け付けますが、 認証済みユーザーが自分自身のデータをリクエストしているかどうかを検証していません。
修正:
// 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 ─────────────────────────────────────────────────────────
エンドポイント:/functions/v1/admin-panel メソッド:GET, POST
認証テスト: ├── JWT なし:❌ 401 Unauthorized ├── 一般ユーザーの JWT あり:✅ 200 OK ← 🔴 P0! └── ステータス:🔴 ロールチェックの欠如
検出事項:🔴 P0 - 権限昇格 管理者向け関数に、認証済みであれば誰でもアクセスできます。 関数コードでロールが検証されていません。
修正:
// 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 ─────────────────────────────────────────────────────────
エンドポイント:/functions/v1/webhook-handler メソッド:POST
認証テスト: ├── JWT なし:✅ 200 OK(Webhook では想定どおり) └── ステータス:ℹ️ 公開(Webhook エンドポイントは通常公開されています)
Webhook セキュリティテスト: ├── 署名の検証:⚠️ テスト不可(有効な署名が必要) └── レート制限:不明
エラーレスポンステスト:
{
"error": "Invalid signature",
"expected": "sha256=abc123...",
"received": "sha256=xyz789..."
}
検出事項:🟠 P1 - 情報漏えい エラーレスポンスから、想定される署名形式が明らかになります。 これにより、攻撃者が検証メカニズムを把握しやすくなる可能性があります。
修正:
// Generic error, log details server-side
if (!validSignature) {
console.error(`Invalid signature: expected ${expected}, got ${received}`);
return new Response('Unauthorized', { status: 401 });
}
───────────────────────────────────────────────────────── 概要 ─────────────────────────────────────────────────────────
検出された関数:5
セキュリティ評価: ├── ✅ 安全:2(hello-world、process-payment) ├── 🔴 P0:2(get-user-data の IDOR、admin-panel の権限昇格) └── 🟠 P1:1(webhook-handler の情報漏えい)
重大な検出事項:
- get-user-data の IDOR - すべてのユーザーが、任意のユーザーのデータにアクセスできます
- admin-panel にロールチェックがない - すべてのユーザーが管理者として扱われます
優先対応事項:
- get-user-data を修正し、リクエストされたデータの所有者がユーザー本人であることを検証する
- admin-panel に管理者ロールの検証を追加する
- webhook-handler のエラーメッセージを修正する
═══════════════════════════════════════════════════════════
## 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. 一般的な名前の列挙
テストした関数名:
- 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. エラーレスポンスの分析
404 Not Found → Function doesn't exist
401 Unauthorized → Function exists, needs auth
200 OK → Function exists, accessible
コンテキスト出力
{
"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"
}
]
}
}
安全な関数のパターン
認証チェック
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
// ...
});
認可チェック(IDOR の防止)
// For user-specific resources
const requestedUserId = body.user_id;
const authenticatedUserId = user.id;
if (requestedUserId !== authenticatedUserId) {
return new Response('Forbidden', { status: 403 });
}
ロールチェック(管理者)
// 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 });
}
入力検証
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 }
);
}
必須:コンテキストファイルの段階的な更新
⚠️ このスキルでは、実行中に追跡ファイルを段階的に更新する必要があります。最後にまとめて更新するだけではいけません。
重要なルール:作業を進めながら記録する
すべての書き込みを最後にまとめて行わないでください。 代わりに、次の手順を実行してください。
- 各関数をテストする前に → 操作を
.sb-pentest-audit.logに記録します - 脆弱性が見つかるたびに →
.sb-pentest-context.jsonを直ちに更新します - 各関数のテスト完了後に → 結果を直ちに記録します
これにより、スキルが中断されたり、クラッシュしたり、タイムアウトしたりしても、その時点までのすべての検出結果が保持されます。
必須アクション(段階的に実施)
-
結果を
.sb-pentest-context.jsonに更新します:{ "functions_audit": { "timestamp": "...", "functions_found": 5, "findings": [ ... ] } } -
.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 -
ファイルが存在しない場合は、書き込む前に作成します。
コンテキストファイルを更新しないことは許容されません。
必須:証拠の収集
📁 証拠ディレクトリ: .sb-pentest-evidence/07-functions-audit/
作成する証拠ファイル
| ファイル | 内容 |
|---|---|
discovered-functions.json | 検出された Edge Functions の一覧 |
function-tests/[name].json | 関数ごとのテスト結果 |
証拠の形式(IDOR の脆弱性)
{
"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_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"
}
}
関連スキル
supabase-audit-rpc— データベース関数(Edge Functions とは異なります)supabase-audit-auth-config— 認証設定supabase-report— 最終レポートに含めます