A POST handler that pipes req.body directly into prisma.user.create({ data: body }) accepts whatever JSON the client sends — extra fields, injected relations, overridden IDs. CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes) and OWASP A03 describe this exactly: an attacker can escalate privileges by setting role: 'admin' or inject unexpected columns. TypeScript types don't run at runtime; only an explicit .parse() call rejects malformed or malicious input before the write commits.
High because unvalidated POST/PUT/PATCH/DELETE bodies enable mass-assignment attacks and arbitrary data injection into the database on every mutating endpoint.
Validate the request body before any database write. The validation must appear above the first prisma.* call in the handler's execution path.
import { z } from 'zod'
const Body = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
})
export async function POST(req: Request) {
const body = await req.json()
const data = Body.parse(body) // unknown fields stripped; invalid shapes rejected
const user = await prisma.user.create({ data })
return Response.json(user, { status: 201 })
}
If you're using tRPC, the .input() call handles this. For Server Actions with next-safe-action, the schema option covers it. The key invariant: no path through the handler should reach a DB write without first passing through a schema parse.
ID: ai-slop-security-theater.unenforced-validation.mutating-routes-validate-input
Severity: high
What to look for: Walk all mutating handlers (API handlers exporting POST, PUT, PATCH, or DELETE). Count all mutating handlers found. For each, count whether the handler body contains at least 1 of these validation invocation patterns BEFORE the first database write call (prisma.X.create, prisma.X.update, prisma.X.upsert, prisma.X.delete, db.insert(, db.update(, db.delete(, Model.create(, raw SQL INSERT/UPDATE/DELETE, fetch to a third-party with req.body payload): a .parse( call, a .safeParse( call, a Zod .parseAsync(, a Joi .validate(, a Yup .validate(, a Valibot parse(, a Superstruct assert(, a class-validator validate(, OR a middleware-applied resolver/validator from a known framework helper (zValidator, validator(...), safeAction, parseWithZod).
Pass criteria: 100% of mutating handlers either invoke validation before the first database write OR have no database write at all. Report: "X mutating handlers inspected, Y validate before DB write, 0 unvalidated."
Fail criteria: At least 1 mutating handler performs a database write without first validating input.
Do NOT pass when: A handler reads req.body and passes it directly to prisma.X.create({ data: body }) without parsing through a schema. Even if Prisma's TypeScript types catch some shape errors, the runtime accepts whatever JSON the client sends.
Skip (N/A) when: Project has 0 API handler files OR no mutating handlers exist.
Detail on fail: "2 mutating handlers without input validation: app/api/users/route.ts POST passes req.body directly to prisma.user.create, app/api/posts/route.ts PUT updates with unvalidated body"
Remediation: Every mutating endpoint is an attack surface. Validate input before any side effect:
import { z } from 'zod'
const Body = z.object({ email: z.string().email(), name: z.string().min(1) })
export async function POST(req: Request) {
const body = await req.json()
const data = Body.parse(body) // throws 400 if invalid
const user = await prisma.user.create({ data })
return Response.json(user, { status: 201 })
}