gbrain Row Level Security(RLS)实战指南:让每个 public 表默认拒绝 anon 读取
发布时间:2026/9/20 7:59:03 锦皓数字建站
实战指南:让每个 public 表默认拒绝 anon 读取`)
gbrain Row Level SecurityRLS实战指南让每个 public 表默认拒绝 anon 读取【免费下载链接】gbrainGarrys Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain本篇指南以 gbrain 官方文档 docs/guides/rls-and-you.md 为骨架结合 doctor 检查实现、v35 schema 迁移 与 schema.sql 基线 源码系统讲解 gbrain 的 RLS 安全模型为什么public模式中每一张表都必须开启行级安全、gbrain doctor报错后如何修复、自动 RLS 事件触发器与一次性回填的运作原理以及刻意让某张表对 anon key 可读的唯一官方逃生通道GBRAIN:RLS_EXEMPT注释契约。读完你将能独立处理 Supabase / 自托管 Postgres 场景下与 RLS 相关的全部运维与合规问题。为什么 RLS 如此重要anon key 就是事实上的公开凭据简短版本gbrain 的public模式中每一张表都必须开启 Row Level SecurityRLS。如果有一张表没开gbrain doctor的检查会从警告升级为失败进程以退出码 1 结束。深层原因在于 gbrain 的典型部署形态Supabase 通过 PostgREST 将public模式中的一切暴露为 REST API客户端应用持有的是anon key它在设计上就是一个客户端侧的秘密——任何拿到前端代码的人都能看到它如果某张 public 表 RLS 关闭anon key 就能直接读取它。对于认证令牌、聊天记录、财务数据这类敏感表这不再是小坑而是一条实实在在的数据外泄通道exfiltration vector。gbrain 自身的连接使用的是service-role实际是拥有BYPASSRLS权限的角色通常是postgres。因此开启 RLS 但不建任何 policy并不会破坏 gbrain 自身——它只是阻断 anon key 的默认读取。这正是 gbrain 的安全姿态security posture对 anon 默认拒绝deny-by-default对 service role 完全放行full access。这一姿态在 src/schema.sql 中有明确注释与落地schema 基线只在当前角色具备BYPASSRLS含继承角色、rolsuper见 issue #1385 的处理时才对全部 gbrain 表执行ALTER TABLE ... ENABLE ROW LEVEL SECURITY否则只发出RAISE WARNING避免把自己锁在门外。当 doctor 失败时三步修复流程gbrain doctor的 RLS 检查会扫描public模式中的每一张基表而非硬编码白名单实现见 src/commands/doctor.ts。检查失败时它会逐表点名并给出可直接复制执行的ALTER TABLE修复语句典型输出如下1 table(s) WITHOUT Row Level Security: expenses_ramp. Fix: ALTER TABLE public.expenses_ramp ENABLE ROW LEVEL SECURITY; If a table should stay readable by the anon key on purpose, see docs/guides/rls-and-you.md for the GBRAIN:RLS_EXEMPT comment escape hatch.99% 的情况下你需要的正是这条修复语句。操作流程以postgres或任意拥有BYPASSRLS的角色连接数据库执行 doctor 给出的 SQL重新运行gbrain doctor确认检查通过。几个值得注意的实现细节均来自 src/commands/doctor.tsdoctor 使用单条 SQL 左连接pg_description在一次往返内同时取回表的rowsecurity状态与可选注释生成修复 SQL 时会对标识符中的双引号做转义→保证连weirdtable这种病态表名渲染出的 SQL 也能安全复制粘贴有豁免表时失败信息末尾会附带(N other table(s) explicitly exempt.)提示避免误以为豁免被撤销。自动 RLS事件触发器 一次性回填仅靠 doctor 事后发现仍然存在时间窗一张表可能在public模式中存在一段时间而完全没有 RLS成为静默风险源。为此 gbrain 通过schema migration v35auto_rls_event_trigger内置了两套机制完整 DDL 见 src/core/migrate.ts。1. DDL 事件触发器event trigger名为auto_rls_on_create_table的 Postgres 事件触发器会对每一个新建的public.*表执行ALTER TABLE … ENABLE ROW LEVEL SECURITY。它覆盖 Postgres 报告为建表命令的全部三种语法CREATE TABLECREATE TABLE AS … SELECTSELECT … INTO这意味着无论表是由 gbrain 自身、共享同一个 Supabase 项目的其他应用还是由人直接执行裸 SQL 创建从它存在的第一毫秒起 RLS 就已开启。关键设计决策源码注释有完整说明只作用于public模式auth、storage、realtime等非 public 模式被显式忽略——这些由 Supabase 自己管理gbrain 不应触碰触发器函数不包裹 EXCEPTIONddl_command_end在建表事务内部触发一旦ALTER TABLE失败就会中止整个 CREATE TABLE——这是响亮的失败信号而不是静默的缺口create-if-absent 而非 DROPCREATEissue #3603CREATE EVENT TRIGGER是超级用户保留操作在 RDS/Aurora 等托管 Postgres 上普通应用角色没有rolsuperrds_superuser也不够。因此迁移改为不存在才创建运维可以先用 master 用户预创建函数与触发器迁移随后自动收敛权限不足时会抛出一条可操作的中文错误信息提示预创建 SQL 的位置而不是原始权限报错。2. 一次性回填one-time backfill首次应用 v35 迁移时回填逻辑会遍历public.*中每一张满足以下条件的基表并开启 RLS当前 RLS 关闭relrowsecurity false表注释不携带GBRAIN:RLS_EXEMPT豁免标记正则与 doctor 完全一致^GBRAIN:RLS_EXEMPT\sreason\S.{3,}。回填前会先校验当前角色是否具备BYPASSRLS识别超级用户与继承角色的rolbypassrls见 #1385不具备则抛异常中止迁移保证config.version不被推进下次initSchema时自动重试。迁移完成后gbrain doctor的rls检查在所有 brain 上应当成为 no-op。让表在回填中保持 RLS 关闭如果你有刻意保持 RLS 关闭的 public 表必须在 v35 迁移运行之前就给表加上GBRAIN:RLS_EXEMPT注释契约格式见下文。回填会对任何不带该注释的 RLS-off public 表强制执行 RLS。注意该迁移没有--dry-run旗标。搞错的最低代价是一次往返运维先执行 SQL 开启了本应豁免的表的 RLS然后执行ALTER TABLE … DISABLE ROW LEVEL SECURITY并补上豁免注释防止下次 doctor 再把它翻回去。全程不会丢失数据。跨应用影响其他应用共享同一个项目时如果非 gbrain 应用side project、自写脚本等在同一个 Supabase 项目中建表触发器同样会给这些表开启 RLS。有两种妥善的应对方式应用连接角色拥有BYPASSRLS例如同样使用postgres角色新表 RLS 开启但应用读写畅通——BYPASSRLS完全绕过 policy应用角色没有BYPASSRLS应用需要在建表后立即CREATE POLICY授予自己所需的读写权限。触发器不会添加任何 policy——它只开启 RLS在应用的 policy 落地之前保持 deny-by-default 姿态。如果两种情况都不满足应用将无法读取自己刚建的表。修复点在应用侧而非 gbrain 侧要么授予BYPASSRLS要么提供 policy。触发器被删了怎么办gbrain doctor内置rls_event_trigger检查实现见 src/commands/doctor.ts用于验证触发器是否安装且启用。它查询pg_event_trigger中auto_rls_on_create_table的状态记录不存在 →warn提示按本指南重建evtenabled既不是Oorigin也不是Aalways→warnR表示仅副本触发普通会话不会触发D表示已禁用提示执行ALTER EVENT TRIGGER auto_rls_on_create_table ENABLE;正常 →ok。如果你出于调试等原因手动删除了触发器可用以下 DDL 重建与 v35 迁移运行的完全相同幂等CREATE OR REPLACEDROP EVENT TRIGGER IF EXISTS可安全地以postgres等BYPASSRLS角色粘贴到 psqlCREATE OR REPLACE FUNCTION auto_enable_rls() RETURNS event_trigger AS $$ DECLARE obj record; BEGIN FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE object_type table AND schema_name public LOOP EXECUTE format(ALTER TABLE %s ENABLE ROW LEVEL SECURITY, obj.object_identity); END LOOP; END; $$ LANGUAGE plpgsql; DROP EVENT TRIGGER IF EXISTS auto_rls_on_create_table; CREATE EVENT TRIGGER auto_rls_on_create_table ON ddl_command_end WHEN TAG IN (CREATE TABLE, CREATE TABLE AS, SELECT INTO) EXECUTE FUNCTION auto_enable_rls();注意此处%s是正确的格式化符因为obj.object_identity已被 Postgres 预加引号如public.My Table若改用%I会造成双重引号。规范的触发器 DDL 存放在src/core/migrate.ts的MIGRATIONS数组中。没有 CLI 快捷方式gbrain apply-migrations --force-retry面向的是 vX.Y.Z 编排器orchestrator注册表而非 v35 这种数字 schema 迁移。为什么不用 FORCE ROW LEVEL SECURITYPostgres 有两只 RLS 旋钮ENABLE阻断 anon/authenticated 角色的读取FORCE额外阻断表 OWNER 的访问除非其持有BYPASSRLS。gbrain 只用ENABLE与 src/schema.sql、migration v24、v29 的姿态保持一致。原因FORCE会把非BYPASSRLS应用锁在它们自己刚建的表之外——触发器函数继承的是调用者的角色而非 gbrain 角色——从而破坏上文跨应用共存的方案。如果你确实想对某张 gbrain 专属表做纵深防御defense-in-depth请在你自己的迁移中显式添加FORCEgbrain 的自动 RLS 默认不会替你开启它。那 1% 的情况刻意豁免偶尔某张 public 表本来就该对 anon key 可读例如支撑公开仪表盘的 analytics 视图只读参考表自带前端、刻意使用 anon key 读取数据的插件。gbrain 为此提供了逃生通道并且故意把它设置得很麻烦——这正是设计的一部分。豁免注释契约-- 在 psql 中以 BYPASSRLS 角色如 postgres连接执行 COMMENT ON TABLE public.your_table IS GBRAIN:RLS_EXEMPT reasonwhy this is anon-readable on purpose;规则doctor 与 v35 回填共用同一正则^GBRAIN:RLS_EXEMPT\sreason\S.{3,}注释值必须以GBRAIN:RLS_EXEMPT开头区分大小写必须包含reason且理由至少 4 个字符没有任何其他途径无前缀变体、配置文件里的开关、环境变量统统不算——只有 Postgres 表注释算数如果该表 RLS 也确实关闭anon key 真正可读的必要条件你还需要显式执行ALTER TABLE ... DISABLE ROW LEVEL SECURITY;。仅关闭 RLS 不够——注释才是告诉 doctor 这是有意的的关键。完整示例ALTER TABLE public.expenses_ramp DISABLE ROW LEVEL SECURITY; COMMENT ON TABLE public.expenses_ramp IS GBRAIN:RLS_EXEMPT reasonanalytics-only, anon-readable ok, owneryou, 2026-04-22;之后gbrain doctor报告rls: ok — RLS enabled on 20/21 public tables (1 explicitly exempt: expenses_ramp)注意每次成功运行 doctor 都会按名字重新枚举你的豁免表。这是有意为之——逃生通道不是一次性签字而是反复出现的提醒。你随时可以运行gbrain doctor查看哪些表处于开放状态。为什么是 SQL 而不是 CLI 子命令gbrain没有提供gbrain rls-exempt add table这类 CLI 命令。原因是安全设计上的深思熟虑一个 CLI 命令会让 agent 轻易地静默向 anon 开放一张表。而在 psql 里手写注释这条约束强制操作者用 SQL 打出豁免理由且这个动作会暴露在多个可审计的面上出现在shell history中出现在git 追踪的 schema dump中下次恢复时出现在pg_dump输出中每次运行都出现在gbrain doctor输出中。agent 依然可以执行这段 SQL但它无法在用户看不到的情况下完成。这就是文档所称的 write it in blood用血写下设计。事后审计豁免查看当前数据库中所有豁免表SELECT c.relname AS table_name, obj_description(c.oid, pg_class) AS comment FROM pg_class c JOIN pg_namespace n ON n.oid c.relnamespace WHERE n.nspname public AND c.relkind r AND obj_description(c.oid, pg_class) LIKE GBRAIN:RLS_EXEMPT%;如果这份清单比你记忆里签过字的还要长那就是警报信号——该去核实是谁、为什么开放了这些表。移除一个豁免只要删掉注释并重新开启 RLS 即可ALTER TABLE public.expenses_ramp ENABLE ROW LEVEL SECURITY; COMMENT ON TABLE public.expenses_ramp IS NULL;gbrain doctor会停止将该表列为豁免并像对待其他表一样继续检查它。PGLite检查自动跳过如果你使用 PGLite零配置默认项doctor完全跳过RLS 检查PGLite 是嵌入式、单用户的前面没有 PostgRESTpublic-schema 暴露风险不存在。输出为rls: ok — Skipped (PGLite — no PostgREST exposure, RLS not applicable)同理rls_event_trigger检查也会跳过PGLite 不支持事件触发器。v35 迁移在 PGLite 上是 no-opsqlFor.pglite为空字符串。一旦你迁移到 Supabase 或自托管 Postgres检查立即开始运行并会标记任何从 PGLite 带过来但未开启 RLS 的表。自托管 Postgres如果运行 Postgres 时前面没有 PostgRESTanon key 暴露风险并不存在。但 gbrain仍然会在缺少 RLS 时使检查失败原因有三所有 public 表开启 RLS是 gbrain 的安全不变量security invariant而非 Supabase 特有的变通方案ALTER TABLE ... ENABLE RLS修复在任何 Postgres 上都是无害的它只约束非 bypass 角色而 gbrain 不使用这类角色如果将来你在前面部署 PostgREST 或类似工具这道防线已经就位。如果你认为这个框架不适合你的部署形态请带着具体细节提 issue以便团队评估是否值得引入自托管豁免模式。结语从修复到预防的完整闭环gbrain 的 RLS 体系是一条完整的闭环schema.sql 基线 v24/v29 迁移 兜底存量表v35 事件触发器杜绝新建表缺口v35 回填收敛历史遗留表doctor 的rls与rls_event_trigger双检查 持续监控最后以GBRAIN:RLS_EXEMPT注释作为唯一、可审计、需手写理由的豁免通道。对运维而言日常需要记住的只有三点新表无需操心触发器自动处理doctor 报错就执行它给出的ALTER TABLE只有确实需要 anon 可读的表才用 psql 写下理由并打上豁免注释。【免费下载链接】gbrainGarrys Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。