Files
gh-djankies-claude-configs-…/skills/reviewing-prisma-patterns/references/validation-checks.md
2025-11-29 18:22:25 +08:00

9.8 KiB

Validation Checks

Complete validation patterns for all 7 critical issue categories in Prisma code review.


1. SQL Injection Detection (CRITICAL - P0)

Pattern: Unsafe raw SQL usage

Detection Command:

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

Red flags:

  • $queryRawUnsafe with string concatenation
  • Prisma.raw() with template literals (non-tagged)
  • Dynamic table/column names via string interpolation
  • Filter conditions with user input interpolation

Example violations:

const users = await prisma.$queryRawUnsafe(
  `SELECT * FROM users WHERE email = '${email}'`
);

const posts = await prisma.$queryRaw(
  Prisma.raw(`SELECT * FROM posts WHERE title LIKE '%${search}%'`)
);

Remediation:

Use $queryRaw tagged template for automatic parameterization:

const users = await prisma.$queryRaw`
  SELECT * FROM users WHERE email = ${email}
`;

const posts = await prisma.$queryRaw`
  SELECT * FROM posts WHERE title LIKE ${'%' + search + '%'}
`;

Use Prisma.sql for composition:

import { Prisma } from '@prisma/client'

const emailFilter = Prisma.sql`email = ${email}`
const users = await prisma.$queryRaw`
  SELECT * FROM users WHERE ${emailFilter}
`

Impact: CRITICAL - SQL injection enables arbitrary database access, data exfiltration, deletion

Reference: @prisma-6/SECURITY-sql-injection


2. Multiple PrismaClient Instances (CRITICAL - P0)

Pattern: Multiple client instantiation

Detection Command:

grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l

Red flags:

  • Count > 1 across codebase
  • Function-scoped client creation
  • Missing global singleton pattern
  • Test files creating separate instances

Example violations:

export function getUser(id: string) {
  const prisma = new PrismaClient();
  return prisma.user.findUnique({ where: { id } });
}

export function getPost(id: string) {
  const prisma = new PrismaClient();
  return prisma.post.findUnique({ where: { id } });
}

Remediation:

Create global singleton:

import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

Import singleton everywhere:

import { prisma } from '@/lib/prisma'

export function getUser(id: string) {
  return prisma.user.findUnique({ where: { id } });
}

Impact: CRITICAL - Connection pool exhaustion, P1017 errors, production outages

Reference: @prisma-6/CLIENT-singleton-pattern


3. Missing Serverless Configuration (HIGH - P1)

Pattern: Serverless deployment without connection limits

Detection:

  1. Check for serverless context:
test -f vercel.json || test -d app/ || grep -q "lambda" package.json
  1. Check for connection_limit:
grep -rn "connection_limit=1" --include="*.env*" --include="schema.prisma" .

Red flags:

  • Serverless deployment detected (Vercel, Lambda, Cloudflare Workers)
  • No connection_limit=1 in DATABASE_URL
  • No PgBouncer configuration
  • Default pool_timeout settings

Example violation:

DATABASE_URL="postgresql://user:pass@host:5432/db"

Remediation:

Add connection_limit to DATABASE_URL:

DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10"

For Next.js on Vercel:

export const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL + '?connection_limit=1'
    }
  }
})

Impact: HIGH - Production database connection exhaustion under load

Reference: @prisma-6/CLIENT-serverless-config


4. Deprecated Buffer API (HIGH - P1)

Pattern: Prisma 6 breaking change - Buffer on Bytes fields

Detection Command:

grep -rn "Buffer\.from\|\.toString()" --include="*.ts" --include="*.js" . | grep -i "bytes\|binary"

Red flags:

  • Buffer.from() used with Bytes fields
  • .toString() called on Bytes field results
  • Missing Uint8Array conversion
  • Missing TextEncoder/TextDecoder

Example violations:

const user = await prisma.user.create({
  data: {
    avatar: Buffer.from(base64Data, 'base64')
  }
});

const avatarString = user.avatar.toString('base64');

Remediation:

Use Uint8Array instead of Buffer:

const base64ToUint8Array = (base64: string) => {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
};

const user = await prisma.user.create({
  data: {
    avatar: base64ToUint8Array(base64Data)
  }
});

Use TextEncoder/TextDecoder:

const encoder = new TextEncoder();
const decoder = new TextDecoder();

const user = await prisma.user.create({
  data: {
    content: encoder.encode('Hello')
  }
});

const text = decoder.decode(user.content);

Impact: HIGH - Type errors, runtime failures after Prisma 6 upgrade

Reference: @prisma-6/MIGRATIONS-v6-upgrade


5. Generic Error Handling (MEDIUM - P2)

Pattern: Missing Prisma error code handling

Detection Command:

grep -rn "catch.*error" --include="*.ts" --include="*.js" . | grep -L "P2002\|P2025\|PrismaClientKnownRequestError"

Red flags:

  • Generic catch (error) without P-code checking
  • No differentiation between error types
  • Exposing raw Prisma errors to clients
  • Missing unique constraint handling (P2002)
  • Missing not found handling (P2025)

Example violation:

try {
  await prisma.user.create({ data });
} catch (error) {
  throw new Error('Database error');
}

Remediation:

Check error.code for specific P-codes:

import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'

try {
  await prisma.user.create({ data })
} catch (error) {
  if (error instanceof PrismaClientKnownRequestError) {
    if (error.code === 'P2002') {
      throw new Error('User with this email already exists')
    }
    if (error.code === 'P2025') {
      throw new Error('Record not found')
    }
  }
  throw new Error('Unexpected error')
}

Impact: MEDIUM - Poor user experience, unclear error messages, potential info leakage

Reference: @prisma-6/TRANSACTIONS-error-handling, @prisma-6/SECURITY-error-exposure


6. Missing Input Validation (MEDIUM - P2)

Pattern: No validation before database operations

Detection Command:

grep -rn "prisma\.\w+\.(create\|update\|upsert)" --include="*.ts" --include="*.js" . | grep -L "parse\|validate\|schema"

Red flags:

  • Direct database operations with external input
  • No Zod/Yup/Joi schema validation
  • Type assertions without runtime checks
  • Missing email/phone/URL validation

Example violation:

export async function createUser(data: any) {
  return prisma.user.create({ data });
}

Remediation:

Add Zod schema validation:

import { z } from 'zod'

const userSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
  age: z.number().int().positive().optional()
})

export async function createUser(data: unknown) {
  const validated = userSchema.parse(data)
  return prisma.user.create({ data: validated })
}

Impact: MEDIUM - Type mismatches, invalid data in database, runtime errors

Reference: @prisma-6/SECURITY-input-validation


7. Inefficient Queries (LOW - P3)

Pattern: Performance anti-patterns

Detection Commands:

grep -rn "\.skip\|\.take" --include="*.ts" --include="*.js" .
grep -rn "prisma\.\w+\.findMany()" --include="*.ts" --include="*.js" . | grep -v "select\|include"

Red flags:

  • Offset pagination (skip/take) on large datasets (> 10k records)
  • Missing select for partial queries
  • N+1 queries (findMany in loops without include)
  • Missing indexes for frequent queries

Example violations:

Offset pagination on large dataset:

const users = await prisma.user.findMany({
  skip: page * 100,
  take: 100
});

Missing select optimization:

const users = await prisma.user.findMany();

N+1 query:

const users = await prisma.user.findMany();
for (const user of users) {
  const posts = await prisma.post.findMany({
    where: { authorId: user.id }
  });
}

Remediation:

Use cursor-based pagination:

const users = await prisma.user.findMany({
  take: 100,
  cursor: lastId ? { id: lastId } : undefined,
  orderBy: { id: 'asc' }
});

Add select for partial queries:

const users = await prisma.user.findMany({
  select: { id: true, email: true, name: true }
});

Fix N+1 with include:

const users = await prisma.user.findMany({
  include: { posts: true }
});

Impact: LOW - Slow queries, high database load, poor performance at scale

Reference: @prisma-6/QUERIES-pagination, @prisma-6/QUERIES-select-optimization


Summary Table

Check Severity Detection Common Fix Skill Reference
SQL Injection P0 $queryRawUnsafe Use $queryRaw tagged template SECURITY-sql-injection
Multiple Clients P0 Count new PrismaClient() Global singleton pattern CLIENT-singleton-pattern
Serverless Config P1 Missing connection_limit Add ?connection_limit=1 CLIENT-serverless-config
Buffer API P1 Buffer.from with Bytes Use Uint8Array MIGRATIONS-v6-upgrade
Error Handling P2 Generic catch Check P-codes (P2002, P2025) TRANSACTIONS-error-handling
Input Validation P2 No validation before DB Add Zod schema SECURITY-input-validation
Query Efficiency P3 skip/take, no select Cursor pagination, select QUERIES-pagination