supabase-extract-service-key について
supabase-extract-service-keyは、クライアント側コードにSupabaseのservice_roleキーが露出していないか調べるセキュリティ監査スキルです。キーのパターンとソースマップを確認し、監査結果と証拠を記録します。
主な機能
- 01クライアントコード内のservice_roleキー検出
- 02一般的なサービスキー変数名の確認
- 03公開されたソースマップの検査
- 04発見事項と証拠の記録
活用シーン
- 01Webアプリのサービスキー漏えいを監査
- 02デプロイ前にクライアントバンドルを確認
- 03ソースマップからのキー漏えいを調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yoanbernabeu/ supabase-pentest-skills --skill supabase-extract-service-keyインストール後、Claude Code で/supabase-extract-service-keyを入力して読み込まれたか確認します。
詳細
Supabase サービスキーの検出
🔴 重大:コンテキストファイルを段階的に更新する必要があります
コンテキストファイルには、最後にまとめてではなく、作業しながら必ず書き込んでください。
- 発見するたびに
.sb-pentest-context.jsonに直ちに書き込んでください- 各アクションの前後に
.sb-pentest-audit.logに記録してください- スキルの完了までファイルの更新を待たないでください
- スキルがクラッシュしたり中断されたりしても、それまでの発見事項がすべて保存されている必要があります
これは任意ではありません。段階的に書き込まないことは重大なエラーです。
このスキルは、service_role キー(管理者キー)がクライアント側のコードで誤って公開されていないかを検出します。
このスキルを使用するタイミング
- すべてのセキュリティ監査の一環として(これは重要です)
- 本番環境にデプロイする前のコードレビュー時
- よくあるこの誤りを確認するため、Supabase の使用を検出した後
前提条件
- 対象アプリケーションにアクセスできること
- Supabase の検出が完了していること(必要に応じて自動的に実行されます)
これが重大である理由
service_role キーは、すべての Row Level Security(RLS)ポリシーをバイパスします。公開された場合:
| 影響 | 説明 |
|---|---|
| 🔴 DB への完全なアクセス | すべてのテーブルの全データを読み取り/書き込み/削除できます |
| 🔴 認証のバイパス | 認証なしで全ユーザーデータにアクセスできます |
| 🔴 ストレージへのアクセス | すべてのバケット内の全ファイルを読み取り/書き込みできます |
| 🔴 ユーザーのなりすまし | 任意のユーザーのトークンを生成できます |
これは P0(重大)の検出事項であり、直ちに対応する必要があります。
サービスキーと匿名キーの比較
| 項目 | 匿名キー | サービスキー |
|---|---|---|
| ロールクレーム | "role": "anon" | "role": "service_role" |
| RLS | ✅ RLS を遵守 | ❌ RLS をバイパス |
| クライアント側 | ✅ 想定どおり | ❌ 絶対に使用しない |
| サーバー側 | ✅ 使用可能 | ✅ 使用するべき |
検出パターン
このスキルは次のパターンを検索します。
1. service_role クレームを持つキー
// Decoded JWT payload contains:
{
"role": "service_role", // ❌ CRITICAL if in client code
"iss": "supabase",
"ref": "abc123def"
}
2. 変数名
// Common naming patterns
SUPABASE_SERVICE_KEY
SUPABASE_SERVICE_ROLE_KEY
SUPABASE_ADMIN_KEY
SUPABASE_SECRET_KEY
SERVICE_ROLE_KEY
3. 誤った公開
// Sometimes exposed alongside anon key
const keys = {
anon: 'eyJ...',
service: 'eyJ...' // ❌ Should not be here
}
使用方法
基本チェック
Check for service key leak on https://myapp.example.com
詳細スキャン
Deep scan for service key exposure on https://myapp.example.com
出力形式
サービスキーが見つからない場合(良好)
═══════════════════════════════════════════════════════════
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
═══════════════════════════════════════════════════════════
サービスキーが見つかった場合(重大)
═══════════════════════════════════════════════════════════
🔴 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
═══════════════════════════════════════════════════════════
コンテキスト出力
.sb-pentest-context.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 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
よくある原因
| 原因 | 解決策 |
|---|---|
| 環境変数の誤り | 匿名キーにのみ NEXT_PUBLIC_ を使用してください |
| コピー&ペーストの誤り | 使用しているキーを再確認してください |
| デバッグコードの残存 | 本番ビルドの前に削除してください |
| バンドラーの設定ミス | サービスキーの環境変数が含まれていないことを確認してください |
修正コードの例
修正前(誤り)
// ❌ 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
)
修正後(正しい)
// ✅ 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(サーバー側)
// 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
// ...
})
必須:コンテキストファイルの段階的な更新
⚠️ このスキルは、最後にまとめてではなく、実行中に追跡ファイルを段階的に更新する必要があります。
重要なルール:作業しながら書き込む
最後にすべての書き込みをまとめて行わないでください。代わりに、次の手順に従ってください。
- アクションを開始する前に →
.sb-pentest-audit.logにアクションを記録します - 発見するたびに → 直ちに
.sb-pentest-context.jsonを更新します - 重要な手順のたびに →
.sb-pentest-audit.logに完了を記録します
これにより、スキルが中断されたり、クラッシュしたり、タイムアウトしたりしても、その時点までのすべての発見事項が保持されます。
必須アクション(段階的に実行)
-
発見事項を
.sb-pentest-context.jsonに更新します。{ "supabase": { "service_key_exposed": true/false, "service_key_location": "path:line" }, "findings": [ { "id": "SERVICE_KEY_EXPOSED", "severity": "P0", "title": "Service Role Key Exposed", ... } ] } -
.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 -
ファイルが存在しない場合は、書き込む前に作成します。
コンテキストファイルを更新しないことは許容されません。
必須:証拠の収集
📁 証拠ディレクトリ: .sb-pentest-evidence/02-extraction/service-key-exposure/
作成する証拠ファイル(サービスキーが見つかった場合)
| ファイル | 内容 |
|---|---|
service-key-exposure/location.txt | ファイルパスと行番号 |
service-key-exposure/decoded-payload.json | service_role であることを証明するデコード済み JWT |
service-key-exposure/code-snippet.txt | コードのコンテキスト(秘匿化済み) |
証拠の形式(P0 の検出事項)
{
"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]'"
}
timeline.md に追加(P0)
## [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**
関連スキル
supabase-extract-anon-key— 想定される anon key を抽出しますsupabase-audit-tables-read— アクセス可能なデータをテストしますsupabase-report— この検出結果を含む完全なレポートを生成します