安全底线是所有用户可控输入必须走参数化查询,绝不能拼接进sql字符串;deno的postgres驱动只认$n占位符,表名列名等动态部分须白名单校验并手动转义。

在 Deno 2 中用 PostgreSQL 驱动写 SQL,安全底线只有一条:**所有用户可控输入必须走参数化查询,绝不能拼接进 SQL 字符串**。Deno 生态目前主流是 postgres(由 denoland 官方维护)和 pgx(基于 pgx/v5 的 Deno 封装),两者都不支持堆叠查询,默认禁用多语句——但防护真正起效的前提,是你没亲手把门焊死前先撬开它。
为什么不能用字符串模板拼接 WHERE 条件或 ORDER BY 字段
哪怕只是拼个列名或排序方向,只要来源不可信(比如 URL 查询参数、表单字段名),就等于把 SQL 解析器的控制权交出去。例如:
const orderField = url.searchParams.get("sort"); // 可能是 "name; DROP TABLE users--"
const query = `SELECT * FROM products ORDER BY ${orderField} ASC`; // 危险!
await client.queryArray(query); // 可能直接执行恶意语句
PostgreSQL 协议允许服务端解析含分号的完整字符串,而 postgres 驱动在 Deno 2 下默认不拦截这种输入——它信任你传进去的是合法 SQL 片段。所以漏洞不出在驱动,而出在你绕过参数化机制的那一刻。
- 表名、列名、排序关键字(
ASC/DESC)、LIMIT数值等,都不能用$1绑定,必须白名单校验后拼接 - 白名单校验要严格:用
Object.hasOwn(allowedFields, input)而非模糊匹配或正则 - 拼接前务必用
sql.Identifier()类似逻辑转义(Deno 的postgres暂无内置 identifier API,需手动替换单引号、双引号、反斜杠)
正确使用 $1、$2 参数占位符的三个硬约束
Deno 的 postgres 驱动沿用 PostgreSQL 原生协议,只认 $n 形式的位置参数,不支持 ? 或命名参数。错误用法会直接报错或静默失败。
- 所有用户输入值(ID、邮箱、JSON 字符串、时间戳)必须用
$1,$2占位,参数以数组形式传入:client.queryArray("SELECT * FROM users WHERE id = $1", [userId]) - 数组长度必须与占位符数量严格一致,少一个会报
ERROR: there is no parameter $2,多一个会被忽略(但属逻辑错误) - NULL 值直接传
null,驱动自动映射为 SQLNULL;不要传字符串"null"或空字符串
事务中执行多条语句时,如何避免隐式提交陷阱
Deno 的 postgres 不像 psycopg2 那样默认开启隐式事务。每次 queryArray() 或 queryObject() 都是独立语句,除非显式用 BEGIN / COMMIT 包裹,否则不会回滚。
- 需要原子性的一组操作,必须手动发
BEGIN,再逐条执行,最后COMMIT或ROLLBACK - 不要依赖连接生命周期自动提交——Deno 连接池(如
createPool())不会帮你做这事 - 捕获异常后,必须立刻发
ROLLBACK,否则该连接后续请求可能卡在未结束事务里
最易被忽略的点是:Deno 环境下没有 psycopg2.sql 那样的 Identifier 工具函数,所有动态表名/列名拼接都得靠手写防御逻辑。别指望驱动替你过滤,它只管把字节流发给数据库——你给它什么,它就传什么。










