Prevent SQL injection by using $queryRaw tagged templates instead of $queryRawUnsafe. Use when writing raw SQL queries or dynamic queries.
Prevents SQL injection in Prisma by enforcing `$queryRaw` tagged templates over `$queryRawUnsafe`. Triggers when writing raw SQL queries or dynamic queries with user input.
/plugin marketplace add djankies/claude-configs/plugin install prisma-6@claude-configsThis skill is limited to using the following tools:
SQL injection is one of the most critical security vulnerabilities in database applications. In Prisma 6, raw SQL queries must be written using $queryRaw tagged templates for automatic parameterization. NEVER use $queryRawUnsafe with user input.
const email = userInput;
const users = await prisma.$queryRaw`
SELECT * FROM "User" WHERE email = ${email}
`;
Prisma automatically parameterizes ${email} to prevent SQL injection.
const email = userInput;
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM "User" WHERE email = '${email}'`
);
VULNERABLE TO SQL INJECTION - attacker can inject: ' OR '1'='1
import { Prisma } from '@prisma/client';
const conditions: Prisma.Sql[] = [];
if (email) {
conditions.push(Prisma.sql`email = ${email}`);
}
if (status) {
conditions.push(Prisma.sql`status = ${status}`);
}
const where = conditions.length > 0
? Prisma.sql`WHERE ${Prisma.join(conditions, ' AND ')}`
: Prisma.empty;
const users = await prisma.$queryRaw`
SELECT * FROM "User" ${where}
`;
VULNERABLE:
const searchTerm = req.query.search;
const results = await prisma.$queryRawUnsafe(
`SELECT * FROM "Product" WHERE name LIKE '%${searchTerm}%'`
);
Attack: '; DELETE FROM "Product"; --
SAFE:
const searchTerm = req.query.search;
const results = await prisma.$queryRaw`
SELECT * FROM "Product" WHERE name LIKE ${'%' + searchTerm + '%'}
`;
VULNERABLE:
const sortColumn = req.query.sortBy;
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM "User" ORDER BY ${sortColumn}`
);
Attack: email; DROP TABLE "User"; --
SAFE:
const sortColumn = req.query.sortBy;
const allowedColumns = ['email', 'name', 'createdAt'];
if (!allowedColumns.includes(sortColumn)) {
throw new Error('Invalid sort column');
}
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM "User" ORDER BY ${sortColumn}`
);
Note: Column names cannot be parameterized, so use allowlist validation.
VULNERABLE:
const tableName = req.params.table;
const data = await prisma.$queryRawUnsafe(
`SELECT * FROM "${tableName}"`
);
Attack: User" WHERE 1=1; DROP TABLE "Session"; --
SAFE:
const tableName = req.params.table;
const allowedTables = ['User', 'Product', 'Order'];
if (!allowedTables.includes(tableName)) {
throw new Error('Invalid table name');
}
const data = await prisma.$queryRawUnsafe(
`SELECT * FROM "${tableName}"`
);
VULNERABLE:
const ids = req.body.ids.join(',');
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM "User" WHERE id IN (${ids})`
);
Attack: 1) OR 1=1; --
SAFE:
const ids = req.body.ids;
const users = await prisma.$queryRaw`
SELECT * FROM "User" WHERE id IN (${Prisma.join(ids)})
`;
VULNERABLE:
const limit = req.query.limit;
const offset = req.query.offset;
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM "User" LIMIT ${limit} OFFSET ${offset}`
);
Attack: 10; DELETE FROM "User"; --
SAFE:
const limit = parseInt(req.query.limit, 10);
const offset = parseInt(req.query.offset, 10);
if (isNaN(limit) || isNaN(offset)) {
throw new Error('Invalid pagination parameters');
}
const users = await prisma.$queryRaw`
SELECT * FROM "User" LIMIT ${limit} OFFSET ${offset}
`;
import { Prisma } from '@prisma/client';
interface SearchFilters {
email?: string;
status?: string;
minAge?: number;
}
async function searchUsers(filters: SearchFilters) {
const conditions: Prisma.Sql[] = [];
if (filters.email) {
conditions.push(Prisma.sql`email LIKE ${'%' + filters.email + '%'}`);
}
if (filters.status) {
conditions.push(Prisma.sql`status = ${filters.status}`);
}
if (filters.minAge !== undefined) {
conditions.push(Prisma.sql`age >= ${filters.minAge}`);
}
const where = conditions.length > 0
? Prisma.sql`WHERE ${Prisma.join(conditions, ' AND ')}`
: Prisma.empty;
return prisma.$queryRaw`
SELECT * FROM "User" ${where}
`;
}
type SortColumn = 'email' | 'name' | 'createdAt';
type SortOrder = 'ASC' | 'DESC';
async function getUsers(sortBy: SortColumn, order: SortOrder) {
const allowedColumns: SortColumn[] = ['email', 'name', 'createdAt'];
const allowedOrders: SortOrder[] = ['ASC', 'DESC'];
if (!allowedColumns.includes(sortBy) || !allowedOrders.includes(order)) {
throw new Error('Invalid sort parameters');
}
return prisma.$queryRawUnsafe(
`SELECT * FROM "User" ORDER BY ${sortBy} ${order}`
);
}
async function searchOrdersWithProducts(
userId?: number,
productName?: string,
minTotal?: number
) {
const conditions: Prisma.Sql[] = [];
if (userId !== undefined) {
conditions.push(Prisma.sql`o."userId" = ${userId}`);
}
if (productName) {
conditions.push(Prisma.sql`p.name LIKE ${'%' + productName + '%'}`);
}
if (minTotal !== undefined) {
conditions.push(Prisma.sql`o.total >= ${minTotal}`);
}
const where = conditions.length > 0
? Prisma.sql`WHERE ${Prisma.join(conditions, ' AND ')}`
: Prisma.empty;
return prisma.$queryRaw`
SELECT o.*, p.name as "productName"
FROM "Order" o
INNER JOIN "Product" p ON o."productId" = p.id
${where}
ORDER BY o."createdAt" DESC
`;
}
async function updateUserStatuses(
userIds: number[],
newStatus: string
) {
if (userIds.length === 0) {
return [];
}
return prisma.$queryRaw`
UPDATE "User"
SET status = ${newStatus}, "updatedAt" = NOW()
WHERE id IN (${Prisma.join(userIds)})
RETURNING *
`;
}
$queryRawUnsafe is ONLY acceptable when:
async function getTableSchema(tableName: 'User' | 'Product' | 'Order') {
return prisma.$queryRawUnsafe(`
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = '${tableName}'
`);
}
Still requires: TypeScript literal type or runtime validation against allowlist.
const status = req.query.status;
const minAge = req.query.minAge;
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM "User" WHERE status = '${status}' AND age >= ${minAge}`
);
const status = req.query.status;
const minAge = parseInt(req.query.minAge, 10);
const users = await prisma.$queryRaw`
SELECT * FROM "User"
WHERE status = ${status} AND age >= ${minAge}
`;
const maliciousEmail = "' OR '1'='1";
const user = await prisma.$queryRaw`
SELECT * FROM "User" WHERE email = ${maliciousEmail}
`;
Expected: Returns empty array (no match for literal string)
const maliciousInput = "test'; --";
const users = await prisma.$queryRaw`
SELECT * FROM "User" WHERE name = ${maliciousInput}
`;
Expected: Searches for exact string test'; --, doesn't comment out rest of query
const maliciousId = "1 UNION SELECT password FROM Admin";
const user = await prisma.$queryRaw`
SELECT * FROM "User" WHERE id = ${maliciousId}
`;
Expected: Type error or no results (string cannot match integer id column)
Use grep to find vulnerable code:
grep -r "\$queryRawUnsafe" --include="*.ts"
grep -r "queryRawUnsafe.*\${" --include="*.ts"
grep -r "queryRawUnsafe.*req\." --include="*.ts"
import { ESLint } from 'eslint';
const dangerousPatterns = [
/\$queryRawUnsafe\s*\([^)]*\$\{/,
/queryRawUnsafe\s*\([^)]*req\./,
/queryRawUnsafe\s*\([^)]*params\./,
/queryRawUnsafe\s*\([^)]*query\./,
/queryRawUnsafe\s*\([^)]*body\./,
];
$queryRawUnsafe with $queryRaw where user input existsPrisma.sql for dynamic query buildingPrisma.join() for array parametersSecurity Best Practices:
$queryRaw tagged templates for user input$queryRawUnsafe with untrusted dataPrisma.sql and Prisma.join() for dynamic queries$queryRawUnsafe usageThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.