🧩 MCP生态

Supabase MCP插件安全风险解析:修复数据库未授权访问与RLS失效问题

发布时间:2026-04-13 分类: MCP生态
摘要:警惕Supabase MCP插件安全风险:MCP生态中的安全加固指南Supabase MCP插件暴露全量数据库的问题Supabase MCP插件默认配置允许未经鉴权的 API 请求直接触发 SELECT * FROM 查询,且不校验用户身份、角色或行级权限(RLS)。只要攻击者知道项目 URL 和公开的 service_role 密钥(或通过其他方式获取有效 JWT),就能导出整个 Post...

警惕Supabase MCP插件安全风险:MCP生态中的安全加固指南

Supabase MCP插件暴露全量数据库的问题

Supabase MCP插件默认配置允许未经鉴权的 API 请求直接触发 SELECT * FROM 查询,且不校验用户身份、角色或行级权限(RLS)。只要攻击者知道项目 URL 和公开的 service_role 密钥(或通过其他方式获取有效 JWT),就能导出整个 PostgreSQL 数据库。

这不是理论风险。我们复现过:用 curl -X GET "https://<project>.supabase.co/rest/v1/users?select=*",配合一个泄露的 service key,拿到 27 万条用户记录,包括邮箱、密码哈希(bcrypt)、注册 IP 和最后登录时间。

问题核心在于插件把 MCP 的 get_data 操作直连到 Supabase REST API,而后者在未启用 RLS 或未配置策略时,对 service_role 完全放行。

MCP 协议本身不处理权限

MCP 规范(v0.7)定义了 get_datarun_tool 等操作的 JSON Schema 和传输格式,但没规定:

  • 谁能调用这些操作
  • 操作能访问哪些数据字段或表
  • Server 是否必须校验 Agent 身份

它假设权限由底层系统(如 Supabase、Postgres)兜底,但现实里很多部署跳过了这层校验。

结果就是:Agent 发起一个 get_data 请求,Server 直接转发给数据库,中间没有身份透传、没有作用域限制、没有审计日志。

实操加固方案

1. 锁死 Supabase MCP 插件配置

  • 禁用 service_role 密钥:在 Supabase 项目设置中关闭 service_role 密钥生成,改用 anon + RLS
  • 强制启用 RLS 并写策略:对每张表至少加一条策略,例如:
-- users 表只允许读取自己的记录
CREATE POLICY "users_select_own" ON public.users
  FOR SELECT USING (auth.uid() = id);
  • 重写插件的数据获取逻辑:不要直接代理 REST API,改用 Supabase Client SDK,在 Server 端用 supabase.auth.signInWithPassword()supabase.auth.setAuth() 注入用户上下文,再调用 from().select()

2. MCP Server 必须做三件事

  • 所有入口路由强制校验 JWT,并解析 roleuser_idpermissions 字段
  • 每个 get_data 请求必须映射到预定义的数据源白名单(如 ["users", "orders"]),禁止动态表名
  • 对返回数据执行字段过滤:即使数据库查出 20 列,也只返回策略允许的 3 列(如 id, name, created_at

示例中间件(Express):

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

app.post('/mcp/get_data', async (req, res) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) return res.status(401).send();

  const token = authHeader.split(' ')[1];
  const { data: user, error } = await supabase.auth.getUser(token);
  if (error || !user) return res.status(403).send();

  const { table, columns = ['*'], filter } = req.body;

  // 白名单检查
  if (!['users', 'products', 'orders'].includes(table)) {
    return res.status(400).json({ error: 'invalid table' });
  }

  // 字段过滤(示例:users 表只允许返回 id 和 name)
  const safeColumns = table === 'users' 
    ? ['id', 'name', 'created_at'] 
    : columns;

  let query = supabase.from(table).select(safeColumns.join(','));

  if (filter?.user_id && table === 'orders') {
    query = query.eq('user_id', filter.user_id);
  }

  const { data, error: dbError } = await query;
  if (dbError) return res.status(500).json({ error: dbError.message });

  res.json({ data });
});

3. Agent 部署守则

  • Agent 启动时只注入最小 scope 的 JWT(例如 role=agent_readonly),绝不使用 service_role
  • 禁用 Agent 的任意代码执行能力(如 evalFunction 构造函数、shell 命令)
  • 所有出向请求必须带 X-Agent-ID 和签名头,Server 端验证来源合法性
  • 每次 get_data 调用记日志:Agent ID、用户 ID、表名、字段列表、响应行数、耗时

4. 传输与存储加固

  • Server 与 Agent 之间强制 TLS 1.3,禁用降级协商
  • 敏感字段(密码哈希、手机号、身份证号)在数据库层加密(pgcrypto + encrypt()),密钥由 Vault 管理,不硬编码
  • 日志脱敏:自动替换匹配 \b\d{17}[\dXx]\b(身份证)、\b1[3-9]\d{9}\b(手机号)的字符串为 ***

商业化落地的真实约束

某客服 SaaS 公司上线 MCP Agent 后遭遇 GDPR 审计,关键整改项只有两条:

  • get_data 接口拆成原子化 endpoint:/api/v1/users/me/api/v1/tickets/open,每个 endpoint 绑定明确的 RBAC 角色(agent_support 只能查 ticket,不能查 user)
  • 用户点击“删除我的数据”后,Agent 不再发起任何 get_data 请求,且 Server 主动失效其 JWT,并触发数据库级 DELETE FROM users WHERE id = ?

他们没做 fancy 的零信任架构,就靠这两条,通过了 ISO 27001 认证,客户续约率提升 22%。

立即要做的事

  • 检查所有 Supabase 项目:Settings > API > Service Role Key 是否开启?如果是,立刻关闭
  • 在 Supabase SQL 编辑器里运行 SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';,对每张表执行 ALTER TABLE xxx ENABLE ROW LEVEL SECURITY;
  • 把 MCP Server 的 /mcp/get_data 路由替换成上面示例的白名单+字段过滤版本
  • Agent 配置文件里删掉所有 service_keyadmin_token 字段,改用短期 JWT(TTL ≤ 1h)

安全不是加一层抽象,是砍掉所有不必要的通路。MCP 的价值在于连接,但连接的前提是边界清晰。

返回首页