noctcore-prisma/no-raw-sql-outside-allowlist
Keep raw SQL that can touch a table inside an allowlist, and never use the
*Unsafevariants.
Recommended preset: error · Autofix: no · Suggestions: no · Type information: not needed
A tenant-scope client extension dispatches on the query’s model. A raw query has no model, so it
passes through the extension unscoped: $queryRaw reading a tenant table returns every tenant’s
rows. The extension cannot parse SQL, so this door can only be guarded statically.
What it flags
Section titled “What it flags”$queryRaw/$executeRawas a tagged template whose literal text names a table-touching keyword (FROM,INTO,UPDATE,JOIN, …). Interpolated values are bound parameters and are not read, so${fromDate}is not the keywordFROM. APrisma.raw/Prisma.sql/Prisma.joinfragment splices text the rule cannot see, so it is reported as unreadable.$queryRaw/$executeRawin call form,$queryRawTyped, a bare reference (const run = tx.$executeRaw) and a destructure (const { $queryRaw } = client): the SQL text is not readable at the call site.$queryRawUnsafe/$executeRawUnsafeeverywhere, allowlist or not: they take a string, so they are an injection sink as well as a tenant escape.
const rows = await tx.$queryRaw`SELECT id FROM "Invoice" WHERE "accountId" = ${accountId}`;await tx.$queryRaw(Prisma.sql`SELECT 1`);await tx.$queryRaw`SELECT ${Prisma.raw(columns)}`;await prisma.$executeRawUnsafe(`DELETE FROM "Invoice" WHERE id = ${id}`);// touches no tableawait prisma.$queryRaw`SELECT 1`;await tx.$executeRaw`SELECT pg_advisory_xact_lock(${lockKey})`;
// a model query through the scoped clientconst rows = await prisma.invoice.findMany({ where: { accountId } });What it does not flag
Section titled “What it does not flag”- A tagged template whose literal text names no table keyword (
SELECT 1,SELECT pg_advisory_xact_lock(${lockKey})).pg_advisory_xact_lockdoes not matchLOCK: keywords match as whole words, and_is a word character. - A keyword inside an interpolated value:
${fromDate}is a bound parameter, not the textFROM. - Any raw SQL in an
allowedFilesfile, except the*Unsafevariants. Allowlisted files skip everything except the*Unsafereport.
The carve-out is a floor, not a proof: a tableless statement can still call a SQL function that
reads a table. That is a reviewed-code problem; the rule exists so the obvious
SELECT ... FROM "Invoice" cannot land silently.
Options
Section titled “Options”| Option | Type | Default | Meaning |
|---|---|---|---|
allowedFiles |
string[] (globs) |
['**/prisma/seed.ts', '**/*.seed.ts', '**/prisma/migrations/**', '**/*.isolation.spec.ts'] |
Files allowed to run any raw SQL except the *Unsafe variants. Replaces the default list. |
tableKeywords |
string[] |
['FROM', 'INTO', 'UPDATE', 'JOIN', 'TABLE', 'DELETE', 'MERGE', 'COPY', 'TRUNCATE', 'LOCK', 'ALTER', 'DROP'] |
Whole-word, case-insensitive keywords that mark a literal chunk as table-touching. |
Globs match the absolute path and the workspace-root-relative path, as described in
no-unscoped-prisma-outside-allowlist.
The rule keys on Prisma’s $-prefixed raw method names rather than on the receiver, so it takes no
receiver options.
'noctcore-prisma/no-raw-sql-outside-allowlist': ['error', { allowedFiles: ['**/prisma/seed.ts', '**/prisma/migrations/**', 'src/reports/**/*.sql.ts'],}],When not to use it
Section titled “When not to use it”If your app is single-tenant and raw SQL is reviewed by other means, the *Unsafe half is still
worth keeping; consider enabling the rule with a wide allowedFiles instead of turning it off.