gbrain Row Level Security(RLS)实战指南:让每个 public 表默认拒绝 anon 读取
2026/9/20 7:58:17 网站建设 项目流程

gbrain Row Level Security(RLS)实战指南:让每个 public 表默认拒绝 anon 读取

【免费下载链接】gbrainGarry's 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 Security(RLS)。如果有一张表没开,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% 的情况下,你需要的正是这条修复语句。操作流程:

  1. postgres(或任意拥有BYPASSRLS的角色)连接数据库;
  2. 执行 doctor 给出的 SQL;
  3. 重新运行gbrain doctor,确认检查通过。

几个值得注意的实现细节(均来自 src/commands/doctor.ts):

  • doctor 使用单条 SQL 左连接pg_description,在一次往返内同时取回表的rowsecurity状态与可选注释;
  • 生成修复 SQL 时会对标识符中的双引号做转义("""),保证连weird"table这种病态表名渲染出的 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 TABLE
  • CREATE TABLE AS … SELECT
  • SELECT … INTO

这意味着无论表是由 gbrain 自身、共享同一个 Supabase 项目的其他应用,还是由人直接执行裸 SQL 创建,从它存在的第一毫秒起 RLS 就已开启

关键设计决策(源码注释有完整说明):

  • 只作用于public模式authstoragerealtime等非 public 模式被显式忽略——这些由 Supabase 自己管理,gbrain 不应触碰;
  • 触发器函数不包裹 EXCEPTIONddl_command_end在建表事务内部触发,一旦ALTER TABLE失败就会中止整个 CREATE TABLE——这是响亮的失败信号,而不是静默的缺口;
  • create-if-absent 而非 DROP+CREATE(issue #3603):CREATE 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\s+reason=\S.{3,})。

回填前会先校验当前角色是否具备BYPASSRLS(识别超级用户与继承角色的rolbypassrls,见 #1385);不具备则抛异常中止迁移,保证config.version不被推进,下次initSchema时自动重试。

迁移完成后,gbrain doctorrls检查在所有 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。有两种妥善的应对方式:

  1. 应用连接角色拥有BYPASSRLS(例如同样使用postgres角色):新表 RLS 开启,但应用读写畅通——BYPASSRLS完全绕过 policy;
  2. 应用角色没有BYPASSRLS:应用需要在建表后立即CREATE POLICY授予自己所需的读写权限。触发器不会添加任何 policy——它只开启 RLS,在应用的 policy 落地之前保持 deny-by-default 姿态。

如果两种情况都不满足,应用将无法读取自己刚建的表。修复点在应用侧而非 gbrain 侧:要么授予BYPASSRLS,要么提供 policy。

触发器被删了怎么办

gbrain doctor内置rls_event_trigger检查(实现见 src/commands/doctor.ts),用于验证触发器是否安装且启用。它查询pg_event_triggerauto_rls_on_create_table的状态:

  • 记录不存在 →warn,提示按本指南重建;
  • evtenabled既不是O(origin)也不是A(always)→warnR表示仅副本触发,普通会话不会触发;D表示已禁用),提示执行ALTER EVENT TRIGGER auto_rls_on_create_table ENABLE;
  • 正常 →ok

如果你出于调试等原因手动删除了触发器,可用以下 DDL 重建(与 v35 迁移运行的完全相同,幂等:CREATE OR REPLACE+DROP EVENT TRIGGER IF EXISTS,可安全地以postgresBYPASSRLS角色粘贴到 psql):

CREATE 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.tsMIGRATIONS数组中。没有 CLI 快捷方式gbrain apply-migrations --force-retry面向的是 vX.Y.Z 编排器(orchestrator)注册表,而非 v35 这种数字 schema 迁移。

为什么不用 FORCE ROW LEVEL SECURITY?

Postgres 有两只 RLS 旋钮:

  • ENABLE:阻断 anon/authenticated 角色的读取;
  • FORCE额外阻断表 OWNER 的访问(除非其持有BYPASSRLS)。

gbrain 只用ENABLE,与 src/schema.sql、migration v24、v29 的姿态保持一致。原因:FORCE会把非BYPASSRLS应用锁在它们自己刚建的表之外——触发器函数继承的是调用者的角色,而非 gbrain 角色——从而破坏上文"跨应用共存"的方案。如果你确实想对某张 gbrain 专属表做纵深防御(defense-in-depth),请在你自己的迁移中显式添加FORCE;gbrain 的自动 RLS 默认不会替你开启它。

那 1% 的情况:刻意豁免

偶尔,某张 public 表本来就该对 anon key 可读,例如:

  • 支撑公开仪表盘的 analytics 视图;
  • 只读参考表;
  • 自带前端、刻意使用 anon key 读取数据的插件。

gbrain 为此提供了逃生通道,并且故意把它设置得很麻烦——这正是设计的一部分

豁免注释契约

-- 在 psql 中,以 BYPASSRLS 角色(如 postgres)连接执行: COMMENT ON TABLE public.your_table IS 'GBRAIN:RLS_EXEMPT reason=<why this is anon-readable on purpose>';

规则(doctor 与 v35 回填共用同一正则^GBRAIN:RLS_EXEMPT\s+reason=\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 reason=analytics-only, anon-readable ok, owner=you, 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 是嵌入式、单用户的,前面没有 PostgREST,public-schema 暴露风险不存在。输出为:

rls: ok — Skipped (PGLite — no PostgREST exposure, RLS not applicable)

同理,rls_event_trigger检查也会跳过(PGLite 不支持事件触发器)。v35 迁移在 PGLite 上是 no-op(sqlFor.pglite为空字符串)。

一旦你迁移到 Supabase 或自托管 Postgres,检查立即开始运行,并会标记任何从 PGLite 带过来但未开启 RLS 的表。

自托管 Postgres

如果运行 Postgres 时前面没有 PostgREST,anon 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 的rlsrls_event_trigger双检查 持续监控,最后以GBRAIN:RLS_EXEMPT注释作为唯一、可审计、需手写理由的豁免通道。对运维而言,日常需要记住的只有三点:新表无需操心(触发器自动处理);doctor 报错就执行它给出的ALTER TABLE;只有确实需要 anon 可读的表,才用 psql 写下理由并打上豁免注释。

【免费下载链接】gbrainGarry's Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询