很多人升级到 Node.js 22 后,会顺手把“运行时更新”误解成“数据库更安全了”。但 SQL 注入防护并不属于 Node.js 运行时能力,真正起作用的是数据库驱动是否正确参数化、ORM 是否绕开了原始拼接,以及你有没有把动态 SQL 结构限制在白名单内。下面就按常见开发场景拆开看:哪些写法是有效防护,哪些只是看起来更规范,实际仍然会把风险带进数据库。
参数化查询先要用对,mysql2 和 pg 都一样
Node.js 22 在 Promise 和模块加载方面有改进,但 mysql2、pg 的参数绑定机制并没有变化。也就是说,只要占位符或传参方式用错,升级版本并不会替你补上这层安全性。

核心原则很简单:把用户输入作为参数传给驱动,让数据库按预编译参数处理,而不是把值直接拼回 SQL 字符串。
db.query('SELECT * FROM users WHERE id = ?', [req.query.id]):正确,数组传参会触发参数绑定。db.query('SELECT * FROM users WHERE id = ?', req.query.id):错误,没有放进数组时,参数处理就可能失效,结果等同于把输入直接带进 SQL。db.query('SELECT * FROM users WHERE name = :name', { name: req.body.name }):这类命名绑定只在特定驱动里成立;文中的前提是pg支持,而mysql2不识别:name,会按原样传递。db.execute()通常比db.query()更严格,因为它要求按预编译模式执行,更适合用来减少误用空间。
这部分最常见的问题不是“开发者完全不知道参数化”,而是“以为自己已经参数化了”。实际项目里,一次少写数组括号、一次误用占位符格式,就足够让整条链路失效。
Sequelize 里的 literal() 是典型高危点
如果项目用了 Sequelize,最需要警惕的往往不是普通查询,而是 Sequelize.literal()。它的行为非常直接:把传入字符串原样塞进 SQL,不做额外转义。只要其中混入了 req.query、req.body 之类的运行时输入,注入入口就已经形成。

order: [[literal(`${req.query.sortBy} ${req.query.order}`)]]:这是直接把排序字段和排序方向拼进ORDER BY,攻击者可以借此插入恶意 SQL。where: { status: { [Op.eq]: literal(`'${req.query.status}'`) } }:这里虽然表面上补了单引号,但本质仍是字符串拼接,不是安全字面量。- 只有在内容完全静态、不包含任何运行时变量时,
literal()才相对可控,例如literal('updated_at DESC')或literal('NOW()')。
这也是很多 ORM 使用场景里的误区:ORM 可以帮你减少手写 SQL,但一旦你主动回到原始片段拼接,ORM 就不会替你兜底。
动态排序、字段名、分组字段必须改走白名单
还有一类风险特别容易被忽略,就是表名、列名、GROUP BY 字段、排序字段这些 SQL 结构部分。它们不是普通值,不能用 ? 这种参数占位来绑定,所以不能指望驱动自动帮你转义。
这时唯一可靠的做法就是白名单校验:只允许预先定义过的字段进入 SQL 结构。
const allowed = ['id', 'email', 'created_at'];
if (!allowed.includes(req.query.sortBy)) throw 400;
这种场景下,白名单不是“附加加固”,而是唯一正确的入口控制。尤其是列表排序、后台筛选、报表聚合这些功能,最容易因为追求灵活性而把结构字段直接暴露给前端。
Joi 只能做输入校验,不能替代参数化
Joi 的价值在于约束输入格式,例如字符串长度、数字类型、枚举范围,但它不是 SQL 防火墙。它能帮你筛掉明显异常的数据,却不能替代数据库层的安全执行方式。

joi.string().pattern(/^[w-]+$/)也许能挡住' OR 1=1这类常见输入,但它解决不了所有驱动差异和边界情况,例如文中提到的某些旧驱动空字节绕过问题。joi.number().integer()对id参数校验当然有意义,但如果后面仍写成`WHERE id = ${validated.id}`,那就是把一个“已经验证过的值”再次拼回 SQL,风险依旧存在。joi.string().max(50)也不能用来“防注入”,因为许多注入载荷本来就很短,长度限制对1; DROP TABLE--这类内容并不构成实质防护。
真正有效的链路只有一条:req → Joi 校验 → 参数化 query → 数据库执行。Joi 负责输入形态,参数化负责执行安全,这两个环节不能互相替代。
在 Node.js 22 里,判断是否安全就看这三件事
把全文收束成实际判断标准,可以快速检查一段 Node.js 22 数据库代码是否靠谱:
- 用户输入是否作为参数传入驱动,而不是拼接进 SQL 字符串。
- ORM 中是否使用了
literal()、原始片段或动态 SQL 结构,如果用了,是否只允许静态内容或白名单字段。 - 输入校验是否只是做格式约束,而不是被误当成“已经防注入”的替代方案。
结论其实很明确:Node.js 22 本身不提供 SQL 注入防护能力。无论是 mysql2、pg,还是 Sequelize、Joi,真正决定结果的都不是版本号,而是你有没有坚持参数化查询、避免原始拼接,并对白名单无法参数化的结构字段做硬限制。









