pg_graphql本身不会发生sql注入,因其不拼接sql字符串,所有查询均通过postgresql的prepare+execute预编译执行,用户输入仅作为绑定参数处理;风险仅存在于自定义resolver中手动拼接sql时。

pg_graphql 本身不会发生 SQL 注入 —— 它不拼接 SQL 字符串,所有查询都走 PostgreSQL 的预编译执行路径。 真正的风险只出现在你绕过 pg_graphql、自己写 resolver 的时候。
pg_graphql 的参数化机制怎么起作用
pg_graphql 把 GraphQL 查询直接翻译成带 $1、$2 占位符的 SQL,然后调用 PostgreSQL 的 PREPARE + EXECUTE。用户输入(比如 $email)永远只是绑定参数,不会进入 SQL 解析阶段。
这意味着即使传入 "'; DROP TABLE accounts; --",PostgreSQL 也只当它是普通字符串值,不会执行语句。
关键点:
-
PREPARE阶段已确定语法结构,运行时只替换参数值 - 字段名、表名、操作类型全部来自 Schema 静态定义,无法被变量动态控制
- 错误信息默认不暴露数据库细节(除非显式开启
debug模式)
手写 resolver 时最常踩的三个坑
问题不在 GraphQL 层,而在你用 pg、asyncpg 或其他驱动手动构造 SQL 的地方。
常见错误包括:
- 用
format()或%s拼接表名/字段名:query = f"SELECT {field} FROM {table}"—— 表名无法参数化,必须白名单校验 - 把用户输入直接塞进 WHERE 条件而没用占位符:
pg.query("SELECT * FROM users WHERE email = '" + email + "'") - 用
json_build_object()构造动态 WHERE,但没过滤 key 名 —— 攻击者可传{"user_id': '1 OR 1=1--": ""}
什么时候该用参数,什么时候必须白名单
参数化只对**值(value)** 有效;对**标识符(identifier)**(如表名、列名、ORDER BY 字段、GROUP BY 字段)必须做白名单或正则校验。
安全写法示例:
- 值:用
$1、$2(PostgreSQL)或?/%s(Python),交由驱动处理 - 列名:从预定义集合取,例如
allowed_fields = {"name", "email", "created_at"},再检查if sort_field not in allowed_fields: raise ValueError - 表名:硬编码或从枚举映射,绝不能来自用户输入
- 动态 WHERE 键:用
dict.keys()做交集校验,例如valid_keys = {"status", "type", "category"}; filters = {k: v for k, v in user_filters.items() if k in valid_keys}
行级安全(RLS)和权限控制不是可选项
即使 SQL 写得完全安全,如果数据库用户权限过大,攻击者仍可能通过合法查询越权读取数据。
必须配合使用:
- 撤销无关表的
SELECT权限:REVOKE ALL PRIVILEGES ON public."Account" FROM api_user,让该表直接不出现在 GraphQL Schema 中 - 对敏感列撤权,字段会自动从对应 type 中消失
- 启用 RLS 策略,例如
CREATE POLICY user_data_policy ON accounts USING (user_id = current_user_id()),pg_graphql 执行时自动生效
真正容易被忽略的是:RLS 策略是否在 api_user 角色上启用(ALTER TABLE accounts ENABLE ROW LEVEL SECURITY),以及该角色是否被正确用于 pg_graphql 连接池。漏掉任一环,防护就形同虚设。











