Unvalidated API input is the foundation of injection attacks, mass assignment vulnerabilities, and type confusion bugs. OWASP API Security Top 10 2023 API6 (Unrestricted Access to Sensitive Business Flows) and API10 (Unsafe Consumption of APIs) both require that input arrives in the expected shape and type. CWE-20 (Improper Input Validation) is behind SQL injection, XSS, command injection, and business logic bypasses. When an endpoint accepts arbitrary JSON and passes it to a database call or business function, attackers supply crafted payloads — extra fields, wrong types, boundary values — to trigger unintended behavior. Schema validation is the first defense that makes all downstream trust explicit.
High because unvalidated input enables injection attacks and business logic bypass, with a direct path from HTTP request to database or system command.
Parse every incoming request body and query string through a Zod schema before touching the data. Return 400 with structured errors on failure so callers get actionable feedback.
// src/app/api/posts/route.ts
import { z } from 'zod'
const createPostSchema = z.object({
title: z.string().min(1).max(200),
body: z.string().min(1).max(10_000),
published: z.boolean().default(false)
})
export async function POST(req: Request) {
const parsed = createPostSchema.safeParse(await req.json())
if (!parsed.success) {
return Response.json({ errors: parsed.error.flatten() }, { status: 400 })
}
const post = await db.post.create({ data: { ...parsed.data, authorId: user.id } })
return Response.json(post, { status: 201 })
}
Include query parameter schemas too — z.object({ page: z.coerce.number().min(1) }).parse(Object.fromEntries(url.searchParams)).
ID: api-security.input-validation.request-validation
Severity: high
What to look for: Enumerate every relevant item. Check whether API routes validate incoming request bodies and query parameters before using them. Look for schema validation using Zod, Joi, yup, or manual validation checks. Verify that invalid requests return 400 Bad Request.
Pass criteria: At least 1 of the following conditions is met. All endpoints that accept user input validate the request body and query parameters against a defined schema. Invalid data is rejected with a 400 or 422 response.
Fail criteria: Endpoints accept and process request data without validation, or validation is incomplete (e.g., only body is validated but not query parameters).
Skip (N/A) when: The API only accepts GET requests with no user input.
Detail on fail: Name endpoints without validation. Example: "POST /api/users accepts arbitrary request bodies without schema validation" or "Query parameters validated but request body is not checked"
Remediation: Implement request validation using a schema library:
import { z } from 'zod'
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).max(150)
})
export default async (req, res) => {
const result = userSchema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({ errors: result.error.errors })
}
const user = result.data
// Process valid data
}