supabase-audit-tables-read について
Supabase のテーブルデータへの読み取り権限を検査するセキュリティ監査スキルです。RLS の有効性やデータ露出を評価する監査担当者向けで、テーブルに読み取り専用クエリを実行し、データの機微性に応じて結果を評価します。
主な機能
- 01読み取り専用のテーブルアクセス検査
- 02クイック、サンプル、件数モード
- 03機微データの種類に応じた重大度評価
- 04テーブルごとの結果と監査ログの逐次保存
- 05アクセス証拠と curl の例の作成
活用シーン
- 01RLS が未認証の読み取りを防ぐか検証
- 02公開テーブルから実際に取得できるデータの確認
- 03行データを取得せずにアクセス可能な件数を把握
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yoanbernabeu/ supabase-pentest-skills --skill supabase-audit-tables-readインストール後、Claude Code で/supabase-audit-tables-readを入力して読み込まれたか確認します。
詳細
テーブルデータアクセスのテスト
🔴 重大:ファイルを段階的に更新する必要があります
最後にまとめて更新するのではなく、作業を進めながらコンテキストファイルに書き込む必要があります。
- 各テーブルのテスト後、直ちに
.sb-pentest-context.jsonに書き込みます- 各テストの前と後に
.sb-pentest-audit.logに記録します- スキルの完了までファイルの更新を待たないでください
- スキルがクラッシュまたは中断された場合でも、それまでの調査結果はすべて保存されている必要があります
これは任意ではありません。段階的な書き込みを怠ると、重大なエラーになります。
このスキルは、公開されているテーブルからデータを読み取り、実際にどの情報へアクセスできるかを判定します。
このスキルを使用するタイミング
- テーブルの一覧表示後、実際にアクセスできるかを確認するとき
- RLS ポリシーの有効性をテストするとき
- データ漏えいの深刻度を評価するとき
- 取得可能なデータを正確に記録するとき
前提条件
- テーブルが一覧表示されていること(必要に応じて
supabase-audit-tables-listが自動的に呼び出されます) - Anon キーが利用可能であること
仕組み
このスキルは、公開されている各テーブルに対して SELECT クエリを実行します。
GET https://[project].supabase.co/rest/v1/[table]?select=*&limit=5
Authorization: Bearer [anon-key]
重要: これは読み取り専用です。データの変更や削除は行いません。
テストモード
| モード | 説明 | クエリ |
|---|---|---|
| Quick | 各テーブルの先頭 5 行 | ?limit=5 |
| Sample | テーブル全体からランダムにサンプルを取得 | ?limit=10&order=random |
| Count | データを取得せず、行数のみを取得 | HEAD リクエスト |
使用方法
基本的な読み取りテスト
Test read access on exposed tables
件数のみをすばやく確認
Count accessible rows in all tables (no data retrieval)
特定のテーブル
Test read access on the users table
出力形式
═══════════════════════════════════════════════════════════
DATA ACCESS TEST RESULTS
═══════════════════════════════════════════════════════════
Test Mode: Quick (5 rows per table)
Tables Tested: 8
─────────────────────────────────────────────────────────
Results by Table
─────────────────────────────────────────────────────────
1. users
Status: 🔴 P0 - DATA EXPOSED
Rows Retrieved: 5 (of 1,247 total)
Sample Data:
┌─────────────────────────────────────────────────────┐
│ id: 550e8400-e29b-41d4-a716-446655440001 │
│ email: [email protected] ← PII EXPOSED │
│ name: John Doe ← PII EXPOSED │
│ avatar_url: https://... │
│ created_at: 2025-01-15T10:30:00Z │
└─────────────────────────────────────────────────────┘
Finding: User emails and names accessible without auth
2. profiles
Status: 🟠 P1 - PARTIAL ACCESS
Rows Retrieved: 5
Note: Only public fields returned (RLS working partially)
Columns Visible: id, bio, website
Columns Blocked: user_id, social_links, private_notes
3. posts
Status: ✅ EXPECTED ACCESS
Rows Retrieved: 5
Note: Only published=true posts returned (RLS working)
Data: Public content, appropriate access level
4. orders
Status: ✅ BLOCKED
Response: 403 Forbidden
Message: "new row violates row-level security policy"
Note: RLS properly blocking access
5. api_keys
Status: ✅ BLOCKED
Response: 403 Forbidden
Note: RLS properly protecting secrets
6. products
Status: ✅ EXPECTED ACCESS
Rows Retrieved: 5
Note: Public catalog data, appropriate access
7. comments
Status: 🟠 P1 - MORE DATA THAN EXPECTED
Rows Retrieved: 5
Issue: user_id column exposed (can correlate to users)
Recommendation: Use a view to hide user_id
8. settings
Status: 🔴 P0 - SENSITIVE DATA EXPOSED
Rows Retrieved: 3
Sample Data:
┌─────────────────────────────────────────────────────┐
│ key: stripe_webhook_secret │
│ value: whsec_xxxxxxxxxxxx ← SECRET EXPOSED │
└─────────────────────────────────────────────────────┘
Finding: Application secrets in accessible table!
─────────────────────────────────────────────────────────
Summary
─────────────────────────────────────────────────────────
P0 (Critical): 2 tables with sensitive data exposed
P1 (High): 2 tables with partial/unexpected exposure
Blocked: 2 tables properly protected
Expected: 2 tables with appropriate public access
Total Rows Accessible: 1,892 across exposed tables
Immediate Actions:
1. Fix 'settings' table - remove from public or add RLS
2. Fix 'users' table - add RLS to protect email/name
3. Review 'comments' to hide user correlation
═══════════════════════════════════════════════════════════
重大度の評価
| ステータス | 重大度 | 基準 |
|---|---|---|
| 🔴 DATA EXPOSED | P0 | 機密データ(PII、シークレット、財務情報)にアクセス可能 |
| 🟠 PARTIAL ACCESS | P1 | 想定より多くのデータにアクセス可能だが、重大ではない |
| 🟡 UNEXPECTED | P2 | アクセス可能だが、リスクの低いデータ |
| ✅ BLOCKED | - | RLS によってアクセスが適切に防止されている |
| ✅ EXPECTED | - | 公開データであり、アクセスは適切 |
データ分類
このスキルは、次の種類の機密データを識別します。
| 種類 | パターン | 公開された場合の重大度 |
|---|---|---|
| PII | email、phone、name、address | P0 |
| 財務情報 | amount、total、card、payment | P0 |
| シークレット | key、secret、token、password | P0 |
| 認証 | user_id、session、jwt | P1 |
| メタデータ | created_at、updated_at | P2 |
コンテキスト出力
{
"data_access": {
"timestamp": "2025-01-31T10:30:00Z",
"tables_tested": 8,
"summary": {
"p0_exposed": 2,
"p1_partial": 2,
"blocked": 2,
"expected": 2
},
"results": [
{
"table": "users",
"status": "exposed",
"severity": "P0",
"rows_accessible": 1247,
"sensitive_columns": ["email", "name"],
"sample_redacted": true
},
{
"table": "settings",
"status": "exposed",
"severity": "P0",
"rows_accessible": 3,
"sensitive_data_types": ["secrets"],
"finding": "Application secrets exposed"
}
],
"total_rows_accessible": 1892
}
}
監査ログのエントリ
[2025-01-31T10:30:00Z] READ_TEST_START tables=8
[2025-01-31T10:30:01Z] READ_TEST table=users status=200 rows=5 severity=P0
[2025-01-31T10:30:01Z] READ_TEST table=orders status=403 severity=none
[2025-01-31T10:30:02Z] READ_TEST_COMPLETE exposed=4 blocked=2
修正例
ユーザーテーブルの場合
-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Only authenticated users see their own data
CREATE POLICY "Users see own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Or create a public view with limited columns
CREATE VIEW public.users_public AS
SELECT id, avatar_url, created_at FROM users;
設定テーブルの場合
-- Remove from public access entirely
REVOKE ALL ON TABLE settings FROM anon, authenticated;
-- Access only via Edge Functions
-- In your Edge Function:
const { data } = await supabaseAdmin
.from('settings')
.select('*')
.eq('key', 'stripe_webhook_secret')
.single()
コンテンツテーブルの場合
-- RLS for published content only
CREATE POLICY "Public sees published posts"
ON posts FOR SELECT
USING (published = true);
-- Authors see their own drafts
CREATE POLICY "Authors see own posts"
ON posts FOR SELECT
USING (auth.uid() = author_id);
よくある問題
❌ 問題: すべてのテーブルが 403 を返す ✅ 解決策: RLS の制限が厳しすぎるか、Anon キーが無効である可能性があります。セキュリティの観点からは、実際には望ましい状態です。
❌ 問題: エラーはないのに結果が空 ✅ 解決策: RLS がすべての行をフィルタリングしています。テーブル構造にはアクセスできますが、データにはアクセスできません。
❌ 問題: 大きなテーブルでタイムアウトする ✅ 解決策: Count モードを使用するか、上限を減らしてください。
必須:コンテキストファイルの段階的な更新
⚠️ このスキルは、実行中に追跡ファイルを段階的に更新する必要があります。最後にまとめて更新してはいけません。
重要なルール:作業を進めながら書き込む
最後にすべてをまとめて書き込まないでください。 代わりに、次の手順に従ってください。
- 各テーブルのテスト前 →
.sb-pentest-audit.logに操作を記録します - 各テーブルのテスト後 → 結果を
.sb-pentest-context.jsonに直ちに更新します - 各調査結果の確認後 →
.sb-pentest-audit.logに重大度を記録します
これにより、スキルが中断、クラッシュ、またはタイムアウトした場合でも、その時点までの調査結果がすべて保持されます。
必須の操作(段階的に実施)
-
結果を含めて
.sb-pentest-context.jsonを更新します。{ "data_access": { "timestamp": "...", "tables_tested": 8, "summary": { "p0_exposed": 2, ... }, "results": [ ... ], "total_rows_accessible": 1892 } } -
.sb-pentest-audit.logに記録します。[TIMESTAMP] [supabase-audit-tables-read] [START] Testing data access [TIMESTAMP] [supabase-audit-tables-read] [FINDING] P0: users table exposed [TIMESTAMP] [supabase-audit-tables-read] [CONTEXT_UPDATED] .sb-pentest-context.json updated -
ファイルが存在しない場合は、書き込む前に作成します。
コンテキストファイルを更新しないことは許容されません。
必須:証拠の収集
📁 証拠ディレクトリ: .sb-pentest-evidence/03-api-audit/data-samples/
作成する証拠ファイル
| ファイル | 内容 |
|---|---|
data-samples/[table]-sample.json | アクセス可能な各テーブルのサンプルデータ |
data-samples/[table]-blocked.json | アクセスがブロックされた証拠(403 レスポンス) |
証拠の形式(データが公開されている場合)
{
"evidence_id": "API-READ-001",
"timestamp": "2025-01-31T10:20:00Z",
"category": "api-audit",
"type": "data_access",
"severity": "P0",
"finding_id": "P0-002",
"table": "users",
"request": {
"method": "GET",
"url": "https://abc123def.supabase.co/rest/v1/users?select=*&limit=5",
"headers": {
"apikey": "[REDACTED]",
"Authorization": "Bearer [REDACTED]"
},
"curl_command": "curl -s 'https://abc123def.supabase.co/rest/v1/users?select=*&limit=5' -H 'apikey: $ANON_KEY' -H 'Authorization: Bearer $ANON_KEY'"
},
"response": {
"status": 200,
"headers": {
"content-range": "0-4/1247"
},
"total_rows": 1247,
"sample_data": [
{
"id": "550e8400-e29b-41d4-...",
"email": "[REDACTED]@example.com",
"name": "[REDACTED]",
"created_at": "2025-01-15T10:30:00Z"
}
],
"data_redacted": true
},
"analysis": {
"severity": "P0",
"pii_exposed": ["email", "name"],
"total_records_accessible": 1247,
"authentication_required": false
}
}
証拠の形式(適切にブロックされている場合)
{
"evidence_id": "API-READ-002",
"timestamp": "2025-01-31T10:21:00Z",
"table": "orders",
"severity": null,
"response": {
"status": 403,
"body": {"message": "new row violates row-level security policy"}
},
"analysis": {
"rls_working": true,
"access_blocked": true
}
}
curl-commands.sh に追加
# === DATA ACCESS TESTS ===
# Test: Users table access
curl -s "$SUPABASE_URL/rest/v1/users?select=*&limit=5" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
# Test: Orders table access (should be blocked)
curl -s "$SUPABASE_URL/rest/v1/orders?select=*&limit=5" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
関連スキル
supabase-audit-tables-list— まずテーブルを一覧表示supabase-audit-rls— RLS ポリシーを詳しく確認supabase-report— 完全なレポートを生成