gstack 部署 Supabase RLS 收紧迁移后如何用 verify-rls.sh 验证读与更新已被拦截
【免费下载链接】gstackUse Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack
gstack 的遥测数据存储在 Supabase 上,supabase/migrations/002_tighten_rls.sql这条迁移把 anon key 对所有遥测表的 SELECT 策略和 installations 表的 UPDATE 策略全部移除,只保留 INSERT 策略(供 v0.11.16 之前的旧客户端继续直写 PostgREST)。迁移部署到 Supabase 项目后,需要用supabase/verify-rls.sh跑一遍冒烟测试,确认三件事:读请求确实被 RLS 拦截、对 installations 的更新确实被拦截、INSERT 仍然放行(否则旧客户端会坏掉)。这个脚本就是 0.11.16.0 版本随迁移一起引入的配套验证工具。
002 迁移做了什么,脚本在验证什么
002_tighten_rls.sql 的关键动作:
DROP POLICY移除telemetry_events、installations、update_checks三张表上的anon_select策略;DROP POLICY移除 installations 上不受限的anon_update_last_seen策略(原策略允许更新全部列);REVOKE SELECT显式收回crash_clusters、skill_sequences两个视图的 anon 读权限(注释里写作 belt-and-suspenders);- 三张表的
anon_insert_only策略保留,注释明确说明这是为了 v0.11.16 之前的旧客户端,待 edge-function 同步普及后才会在后续迁移中移除。
脚本头部注释(verify-rls.sh)声明的验证目标与之一一对应:SELECT denied on all tables and views、UPDATE denied on installations、INSERT still allowed。
运行前提
- 需要
bash和curl(脚本内部用curl发请求,mktemp建临时响应文件); - 需要能访问 config.sh 里配置的 Supabase 项目地址。该文件自动提供两个变量:
GSTACK_SUPABASE_URL(项目 URL)和GSTACK_SUPABASE_ANON_KEY(公开 key,注释说明 RLS 已拒绝 anon key 的一切数据访问,因此安全可提交仓库); - 脚本必须针对已经部署了 002 迁移的 Supabase 项目运行,否则验证结果没有意义。
一个必须在运行前明确的副作用:第三组 INSERT 检查会向该项目的telemetry_events、update_checks、installations表真实写入测试行(如gstack_version: "verify_rls_test"、installation_id: "verify_rls_test")。这是脚本设计如此——它要验证的就是"INSERT 仍然放行"。如果你不想在生产数据里留下这些测试行,应在测试环境项目上运行,或运行后自行清理。
执行验证
在 gstack 仓库根目录运行脚本(verify-rls.sh 头部注释给出的方式):
bash supabase/verify-rls.shCHANGELOG 中另外记录过cd supabase && ./verify-rls.sh的等价写法,两种方式都是文档中出现过的真实命令。
脚本会依次执行 9 项检查,全部走${GSTACK_SUPABASE_URL}/rest/v1/路径,携带apikey和Authorization: Bearer头(值就是 anon key):
| 检查项 | 方法 | 预期 |
|---|---|---|
SELECTtelemetry_events/installations/update_checks/crash_clusters/skill_sequences(各 1 条) | GET | deny |
UPDATEinstallations(installation_id=eq.test_verify_rls,body{"gstack_version":"hacked"}) | PATCH | deny |
INSERTtelemetry_events/update_checks/installations | POST | allow |
如何判断结果
每个检查打印一行PASS、FAIL或WARN,结束后输出汇总:
Results: 9 passed, 0 failed (of 9 checks) VERDICT: PASS — reads/updates blocked, inserts allowed以上为脚本输出格式(PASS/FAIL 行是脚本按判断逻辑拼接生成的,具体每条的 HTTP 码取决于服务端响应;Results与VERDICT行的文案来自 verify-rls.sh)。退出码:全部通过为 0,有任何 FAIL 或 WARN 时打印VERDICT: FAIL并以 1 退出。
deny 类检查的判定规则(verify-rls.sh)值得留意,因为 HTTP 码相同但含义不同:
- 返回
401/403:被拒绝,PASS; - GET 返回
200且响应体是[]或空:说明 RLS 在行级过滤了数据,PASS(输出注明 "RLS filtering");GET 返回200且有数据:说明数据泄漏,FAIL; - PATCH 返回
204:没有行被修改——无论原因是 RLS 拦截还是行不存在,攻击者都无法改数据,计为 PASS; - 连接失败(HTTP 码记为
000)或其他未预期状态码:记 WARN 并计入 failed,最终VERDICT: FAIL。
allow 类检查中,200/201/204以及409(主键冲突,说明 INSERT 策略生效、行已存在)都计 PASS;若 INSERT 被拒(401/403)则 FAIL,意味着迁移误删了旧客户端依赖的策略。
两个会影响判读的版本细节
- 003 迁移会改变 UPDATE 检查的语义。003_installations_upsert_policy.sql 在 002 之后重新给 installations 加了一条受限的 UPDATE 策略:anon 通过列级
GRANT UPDATE (last_seen, gstack_version, os)只能更新这三列,first_seen和installation_id不可改。如果你的项目已经部署到 003,脚本里那条gstack_version的 PATCH 检查即使服务端允许该列更新,也会因为test_verify_rls这行不存在而返回 204,脚本按"no rows affected"计为 PASS。也就是说,这条检查在 002 之后的完整迁移链上验证的是"匿名调用者无法造成数据变更",而不是字面上的"UPDATE 语句被策略拒绝"。 - 旧版脚本的边界 case 已修复。CHANGELOG(0.11.16.1)记录了
verify-rls.sh曾存在的误判:现在 INSERT 成功、409冲突、204空更新都被正确处理为通过。如果你手上的脚本版本早于该修复,遇到409/204时的判定结果可能和本文描述不一致,建议先确认脚本内容。
结论与限制
- 成功条件唯一:9 项检查全部 PASS,
VERDICT: PASS,退出码 0,即"读/更新被拦截、INSERT 放行"与 002 迁移的设计目标一致。 - 任何
FAIL或WARN(含连接失败)都意味着验证未通过:要么迁移未部署到位,要么 URL/anon key 配置(config.sh)与目标项目不符,要么网络不通。 - 脚本对
mktemp失败会直接中止(verify-rls: mktemp failed, aborting),不使用可预测的 PID 路径回退——这是共享机器上防止响应文件被预建/符号链接攻击的行为,遇到中止时应检查TMPDIR//tmp的可写性而不是绕开它。 - 该脚本只覆盖 REST 层(PostgREST + RLS)的 anon key 行为;服务端的读写走 edge function 与
service_role_key,不在本脚本的验证范围内。
【免费下载链接】gstackUse Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考