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.
High because unvalidated request bodies directly enable injection attacks (CWE-20, OWASP A03) and cause data corruption from type mismatches.
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:
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.
ID: api-design.contract-quality.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:
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