pg_graphql 通过将 graphql 查询直接翻译为参数化 sql 并交由 postgresql 的 prepare + execute 执行,杜绝字符串拼接,使用户输入仅作为绑定参数处理,从而根本性防止 sql 注入;但自定义 resolver 中手动拼接 sql 仍需严格区分值(用参数化)与标识符(须白名单校验)。

pg_graphql 本身不会发生 SQL 注入 —— 它不拼接 SQL 字符串,所有查询都走 PostgreSQL 的预编译执行路径。 但如果你在自定义 resolver(比如用 Node.js + pg 或 Python + asyncpg 手写)里手动拼接 SQL,那风险就完全取决于你写的代码。
pg_graphql 的 SQL 注入防护机制怎么工作的
pg_graphql 是 PostgreSQL 的扩展,它把 GraphQL 查询直接翻译成参数化 SQL,全程不经过字符串拼接。所有变量($id、$email 等)都作为绑定参数传给 PgExecutor,底层调用的是 PREPARE + EXECUTE。这意味着即使用户传入 "'; DROP TABLE accounts; --" 这样的值,也会被当作字面量字符串处理,不会触发语句注入。
- 它不解析、不 eval、不 format —— 没有
sql = "SELECT * FROM users WHERE id = " + user_input这类操作 - 所有字段名、表名、操作类型都来自 Schema 静态定义,运行时不可动态增删
- 错误信息默认不暴露数据库细节(除非你显式开启
debug模式)
手写 resolver 时最容易踩的 SQL 注入坑
问题不在 GraphQL 层,而在你绕过 pg_graphql、自己写数据库访问逻辑的时候。常见错误包括:
- 用
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--": ""} - 在 resolver 中调用
eval()、exec()或第三方模板引擎渲染 SQL
安全写法:什么时候该用参数,什么时候必须白名单
参数化只对**值(value)** 有效;对**标识符(identifier)** 如表名、列名、ORDER 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 后还可能出问题
RLS(行级安全)能防止越权读写,但它不防 SQL 注入。如果 resolver 里拼接了恶意 SQL,RLS 策略甚至根本不会生效 —— 因为语句在进入策略检查前就被执行了(或直接报错)。更危险的是,某些拼接方式可能绕过 RLS 的 USING 条件,比如通过子查询或 CTE 提前读取敏感数据。
真正关键的防线只有两条:一是别拼 SQL,二是所有动态部分都落在白名单内。pg_graphql 帮你守住了第一条;第二条,得你自己盯住 resolver 里的每一行字符串操作。










