# Request bodies validated against schema

- **Pattern:** `ab-000360` (`api-design.contract-quality.schema-validation`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000360
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000360)

## Why it matters

Unvalidated request bodies are the direct path to CWE-20 (Improper Input Validation) and OWASP A03:2021 (Injection). A handler that destructures `req.body` without parsing trusts whatever the client sends — malformed types, missing required fields, oversized strings, or injected payloads. Even without injection intent, unvalidated inputs cause runtime exceptions that leak stack traces, corrupt database records with wrong types, and produce 500 errors that hide the real cause from developers. iso-25010:2011 security.integrity requires that data entering the system be validated at the boundary.

## Severity rationale

High because unvalidated request bodies directly enable injection attacks (CWE-20, OWASP A03) and cause data corruption from type mismatches.

## Remediation

Add schema validation to every endpoint that accepts a body. Use Zod's `safeParse` so validation failures return structured 400 errors rather than unhandled exceptions:

```ts
import { z } from 'zod'

const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(['USER', 'ADMIN']).default('USER'),
})

const parsed = CreateUserSchema.safeParse(await req.json())
if (!parsed.success) {
  return Response.json(
    { error: { code: 'VALIDATION_ERROR', details: parsed.error.issues } },
    { status: 400 }
  )
}
const { name, email, role } = parsed.data
```

Validation errors must include field-level detail (`details`) so consumers can fix their requests without guessing.

## Detection

- **ID:** `schema-validation`
- **Severity:** `high`
- **What to look for:** Examine POST, PUT, and PATCH handlers. Check whether incoming request bodies are validated before processing. Look for: Zod `.parse()`/`.safeParse()`, Joi `.validate()`, Yup `.validate()`, class-validator decorators, Pydantic model parsing, Go struct tags with a validator, or manual validation logic. Check that validation happens BEFORE the handler processes the request (not after). Also check: do validation errors return a structured error response with field-level details?
- **Pass criteria:** Count all endpoints that accept request bodies (POST, PUT, PATCH). All endpoints that accept request bodies validate the input using a schema validation library or framework feature. Validation errors return structured responses identifying which fields failed and why. At least 100% of body-accepting endpoints must have validation.
- **Fail criteria:** One or more endpoints that accept request bodies perform no validation -- they destructure or access `req.body` fields directly without checking types, required fields, or format constraints. Or validation exists but errors return generic messages without field-level detail. Quote the handler code pattern showing missing validation (e.g., `const { name } = req.body` with no `.parse()` or `.validate()` call).
- **Skip (N/A) when:** No endpoints accept request bodies (GET-only API). Also skip for GraphQL (type system provides built-in validation) and gRPC (Protobuf provides built-in deserialization).
- **Detail on fail:** Identify which endpoints lack validation (e.g., "POST /api/users destructures req.body.name and req.body.email without validation. POST /api/orders accepts body with no type checking. 4 of 6 POST endpoints have no request validation."). Max 500 chars.
- **Remediation:** Add request validation to every endpoint that accepts a body. Zod is the most popular choice for TypeScript APIs:

  ```ts
  import { z } from 'zod'

  const CreateUserSchema = z.object({
    name: z.string().min(1).max(100),
    email: z.string().email(),
    role: z.enum(['USER', 'ADMIN']).default('USER'),
  })

  // In handler:
  const parsed = CreateUserSchema.safeParse(await req.json())
  if (!parsed.success) {
    return Response.json(
      { error: { code: 'VALIDATION_ERROR', details: parsed.error.issues } },
      { status: 400 }
    )
  }
  const { name, email, role } = parsed.data
  ```

## External references

- cwe CWE-20
- owasp A03
- iso-25010 security.integrity

Taxons: injection-and-input-trust

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