mcpField
S
supabase-security-basics

supabase-security-basics

github.com/jeremylongshore安全2.9k69 次安装2 天前更新
代码仓库

关于 supabase-security-basics

Supabase Security Basics 是帮助开发者保护 Supabase 项目的技能,涵盖 API 密钥隔离、RLS 策略和 API 加固,并提供 SQL 与 JavaScript 示例。

主要功能

  1. 01区分客户端与服务端密钥
  2. 02RLS 策略模式与审计
  3. 03API 加固清单
  4. 04SQL 注入防护指导
  5. 05安全错误诊断

适用场景

  1. 01审计 public 表的 RLS 覆盖情况
  2. 02将 service role 密钥限制在服务端代码
  3. 03加固 CORS 和数据库连接

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jeremylongshore/tons-of-skills-marketplace --skill supabase-security-basics

安装后在 Claude Code 中输入/supabase-security-basics确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
errors.md
examples.md
implementation.md

详细介绍

Supabase 安全基础

概述

Supabase 通过 PostgREST 将 Postgres 数据库直接暴露到互联网。任何未启用行级安全(RLS)的表,只要有人拥有你的项目 URL 和 anon key(两者都是公开的),就可以被其完全读取和写入。本技能介绍 Supabase 安全的三大支柱:密钥分离(anon 与 service_role)、RLS 策略强制执行,以及 API 面加固。

前提条件

  • 已创建 Supabase 项目(本地或托管),并可访问 Dashboard
  • 已安装 @supabase/supabase-js(npm install @supabase/supabase-js)
  • 已配置 SUPABASE_URL 和 SUPABASE_ANON_KEY 环境变量
  • 了解 SQL 和 Postgres 基础知识

说明

步骤 1 — 了解两种 API 密钥

Supabase 为每个项目提供两种密钥。混淆它们是最常见的安全错误:

密钥环境变量是否暴露给客户端?RLS 行为
Anon keySUPABASE_ANON_KEY是 — 可安全用于浏览器遵循所有 RLS 策略
Service role keySUPABASE_SERVICE_ROLE_KEY绝不可暴露绕过所有 RLS

anon key 是一个 JWT,PostgREST 会用它来确定适用哪些 RLS 策略。将它包含在客户端代码包中是安全的——它只能访问 RLS 策略明确允许的数据。service role key 会绕过所有 RLS 策略,因此只能存在于服务端代码中(API 路由、Edge Functions、定时任务、迁移脚本)。

// CORRECT: anon key on the client
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
// CORRECT: service role key ONLY in server-side code
// e.g., app/api/admin/route.ts (Next.js server route)
import { createClient } from '@supabase/supabase-js'

const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
  { auth: { autoRefreshToken: false, persistSession: false } }
)
// WRONG — service role key in client-side code
// This bypasses ALL RLS and leaks your admin key to every user
const supabase = createClient(url, process.env.NEXT_PUBLIC_SERVICE_ROLE_KEY!)  // NEVER DO THIS

**密钥轮换:**在 Dashboard > Settings > API 中重新生成密钥。轮换后,更新所有环境变量并重新部署所有服务。旧密钥会立即失效——没有宽限期。

步骤 2 — 为每张表强制启用行级安全

如果未启用 RLS,public schema 中的任何表都可通过 REST API 被持有 anon key 的任何人完全访问。RLS 不是可选项——它是主要的访问控制层。

-- Audit: find tables missing RLS
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;

-- Enable RLS on every public table
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

-- CRITICAL: enabling RLS with NO policies blocks ALL access via the API.
-- You MUST add at least one policy per table per operation (SELECT, INSERT, UPDATE, DELETE).

策略模式 — 用户读取和写入自己的行:

-- SELECT: user can only read their own rows
CREATE POLICY "Users read own data"
  ON public.todos FOR SELECT
  USING (auth.uid() = user_id);

-- INSERT: user can only insert rows for themselves
CREATE POLICY "Users insert own data"
  ON public.todos FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- UPDATE: user can only update their own rows
CREATE POLICY "Users update own data"
  ON public.todos FOR UPDATE
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

-- DELETE: user can only delete their own rows
CREATE POLICY "Users delete own data"
  ON public.todos FOR DELETE
  USING (auth.uid() = user_id);

策略模式 — 公开读取、已认证用户写入:

CREATE POLICY "Anyone can read posts"
  ON public.posts FOR SELECT
  USING (true);

CREATE POLICY "Authenticated users can insert"
  ON public.posts FOR INSERT
  WITH CHECK (auth.uid() IS NOT NULL);

策略模式 — 通过自定义 JWT 声明进行基于角色的访问控制:

-- Admin-only policy using app_metadata
CREATE POLICY "Admins have full access"
  ON public.settings FOR ALL
  USING (
    (auth.jwt() -> 'app_metadata' ->> 'role') = 'admin'
  );

在服务端设置自定义声明:

// Server-side only — requires service role key
const { error } = await supabaseAdmin.auth.admin.updateUserById(userId, {
  app_metadata: { role: 'admin' }
})

策略模式 — 按组织范围控制访问:

CREATE POLICY "Org members can read projects"
  ON public.projects FOR SELECT
  USING (
    EXISTS (
      SELECT 1 FROM public.members
      WHERE members.organization_id = projects.organization_id
      AND members.user_id = auth.uid()
    )
  );

关键区别 — USING 与 WITH CHECK:

  • USING (expr) — 筛选用户可以查看的现有行(SELECT、UPDATE、DELETE)
  • WITH CHECK (expr) — 验证新建或修改的行数据(INSERT、UPDATE)
  • 对于 UPDATE,两者都需要:USING 控制哪些行可以作为操作目标,WITH CHECK 控制新值必须满足的条件
步骤 3 — 加固 API 面

**JWT 验证:**Supabase 会自动在服务端验证 JWT。RLS 策略中的 auth.uid() 函数会从已验证的 JWT 中提取已认证用户的 ID。你无需在 RLS 策略中手动验证令牌——Supabase 会处理这一过程。

**防止 SQL 注入:**Supabase JS SDK 内部使用参数化查询。切勿根据用户输入拼接原始 SQL 字符串——始终使用 SDK 查询构建器:

// SAFE: SDK parameterizes automatically
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('author_id', userId)
  .ilike('title', `%${searchTerm}%`)

// DANGEROUS: raw SQL with string interpolation
// Only use supabase.rpc() with parameterized functions, never template literals

**网络限制:**在 Dashboard > Settings > Database > Network Restrictions 中,将直接数据库连接限制为已知 IP 地址范围。这不会影响 REST API(它通过 PostgREST 连接),但可以保护直接的 Postgres 连接。

**CORS 配置:**在 Dashboard > Settings > API > CORS 中为每个项目配置允许的来源。默认允许所有来源(*)——在生产环境中应限制为你的域名。

**禁用未使用的身份验证提供方:**Dashboard > Authentication > Providers。禁用当前未使用的任何提供方(email、phone、Google、GitHub 等),以缩小攻击面。

**强制使用 SSL:**Dashboard > Settings > Database > SSL Configuration。为所有直接数据库连接强制启用 SSL。

**语句超时:**防止长时间运行的查询耗尽数据库资源:

ALTER ROLE authenticated SET statement_timeout = '10s';
ALTER ROLE anon SET statement_timeout = '5s';

撤销默认 schema 授权(仅验证):

-- Supabase handles this by default, but verify:
-- anon and authenticated roles should only access data through RLS policies
SELECT grantee, privilege_type, table_name
FROM information_schema.role_table_grants
WHERE table_schema = 'public'
AND grantee IN ('anon', 'authenticated')
ORDER BY table_name, grantee;

输出

完成这些步骤后,你将拥有:

  • 仅用于客户端代码的 anon key,以及仅限服务端使用的 service role key
  • 所有 public 表均启用了 RLS,并为每种操作配置了明确的策略
  • 已配置用于基于角色的访问模式的自定义 JWT 声明
  • 已强化网络限制、CORS、SSL 和语句超时设置
  • 已禁用未使用的 auth 提供商
安全审计清单
  • 所有 public 表均已启用 RLS(SELECT rowsecurity FROM pg_tables WHERE schemaname='public')
  • 每张表都为所需的每种操作至少配置了一条 RLS 策略
  • service role key 未出现在任何客户端环境变量或 NEXT_PUBLIC_* 环境变量中
  • .env 文件已加入 .gitignore
  • 已启用电子邮件确认(Dashboard > Authentication > Settings)
  • OAuth 重定向 URL 已限制为你的域名
  • 已禁用未使用的 auth 提供商
  • 已启用 SSL 强制执行(Dashboard > Database > SSL)
  • 数据库密码已从默认值修改
  • 已为直接数据库连接配置网络限制
  • 已为 authenticated 和 anon 角色设置 statement_timeout
  • 已为敏感用户操作启用 MFA
  • 已为生产环境启用时间点恢复(PITR)
  • 任何疑似泄露后都已轮换 API 密钥

错误处理

错误原因解决方案
42501: new row violates row-level security policy缺少 RLS 策略,或 WITH CHECK 条件不满足为该操作添加或修复 RLS 策略;确认 auth.uid() 与行中的用户列匹配
查询返回空的 data,且没有错误RLS 的 USING 子句筛除了所有行确认策略中的 auth.uid() 与已认证用户匹配;检查 JWT 声明
PGRST301: JWSErrorJWT 令牌无效或已过期重新认证用户;确认 SUPABASE_ANON_KEY 与项目匹配
PGRST302: anonymous access disabled客户端初始化时未提供 anon key将 anon key 传给 createClient();检查是否已设置环境变量
permission denied for table X已启用 RLS,但没有匹配的策略为特定操作(SELECT/INSERT/UPDATE/DELETE)创建策略
Could not find the function auth.uid()在 PostgREST 上下文之外运行 SQLauth.uid() 仅适用于由 PostgREST 评估的 RLS 策略;在迁移中使用显式用户 ID

示例

新表的最简安全配置:

-- 1. Create table
CREATE TABLE public.notes (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) NOT NULL DEFAULT auth.uid(),
  content TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- 2. Enable RLS immediately
ALTER TABLE public.notes ENABLE ROW LEVEL SECURITY;

-- 3. Add policies
CREATE POLICY "Users manage own notes" ON public.notes
  FOR ALL USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

客户端查询(anon key——强制执行 RLS):

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

// This only returns notes belonging to the authenticated user
// because the RLS policy filters by auth.uid()
const { data: notes, error } = await supabase
  .from('notes')
  .select('*')
  .order('created_at', { ascending: false })

资源

后续步骤

  • 使用 supabase-prod-checklist 应用生产环境强化配置
  • 使用 supabase-auth-flows 设置 auth 流程
  • 使用 supabase-migrations 配置数据库迁移

评论

支持 Markdown