资讯动态

postgres_lsp 数据库规则详解:rlsReferencesUserMetadata 检测 RLS 策略中的不安全 user_metadata 引用

发布时间:2026/9/18 8:46:59 来源:尧图企业网站定制
postgres_lsp 数据库规则详解rlsReferencesUserMetadata 检测 RLS 策略中的不安全 user_metadata 引用【免费下载链接】postgres_lspA Language Server for Postgres项目地址: https://gitcode.com/GitHub_Trending/po/postgres_lsp本篇技术指南围绕 postgres_lsp 项目的 splinter 数据库级安全规则rlsReferencesUserMetadata诊断类别splinter/security/rlsReferencesUserMetadata展开讲解该规则如何检测 Supabase Authuser_metadata在行级安全RLS策略中被不安全引用的风险并结合源码逐行剖析其检测 SQL、运行机制与配置方式。读完本文你将掌握该规则的完整工作原理、如何在自己的 Postgres/Supabase 项目中开启与配置它以及如何通过dblint命令将其纳入数据库迁移检查流水线。规则概览它检测什么rlsReferencesUserMetadata是 postgres_lsp 中 splinter数据库级 lint安全类别下的一条规则用于检测 Supabase Authuser_metadata被不安全地引用在行级安全RLS策略中的情况。根据 规则文档该规则的核心信息如下属性值诊断类别splinter/security/rlsReferencesUserMetadataSeverityError错误触发场景RLS 策略的USINGqual或WITH CHECK表达式引用了 Supabase Auth 的user_metadata适用前提需要 Supabase 数据库/项目未检测到时会自动跳过[!NOTE] 该规则依赖 Supabase 数据库环境只有当数据库中检测到 Supabase 角色anon、authenticated、service_role时才会执行否则会自动跳过避免在普通 Postgres 实例上产生误报。在 security 规则组的模块声明 中可以看到rlsReferencesUserMetadata与authUsersExposed、rlsDisabledInPublic、sensitiveColumnsExposed等同属Security规则组共同构成针对 Supabase 场景的安全扫描矩阵。为什么 user_metadata 不能用于安全上下文该规则存在的根本原因是 Supabase Auth 的user_metadata是最终用户可编辑的字段。在 Supabase 的 JWT 认证体系中auth.jwt() - user_metadata从请求携带的 JWT 中提取user_metadata对象current_setting(request.jwt.claims) - user_metadata从request.jwt.claims配置参数中读取同样的用户元数据。由于用户可以在客户端自行注册、修改自己的资料信息如昵称、头像等自定义字段这些元数据一旦被写入 RLS 策略的USING或WITH CHECK表达式攻击者就可以通过篡改自己 JWT 中的 user_metadata 来绕过行级安全限制访问或修改本不该有权限的行。规则文档中给出的修复指引remediation指向 Supabase 官方数据库 linter 的对应条目0015_rls_references_user_metadata说明这一安全问题已被业界普遍关注。检测 SQL 逐段拆解规则的实际检测逻辑全部用 SQL 实现仓库中有两份等价来源手工维护的权威 SQL 文件crates/pgls_splinter/vendor/security/rls_references_user_metadata.sql以-- meta:注释声明规则的名称、标题、严重级别、类别等元数据由 xtask codegen 生成、通过include_str!内嵌进 Rust 二进制的文档字符串版本crates/pgls_splinter/src/rules/security/rls_references_user_metadata.rs。第一步收集所有 RLS 策略CTE policieswith policies as ( select nsp.nspname as schema_name, pb.tablename as table_name, polname as policy_name, qual, with_check from pg_catalog.pg_policy pa join pg_catalog.pg_class pc on pa.polrelid pc.oid join pg_catalog.pg_namespace nsp on pc.relnamespace nsp.oid join pg_catalog.pg_policies pb on pc.relname pb.tablename and nsp.nspname pb.schemaname and pa.polname pb.policyname )CTE 通过pg_policy策略定义、pg_class表/关系、pg_namespace模式与视图pg_policies的四表关联将每一条 RLS 策略还原为可读的schema_name、table_name、policy_name并取出其表达式qual即策略的USING表达式决定哪些行对当前用户可见/可操作with_check即策略的WITH CHECK表达式决定新插入或更新后的行是否允许写入。第二步拼接诊断输出select rls_references_user_metadata as name!, RLS references user metadata as title!, ERROR as level!, EXTERNAL as facing!, array[SECURITY] as categories!, Detects when Supabase Auth user_metadata is referenced insecurely in a row level security (RLS) policy. as description!, format( Table \%s.%s\ has a row level security policy \%s\ that references Supabase Auth \user_metadata\. \user_metadata\ is editable by end users and should never be used in a security context., schema_name, table_name, policy_name ) as detail!, https://supabase.com/docs/guides/database/database-linter?lint0015_rls_references_user_metadata as remediation!, jsonb_build_object( schema, schema_name, name, table_name, type, table ) as metadata!, format(rls_references_user_metadata_%s_%s_%s, schema_name, table_name, policy_name) as cache_key! from policies每行结果都会生成一条结构化的诊断记录name!/title!/level!/facing!/categories!规则的名称、标题、级别ERROR、面向对象类型EXTERNAL与分类SECURITY供诊断系统分类与展示detail!由format生成的面向开发者的详细描述明确指出“哪张表的哪个策略引用了user_metadata而user_metadata可被最终用户编辑绝不应出现在安全上下文中”metadata!通过jsonb_build_object输出的结构化对象信息schema、表名、类型用于下游报告与定位cache_key!以schema_table_policy三元组生成的唯一键供结果去重与缓存排序使用。值得注意所有这些输出列名都带!后缀结合 splinter 执行入口 中最终包装为SELECT * FROM (...) AS all_results ORDER BY cache_key!的拼接逻辑可以看出cache_key!还被用作最终结果集的确定性排序键保证多次运行输出顺序稳定。第三步命中条件与误报控制where schema_name not in ( _timescaledb_cache, _timescaledb_catalog, _timescaledb_config, _timescaledb_internal, auth, cron, extensions, graphql, graphql_public, information_schema, net, pgmq, pgroonga, pgsodium, pgsodium_masks, pgtle, pgbouncer, pg_catalog, pgtle, realtime, repack, storage, supabase_functions, supabase_migrations, tiger, topology, vault ) and ( -- Example: auth.jwt() - user_metadata -- False positives are possible, but it isnt practical to string match -- If false positive rate is too high, this expression can iterate qual like %auth.jwt()%user_metadata% or qual like %current_setting(%request.jwt.claims%)%user_metadata% or with_check like %auth.jwt()%user_metadata% or with_check like %current_setting(%request.jwt.claims%)%user_metadata% )命中逻辑包含两层过滤模式白名单排除对 TimescaleDB、auth、extensions、storage、vault 等 30 个系统/托管模式不做检查避免对平台内部策略产生噪声告警字符串模式匹配对qual与with_check两个表达式分别匹配两种典型的 user_metadata 读取写法——auth.jwt() - user_metadata与current_setting(request.jwt.claims) - user_metadata。SQL 注释中诚实声明了这种实现方式的取舍纯字符串匹配存在误报可能例如把user_metadata用在非安全场景但“在 SQL 层面做精确的语义解析并不现实”。如果误报率过高可以迭代改进这个匹配表达式。这正是字符串匹配型数据库规则在工程成本与准确性之间的典型平衡。源码级实现规则如何被声明与调度SplinterRule trait 与 REQUIRES_SUPABASE 标志与基于 AST 的 pgls 静态 lint 规则不同splinter 规则是一种“数据库级”规则规则的逻辑写在 SQL 文件中而不是 Rust 中执行时向数据库发起查询。这一设计在 crates/pgls_splinter/src/rule.rs 的SplinterRuletrait 中明确注释规则执行 SQL 查询没有基于 AST 的执行路径规则逻辑位于 SQL 文件而非 Rust 代码。RlsReferencesUserMetadata的实现rls_references_user_metadata.rs通过declare_rule!宏声明包含四个关键常量pub RlsReferencesUserMetadata { version: 1.0.0, name: rlsReferencesUserMetadata, severity: pgls_diagnostics::Severity::Error, recommended: true, } impl SplinterRule for RlsReferencesUserMetadata { const SQL_FILE_PATH: static str security/rls_references_user_metadata.sql; const DESCRIPTION: static str Detects when Supabase Auth user_metadata is referenced insecurely in a row level security (RLS) policy.; const REMEDIATION: static str https://supabase.com/docs/guides/database/database-linter?lint0015_rls_references_user_metadata; const REQUIRES_SUPABASE: bool true; }其中REQUIRES_SUPABASE true是“Supabase 专属规则”的身份标识文件头部注释Generated file, do not edit by hand, see xtask/codegen表明该 Rust 声明由 xtask/codegen/src/generate_splinter.rs 从 vendor SQL 文件中的-- meta:注释自动生成保证文档、SQL 与 Rust 声明三处信息一致。Supabase 环境检测与自动跳过REQUIRES_SUPABASE标志在 splinter 执行入口 中被消费// Check if Supabase roles exist (anon, authenticated, service_role) let has_supabase_roles params.schema_cache.is_some_and(|cache| { let required_roles [anon, authenticated, service_role]; required_roles.iter().all(|role_name| { cache.roles.iter().any(|role| role.name.as_str() *role_name) }) }); for rule_name in collector.enabled_rules { // Skip Supabase-specific rules if Supabase roles dont exist if !has_supabase_roles let Some(metadata) crate::registry::get_rule_metadata(rule_name) metadata.requires_supabase { continue; } ... }执行流程是先从 schema cache 中检查数据库是否同时存在anon、authenticated、service_role三个角色若角色不全则所有requires_supabase true的规则包括本规则被直接跳过命中的规则 SQL 在编译期通过include_str!内嵌运行时拼接成一条UNION ALL大查询统一执行lib.rs并设置set local search_path 避免搜索路径干扰执行结果再按全局 ignore 与每规则 ignore matcher 过滤lib.rs。如何配置该规则规则的配置在splinter配置节的security分组下键名为 camelCase 的rlsReferencesUserMetadata可取值error、warn、off。文档中的基础配置示例{ splinter: { rules: { security: { rlsReferencesUserMetadata: error } } } }配置结构对应的源码声明位于 crates/pgls_configuration/src/splinter/rules.rs该字段类型为OptionRuleConfigurationSplinterRuleOptions与同组的rlsPolicyAlwaysTrue、sensitiveColumnsExposed等规则保持一致支持更细粒度的选项如options与忽略模式。配置说明摘自 生成规则文档 中的 Configuration 段{ splinter: { rules: { security: { rlsReferencesUserMetadata: warn } } } }配置为推荐的推荐语义从生成代码可以看到该规则的recommended: true且其所属的整个Security规则组都被列为 recommended 规则rules.rs。也就是说在未显式覆盖的默认推荐配置下它默认以error级别参与数据库扫描如果你希望先观察后收紧可将其降为warn或临时关闭off。运行与集成dblint 数据库检查该规则属于数据库级规则运行入口是 CLI 的dblint子命令。其调用链crates/pgls_cli/src/commands/dblint.rs为let params PullDatabaseDiagnosticsParams { categories: RuleCategoriesBuilder::default().all().build(), max_diagnostics, only: Vec::new(), // Uses configuration settings skip: Vec::new(), // Uses configuration settings }; let PullDiagnosticsResult { diagnostics, skipped_diagnostics } workspace.pull_db_diagnostics(params)?;要点only/skip保持为空完全遵循配置文件中的规则开关因此上述error/warn配置直接生效诊断结果会汇总为报告默认 terminal 报告器也支持 json、junit、github、gitlab 等报告器并依据enforce_exit_codes逻辑设置退出码存在 error 即失败--error-on-warnings开启时 warning 也会导致失败该命令需要连接数据库数据库连接配置见 configure_database.md。因此将该规则纳入 CI 的最小流程是在postgres-language-server.jsonc或项目配置中启用rlsReferencesUserMetadata: error在迁移检查流水线中运行pgls dblint配合 checking_migrations.md 的检查方式由于该规则只针对 Supabase 环境需存在anon/authenticated/service_role角色非 Supabase 实例会自动跳过无需额外开关。与其他 RLS 安全规则的协同在 Security 规则组 中与行级安全直接相关的规则还包括规则关注点rlsDisabledInPublicpublic 模式下表未开启 RLSrlsEnabledNoPolicy开启了 RLS 但未创建任何策略rlsPolicyAlwaysTrue策略使用USING (true)/WITH CHECK (true)等过度宽松表达式authUsersExposedauth 相关表被过度暴露sensitiveColumnsExposed含敏感列的表经 API 暴露且无 RLS 保护policyExistsRlsDisabled已存在策略但 RLS 被关闭完整的数据库规则清单与来源可参阅 database_rules.md 与 database_rule_sources.md。rlsReferencesUserMetadata与其他规则互补前者堵住“策略表达式里引用了用户可控字段”的漏洞后者解决“策略缺失或过度宽松”的问题共同构成 RLS 安全的纵深防线。小结rlsReferencesUserMetadata是 postgres_lsp splinter 体系中典型的一条“SQL 即规则”数据库安全规则检测对象RLS 策略的USING/WITH CHECK表达式中引用了auth.jwt() - user_metadata或current_setting(request.jwt.claims) - user_metadata安全原理user_metadata由最终用户可编辑绝不能进入行级安全的判定条件实现方式规则逻辑完整封装在 vendor SQL 文件 中通过 xtask codegen 生成 Rust 声明并标记REQUIRES_SUPABASE true运行机制仅在检测到 Supabase 角色时执行lib.rs非 Supabase 实例自动跳过配置方式splinter.rules.security.rlsReferencesUserMetadata支持error/warn/off默认推荐启用集成方式通过pgls dblint命令执行可在迁移与 CI 流水线中强制拦截。对于使用 Supabase 的项目将本条规则与同组的其他 RLS 规则一起纳入推荐配置是防止行级安全被用户可控元数据击穿的第一道自动化防线。【免费下载链接】postgres_lspA Language Server for Postgres项目地址: https://gitcode.com/GitHub_Trending/po/postgres_lsp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价