FlawOpen

flawopen.com/SQL Injection in JavaScript/Prisma

Is Prisma $queryRaw safe from SQL injection?

Reference guide
Short answer

Think of a query parameter as a sealed value slot in a SQL form: the database treats the value as data, not query syntax. Prisma's $queryRaw tagged-template form binds interpolated values. $queryRawUnsafe can also bind values when you pass placeholders and arguments; it becomes risky when you build SQL by concatenating untrusted text. Neither method binds table or column names.

UNSAFE — concatenate input into SQL
const userId = request.query.userId;
const query =
  `SELECT id FROM users WHERE id = ${userId}`;
const rows = await prisma.$queryRawUnsafe(query);
SAFE — bind values; allowlist SQL structure
const rows = await prisma.$queryRaw`
  SELECT id FROM users WHERE id = ${userId}
`;

// PostgreSQL example: $1 binds data, not an identifier.
const columns = { name: 'name', created: 'created_at' } as const;
const column = columns[requestedSort as keyof typeof columns] ?? 'created_at';
const pageSize = 25;
const sortedRows = await prisma.$queryRawUnsafe(
  `SELECT id FROM users ORDER BY ${column} LIMIT $1`,
  pageSize
);
// The selected SQL structure comes from a fixed map.

What Prisma parameters do — and what they do not do

In $queryRaw, use the tagged-template call directly: each interpolated value becomes a database parameter. Do not first assemble a query string and pass that string as the only argument. Prisma also supports parameter placeholders with $queryRawUnsafe(query, ...values); this can bind values, but the SQL text still needs to be trusted. Parameters represent data values, not identifiers, operators, or SQL fragments. Map any selectable column to a fixed application-owned allowlist. Prisma recommends TypedSQL for many type-safe raw-query use cases.

How to audit Prisma raw queries

grep -rn "\$queryRawUnsafe\|\$executeRawUnsafe" --include="*.ts" --include="*.js" .

FAQ

Why does $queryRawUnsafe exist?

It accepts raw SQL strings and can support parameter placeholders when values are passed separately. Its flexibility makes unsafe string construction easy, so inspect the SQL text and arguments together. It does not make dynamic identifiers safe; choose identifiers from a fixed allowlist.

References

See also SQL Injection (CWE-89): How It Works and How to Prevent It in Every Language — flawopen.com Is Rails where() String Interpolation Safe? Is Sequelize query() Safe from SQL Injection? Is SQLAlchemy text() Safe from SQL Injection? Does sqlx's Compile-Time Checking Prevent SQL Injection? Are Stored Procedures Immune to SQL Injection? Is TypeORM QueryBuilder Safe from SQL Injection?