RLS不防SQL注入,仅做行级过滤;SQL注入必须靠参数化查询或预编译语句防护。二者作用阶段不同:RLS在数据库内核层重写查询,SQL注入防护在应用层输入处理阶段。

RLS 本身不防 SQL 注入,它只做行级数据过滤;SQL 注入防护必须靠参数化查询或预编译语句。
RLS 和 SQL 注入是两类完全不同的安全机制
很多人误以为启用 RLS 就能“顺便”挡住 SQL 注入,这是危险的认知偏差。RLS 是 PostgreSQL 查询重写器在执行前自动注入 WHERE 条件的机制,它发生在 SQL 解析之后、执行之前,对恶意字符串拼接毫无感知。而 SQL 注入发生在应用层构造 SQL 字符串阶段——如果代码里写了 query = "SELECT * FROM users WHERE id = " + user_input,RLS 根本没机会介入。
- RLS 防的是「合法查询访问了不该看的行」
- SQL 注入防的是「非法输入篡改了查询语义」
- 二者作用阶段不同:一个是数据库内核层的访问控制,一个是应用与数据库交互时的输入处理
为什么 CREATE POLICY 里用 current_setting() 容易被绕过?
很多教程推荐在 RLS 策略中用 current_setting('app.tenant_id') 获取上下文,但这个值来自会话变量,而会话变量可被任意连接用户修改——只要该用户有 SET 权限,就能执行 SET app.tenant_id = 'fake-uuid',让 RLS 策略失效。
- 必须配合
LEAKPROOF函数封装敏感逻辑,比如get_tenant_id()内部校验 GUC 值合法性并强制转换类型 - 禁止给应用角色授予
SET权限:REVOKE SET ON PARAMETER app.tenant_id FROM app_user; - 更稳妥的做法是依赖 JWT payload 解析(如 Supabase 的
current_user_id()),而非手动设 GUC
pg_graphql 场景下真正的注入防护点在哪?
pg_graphql 本身不拼接 SQL 字符串,它把 GraphQL 请求解析成 AST 后,生成参数化查询(PREPARE + EXECUTE)。所以它的安全性取决于两点:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
- 你是否关闭了
pg_graphql.enable_search_path(防止通过 search_path 注入 schema) - 你是否在角色上禁用了
BYPASSRLS(否则 RLS 策略直接被跳过) - 你是否用
REVOKE ALL ON TABLE ... FROM public切断匿名访问路径
注意:pg_graphql 生成的查询仍会走 RLS,但它不解决应用层自己用 pg_query 或 psycopg2.execute() 手动拼接的问题——这部分必须由开发者保证使用 cursor.execute("SELECT ...", (param,)) 形式。
多租户系统中最容易被忽略的 RLS 失效点
当表之间存在 JOIN,且关联字段未受 RLS 约束时,过滤会失效。例如:
SELECT a.*, b.name FROM accounts a JOIN billing_info b ON a.id = b.account_id;
即使 accounts 表启用了 RLS,billing_info 没启,攻击者仍可通过 JOIN 推出其他租户数据。
- 所有参与 JOIN 的表都必须单独启用 RLS 并配置策略
- 避免跨租户外键引用;若必须存在,确保外键列也纳入策略条件(如
tenant_id) - 用
EXPLAIN (ANALYZE, VERBOSE)查看实际执行计划,确认每张表都加了 RLS 过滤条件
真正难的不是写一条 CREATE POLICY,而是确保策略在所有访问路径(直接查、视图、函数返回集、JOIN、UNION)下都生效,且不被管理员角色意外绕过。

















