Knex.js本身不提供原生预编译语句机制,其where()等方法默认生成参数化SQL交由底层驱动(如mysql2、pg)安全执行;knex.raw()必须显式传入参数数组,否则易导致SQL注入;表名、字段名等结构部分无法参数化,须通过白名单校验。

Knex.js 本身不提供原生的预编译语句(Prepared Statement)绑定机制——它生成的 SQL 字符串最终仍由底层驱动(如 mysql2 或 pg)执行,而是否真正走预编译路径,取决于你**怎么把 SQL 和参数交给驱动**,而不是 Knex 自己“编译”了什么。
Knex 的 where()、select() 等方法默认是安全的
Knex QueryBuilder 构建的绝大多数查询(如 .where('id', req.query.id)、.where({ username: req.body.username }))会自动转为带 ? 或 $1 占位符的参数化语句,并交由底层驱动以安全方式执行。这背后依赖的是:mysql2 的 execute() 方法或 pg 的 client.query(text, params) 模式。
- ✅ 安全:所有字段名、表名都硬编码,用户输入只出现在值的位置(如
.where('email', userInput)) - ❌ 不安全:一旦你用模板字符串拼接用户输入(
.whereRaw(`status = '${req.query.status}'`)),就绕过了整个参数化流程 - ⚠️ 注意:
whereRaw()、selectRaw()、knex.raw()这些 API 本身不防注入——它们只是把字符串原样传下去,必须手动配参数数组
knex.raw() 必须显式传参数组,否则退化为字符串拼接
这是最常翻车的地方。knex.raw() 接收两个参数:SQL 模板字符串 + 参数数组。漏掉第二个参数,或参数没用占位符匹配,等于直接把用户输入塞进 SQL 字符串里。
- ✅ 正确:
knex.raw("SELECT * FROM users WHERE email = ? AND age > ?", [req.body.email, req.body.age]) - ❌ 危险:
knex.raw("SELECT * FROM users WHERE email = '" + req.body.email + "'")—— 单引号拼接,完全暴露 - ❌ 危险:
knex.raw("SELECT * FROM users WHERE email = ?", req.body.email)—— 第二个参数不是数组,req.body.email被当作文本直接插进去(mysql2下会报错;pg下可能静默失败或误解析) - ⚠️ 注意:
sqlite3驱动只认?,不支持命名参数;pg支持$1、$2或%(name)s,但必须和参数数组顺序/键名严格对应
动态表名/字段名不能参数化,必须白名单校验
SQL 标准不允许对表名、列名、排序字段(ORDER BY)、分组字段(GROUP BY)使用占位符——这些属于「结构部分」,数据库驱动无法在预编译阶段预留位置。Knex 也不支持对 .from() 或 .orderBy() 的值做参数绑定。
- ✅ 可行:
const validSortFields = ['name', 'email', 'created_at']; if (!validSortFields.includes(req.query.sort)) throw new Error('Invalid sort'); knex.orderBy(req.query.sort, 'asc') - ❌ 无效:
knex.orderBy('?', [req.query.sort])—— Knex 会把它当字面量字符串,生成ORDER BY '?' ASC - ⚠️ 特别注意:Knex 的
knex.ref()只用于标识引用(如防止关键字冲突),不提供运行时校验,不能替代白名单
真正决定是否触发预编译的,是底层驱动如何处理你传入的 SQL 和参数。Knex 只负责组装,不负责“编译”。哪怕你写了一百个 .where(),只要最后调用的是 knex.queryBuilder().toString() 再手动拼接执行,就等于亲手拆掉防护。安全边界始终在「谁控制 SQL 结构」和「谁传递纯数据」这两条线上。


















