关于 supabase-extract-service-key
supabase-extract-service-key 是安全审计技能,用于检查客户端代码中是否暴露 Supabase service_role 密钥。它检查密钥特征和源映射文件,并记录审计发现与证据。
主要功能
- 01检测客户端代码中的 service_role 密钥
- 02检查常见服务密钥变量名
- 03检查可访问的源映射文件
- 04记录发现与支持证据
适用场景
- 01审计 Web 应用中的服务密钥泄露
- 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 密钥会绕过所有行级安全性(RLS)策略。如果密钥泄露:
| 影响 | 说明 |
|---|---|
| 🔴 完全访问数据库 | 读取、写入、删除所有表中的所有数据 |
| 🔴 绕过身份验证 | 无需身份验证即可访问所有用户数据 |
| 🔴 访问存储 | 读取、写入所有存储桶中的所有文件 |
| 🔴 冒充用户 | 为任意用户生成令牌 |
这是一个需要立即处理的 P0(严重)发现。
Service 密钥与 Anon 密钥
| 方面 | Anon 密钥 | Service 密钥 |
|---|---|---|
| 角色声明 | "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 密钥(良好)
═══════════════════════════════════════════════════════════
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 密钥(严重)
═══════════════════════════════════════════════════════════
🔴 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 Map
此技能还会检查是否暴露了可能泄露密钥的 source map:
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
常见原因
| 原因 | 解决方案 |
|---|---|
| 环境变量错误 | 仅对 anon 密钥使用 NEXT_PUBLIC_ |
| 复制粘贴错误 | 仔细确认所用的密钥 |
| 遗留调试代码 | 在生产构建前移除 |
| 打包工具配置错误 | 确保未包含 service 密钥环境变量 |
修复代码示例
修复前(错误)
// ❌ 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 密钥)
| 文件 | 内容 |
|---|---|
service-key-exposure/location.txt | 文件路径和行号 |
service-key-exposure/decoded-payload.json | 解码后的 JWT,用以证明其为 service_role |
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 keysupabase-audit-tables-read— 测试哪些数据可访问supabase-report— 生成包含此发现的完整报告
评论
支持 Markdown