prisma.$queryraw 必须用模板字符串占位符传参,禁止字符串插值;用户输入需白名单校验后才可用于动态结构;参数类型须严格匹配,复杂查询应统一纳入事务。

prisma.$queryRaw 必须用模板字符串参数占位符,不能插值
很多人以为写 prisma.$queryRaw`SELECT * FROM users WHERE name = ${req.body.name}` 是安全的,其实不是。模板字符串里的 ${...} 在 JS 层就完成了字符串拼接,等价于手动拼 SQL,Prisma 完全没机会做参数绑定。
正确写法只有一种:所有用户输入必须作为独立参数传入,且只出现在占位符位置:
-
prisma.$queryRaw`SELECT * FROM users WHERE name = ${name}`✅ ——name是变量,不是字符串插值 -
prisma.$queryRaw`SELECT * FROM users WHERE id IN (${Prisma.join(ids)})`✅ ——Prisma.join()专为数组设计,底层走参数化 -
prisma.$queryRawUnsafe(...)❌ —— 名字就写着“不安全”,除非你 100% 确保输入是硬编码常量
动态表名和字段名必须白名单校验,Prisma 不提供运行时转义
SQL 的结构部分(如 FROM、ORDER BY、GROUP BY)根本无法参数化。Prisma 不会对 req.query.sortBy 做任何过滤或转义,它只管“值”的绑定。
所以你要在 Controller 层自己控制入口:
- 定义允许的字段白名单:
const allowedSortFields = ['id', 'title', 'created_at'] - 校验后才使用:
const sortField = allowedSortFields.includes(req.query.sortBy) ? req.query.sortBy : 'id' - 再构造查询:
orderBy: { [sortField]: 'asc' }或prisma.$queryRaw`ORDER BY ${Prisma.raw(sortField)} ASC`(注意:仅当sortField已白名单校验过)
别用正则删单引号、别信“只允许字母数字”——攻击者早就能用 Unicode 字符、反引号、空字节绕过。
$queryRaw 和 $executeRaw 的参数类型必须匹配,否则驱动可能降级为字符串拼接
Node.js 20 的 @prisma/client 默认使用更严格的参数绑定逻辑,但如果你传错类型,比如把数字当字符串、把数组当单值,某些底层驱动(尤其是 MySQL)会悄悄 fallback 到字符串拼接,导致破防。
ZenHeart zenlink 与 zenlink-mcp 为 OpenClaw,涵盖安装、MCP 注册、钩子接线、入站轮询及升级维护。
常见踩坑点:
- 传
req.query.id前没转成 number?parseInt(req.query.id, 10)或Number(req.query.id)更稳妥 - 想查多个 ID 却直接传数组:
WHERE id IN (${ids})❌ —— 必须用Prisma.join(ids) - 日期字段传了字符串而非
new Date()实例,某些场景下会被 toString() 后拼接
建议所有用户输入进查询前加一层类型断言或转换,尤其在 Node.js 20 的 strict mode 下更关键。
复杂查询中混合使用 Prisma Client 方法和 $queryRaw 时,注意事务边界
你在同一个数据库事务里既调用了 prisma.user.update() 又执行了 prisma.$queryRaw,这两者默认不在同一连接上下文里,除非显式用 prisma.$transaction() 包裹。
否则可能出现:
- 事务未生效:
$queryRaw走的是独立连接,提交/回滚不联动 - 隔离级别不一致:Client 方法可能带乐观锁,
$queryRaw却直连,数据状态不一致 - 连接泄漏:手动管理连接池时,
$queryRaw可能跳过 Prisma 的连接复用机制
安全做法是:所有涉及事务的原始 SQL,一律放进 prisma.$transaction([...]) 数组里,或用回调形式确保共享连接。
真正容易被忽略的是:Node.js 20 的 Promise 处理机制让异步错误更隐蔽,$queryRaw 报错时如果没 catch,可能静默失败;而 SQL 注入漏洞往往就藏在这种“看似正常但逻辑已歪”的地方。










