Supabase服务密钥泄露检测技能Skill supabase-extract-service-key

这个技能用于检测 Supabase 服务角色密钥是否在客户端代码中意外泄露,防止关键安全漏洞(P0级别)。适用于安全审计、代码审查和生产部署检查。关键词包括:Supabase、服务密钥、安全检测、客户端泄露、P0漏洞、安全审计、渗透测试。

安全审计 0 次安装 0 次浏览 更新于 3/18/2026

名称: supabase-extract-service-key 描述: 关键 - 检测 Supabase 服务角色密钥是否在客户端代码中泄露。这是一个 P0 严重性问题。

Supabase 服务密钥检测

🔴 关键: 渐进式文件更新要求

您必须在进行过程中写入上下文文件,而不是只在结束时。

  • 每次发现后立即写入 .sb-pentest-context.json
  • 每次操作前后记录到 .sb-pentest-audit.log
  • 不要等到技能完成才更新文件
  • 如果技能崩溃或中断,所有先前发现必须已经保存

这不是可选的。不渐进式写入是严重错误。

这个技能检测 服务角色密钥(管理员密钥)是否意外暴露在客户端代码中。

何时使用此技能

  • 作为每次安全审计的一部分(这是关键的)
  • 在部署到生产环境前审查代码时
  • 检测到 Supabase 使用后检查此常见错误

先决条件

  • 目标应用程序可访问
  • Supabase 检测已完成(如果需要则自动调用)

为什么这是关键的

服务角色密钥 绕过所有行级安全(RLS)策略。如果暴露:

影响 描述
🔴 完全数据库访问 读取/写入/删除所有表中的所有数据
🔴 身份验证绕过 无需身份验证访问所有用户数据
🔴 存储访问 读取/写入所有存储桶中的所有文件
🔴 用户冒充 为任何用户生成令牌

这是一个 P0(关键)发现,需要立即行动。

服务密钥与匿名密钥

方面 匿名密钥 服务密钥
角色声明 "role": "anon" "role": "service_role"
RLS ✅ 尊重 RLS ❌ 绕过 RLS
客户端 ✅ 预期 ❌ 从不
服务器端 ✅ 可以使用 ✅ 应该使用

检测模式

技能搜索:

1. 具有 service_role 声明的密钥

// 解码的 JWT 负载包含:
{
  "role": "service_role",  // ❌ 如果在客户端代码中则关键
  "iss": "supabase",
  "ref": "abc123def"
}

2. 变量名称

// 常见命名模式
SUPABASE_SERVICE_KEY
SUPABASE_SERVICE_ROLE_KEY
SUPABASE_ADMIN_KEY
SUPABASE_SECRET_KEY
SERVICE_ROLE_KEY

3. 意外暴露

// 有时与匿名密钥一起暴露
const keys = {
  anon: 'eyJ...',
  service: 'eyJ...'  // ❌ 不应该在这里
}

使用方式

基本检查

在 https://myapp.example.com 上检查服务密钥泄露

深度扫描

在 https://myapp.example.com 上深度扫描服务密钥暴露

输出格式

未找到服务密钥(好)

═══════════════════════════════════════════════════════════
 服务密钥检查
═══════════════════════════════════════════════════════════

 状态: ✅ 在客户端代码中未检测到服务角色密钥

 扫描内容:
 ├── HTML 源: 干净
 ├── JavaScript 包: 5 个文件,2.3MB 分析
 ├── 内联脚本: 12 个块检查
 └── 源映射: 未暴露(好)

 JWT 分析:
 └── 找到 1 个密钥,确认角色=匿名(安全)

 结果: 通过 - 无关键密钥暴露
═══════════════════════════════════════════════════════════

找到服务密钥(关键)

═══════════════════════════════════════════════════════════
 🔴 关键: 服务密钥暴露
═══════════════════════════════════════════════════════════

 严重性: P0 - 关键
 状态: ❌ 在客户端代码中找到服务角色密钥!

 ⚠️  需要立即行动 ⚠️

 暴露的密钥:
 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBh
 YmFzZSIsInJlZiI6ImFiYzEyM2RlZiIsInJvbGUiOiJzZXJ2aWNlX3
 JvbGUiLCJpYXQiOjE2NDAwMDAwMDAsImV4cCI6MTk1NTM2MDAwMH0
 .xxxxxxxxxxxxx

 位置:
 └── /static/js/admin.chunk.js (第 89 行)
     const SUPABASE_KEY = 'eyJhbG...'  // 在 createClient() 中使用

 解码的负载:
 ├── 角色: service_role ← 关键
 ├── 引用: abc123def
 └── 过期时间: 2031-12-20

 影响评估:
 ├── 🔴 可能完全数据库访问
 ├── 🔴 所有 RLS 策略被绕过
 ├── 🔴 所有用户数据暴露
 └── 🔴 所有存储桶可访问

 ═══════════════════════════════════════════════════════════
 立即修复步骤
 ═══════════════════════════════════════════════════════════

 1. 立即旋转密钥
    → Supabase 仪表板 > 设置 > API > 重新生成服务角色密钥

 2. 从客户端代码中删除
    → 从源代码中删除密钥
    → 重新部署应用程序

 3. 审计滥用
    → 检查 Supabase 日志以查找未经授权的访问
    → 审查数据库以查找意外更改

 4. 使用边缘函数
    → 将特权操作移到边缘函数
    → 客户端调用边缘函数,边缘函数在服务器端使用服务密钥

 文档:
 → 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": "服务角色密钥在客户端代码中暴露",
      "description": "服务角色密钥在客户端 JavaScript 中找到",
      "location": {
        "file": "/static/js/admin.chunk.js",
        "line": 89
      },
      "evidence": {
        "key_prefix": "eyJhbGciOiJIUzI1NiI...",
        "role": "service_role",
        "project_ref": "abc123def"
      },
      "remediation": {
        "immediate": "在 Supabase 仪表板中旋转密钥",
        "long_term": "移到边缘函数",
        "docs": "https://supabase.com/docs/guides/api/api-keys"
      }
    }
  ],
  "supabase": {
    "service_key_exposed": true,
    "service_key_location": "/static/js/admin.chunk.js:89"
  }
}

源映射检查

技能还检查可能暴露密钥的源映射:

源映射分析:
├── main.js.map: ❌ 暴露(可能包含秘密)
├── vendor.js.map: ❌ 暴露
└── 推荐: 在生产环境中禁用源映射

检查源映射内容:
→ 添加 .map 到 JS URL:/static/js/main.js.map

常见原因

原因 解决方案
错误的环境变量 仅对匿名密钥使用 NEXT_PUBLIC_
复制粘贴错误 仔细检查使用的密钥
调试代码遗留 生产构建前移除
错误配置的打包器 确保服务密钥环境变量未包含

修复代码示例

之前(错误)

// ❌ 错误 - 服务密钥在客户端
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_SERVICE_KEY  // ❌ 永远不要这样做
)

之后(正确)

// ✅ 正确 - 仅匿名密钥在客户端
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY  // ✅ 对客户端安全
)

// 对于特权操作,调用边缘函数:
const { data } = await supabase.functions.invoke('admin-action', {
  body: { action: 'delete-user', userId: '123' }
})

边缘函数(服务器端)

// supabase/functions/admin-action/index.ts
import { createClient } from '@supabase/supabase-js'

Deno.serve(async (req) => {
  // ✅ 服务密钥仅在服务器
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL'),
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')  // ✅ 在服务器安全
  )

  // 执行特权操作
  // ...
})

强制性: 渐进式上下文文件更新

⚠️ 此技能必须在执行过程中渐进式更新跟踪文件,而不是只在结束时。

关键规则: 渐进写入

不要在结束时批量所有写入。而是:

  1. 在开始任何操作之前 → 将操作记录到 .sb-pentest-audit.log
  2. 每次发现后 → 立即更新 .sb-pentest-context.json
  3. 每个重要步骤后 → 将完成记录到 .sb-pentest-audit.log

这确保如果技能被中断、崩溃或超时,所有到该点的发现都被保留。

必需操作(渐进式)

  1. 用发现更新 .sb-pentest-context.json

    {
      "supabase": {
        "service_key_exposed": true/false,
        "service_key_location": "路径:行"
      },
      "findings": [
        {
          "id": "SERVICE_KEY_EXPOSED",
          "severity": "P0",
          "title": "服务角色密钥暴露",
          ...
        }
      ]
    }
    
  2. 记录到 .sb-pentest-audit.log

    [时间戳] [supabase-extract-service-key] [开始] 检查服务密钥暴露
    [时间戳] [supabase-extract-service-key] [关键] 服务密钥暴露在 路径:行
    [时间戳] [supabase-extract-service-key] [上下文已更新] .sb-pentest-context.json 更新
    
  3. 如果文件不存在,在写入前创建。

不更新上下文文件是不可接受的。

强制性: 证据收集

📁 证据目录: .sb-pentest-evidence/02-extraction/service-key-exposure/

要创建的证据文件(如果找到服务密钥)

文件 内容
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...' // [已编辑]"
  },

  "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)

## [时间戳] - 🔴 P0 关键: 服务角色密钥暴露
- 服务角色密钥在客户端代码中找到
- 位置: [文件]:[行]
- 影响: 完全数据库访问,RLS 绕过
- 证据: `02-extraction/service-key-exposure/`
- **需要立即行动**

相关技能

  • supabase-extract-anon-key — 提取(预期的)匿名密钥
  • supabase-audit-tables-read — 测试可访问的数据
  • supabase-report — 生成包括此发现的完整报告