# Mutating routes validate input before processing

- **Pattern:** `ab-000276` (`ai-slop-security-theater.unenforced-validation.mutating-routes-validate-input`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000276
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000276)

## Why it matters

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.

## Severity rationale

High because unvalidated POST/PUT/PATCH/DELETE bodies enable mass-assignment attacks and arbitrary data injection into the database on every mutating endpoint.

## Remediation

Validate the request body before any database write. The validation must appear above the first `prisma.*` call in the handler's execution path.

```ts
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.

## Detection

- **ID:** `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:

  ```ts
  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 })
  }
  ```

## External references

- cwe CWE-20
- cwe CWE-915
- owasp A03
- owasp A04

Taxons: placeholder-hygiene, injection-and-input-trust

HTML version: https://auditbuffet.com/patterns/ab-000276
