User input is validated server-side, not just client-side
Why it matters
A route handler that does const body = await request.json() and passes the result straight to Prisma, Supabase, or a raw SQL call is trusting whatever the client sends — including type mismatches, unexpected fields, oversized payloads, and deliberately malformed structures. OWASP 2021 A03 Injection remains the canonical cause of most mega-breaches, and client-side-only validation means an attacker who pipes to your API via curl bypasses every check you wrote in the browser. Without a schema parse in front, attackers can submit extra fields that the ORM happily writes (mass-assignment), coerce types to trigger errors that leak stack traces, or send payloads large enough to DoS the JSON parser. AI coding tools consistently skip this step — they'll generate a handler that destructures the request body, use the fields immediately, and move on, because the happy path works and validation adds visible line count. Zod / Yup / Valibot / Pydantic aren't hard to add; they just get forgotten.
Severity rationale
High when any database-writing handler skips validation because mass-assignment and type-coercion bugs follow directly; elevated above medium because a single unvalidated write endpoint is typically enough to compromise data integrity.
Remediation
Define a Zod schema and parse at the top of every handler:
const Body = z.object({ email: z.string().email(), name: z.string().min(1).max(120) })
const body = Body.parse(await request.json())
// use body.email, body.name
Deeper remediation guidance and cross-reference coverage for this check lives in the security-hardening Pro audit — run that after applying this fix for a more exhaustive pass on the same topic.
Detection
- ID:
input-validated-server-side - Severity:
high - What to look for: Enumerate every API route handler / route handler / server action / form action in the project. For each, check whether the request body, query params, or form data are parsed server-side through a recognized validation library — Zod, Yup, Joi, Valibot, ArkType, class-validator, Ajv, Pydantic, marshmallow — called INSIDE the route handler (not just imported in client components), OR manual parsing with explicit per-field checks on type, length, enum membership, and whitelist rules. Client-side HTML5 attributes (
required,maxlength,pattern="",type="email") do NOT count as validation since any attacker can bypass them by hitting the API directly with curl. Count handlers with server-side validation vs. handlers without. - Pass criteria: At least 80% of handlers that consume request data validate it server-side through a recognized library before use, AND no handler that writes to a database uses unvalidated input.
- Fail criteria: Less than 80% server-side validation rate, OR any database-writing handler skips validation, OR validation is only present as HTML5 attributes on the client-side form with nothing gating the route.
- Skip (N/A) when: No API routes / server actions / route handlers detected (project is purely static).
- Do NOT pass when: Validation exists but runs AFTER the database write — the order matters. Also do not pass when only some fields are validated and the rest pass through as-is. Also do not pass when "validation" means only client-side HTML5 form attributes (
type="email",required,pattern) with no server-side check — an attacker who pipes to your API via curl bypasses every one of those. - Report even on pass:
"Found N handlers that consume request data; M validate via {library list}. Validation rate: M/N = X%." - Detail on fail:
"4 of 12 API handlers parse request body without Zod/Yup validation; 1 of those writes to the database (app/api/comments/route.ts)". - Remediation: Define a Zod schema and parse at the top of every handler:
const Body = z.object({ email: z.string().email(), name: z.string().min(1).max(120) }) const body = Body.parse(await request.json()) // use body.email, body.name
Taxons
History
- 2026-04-22·v1.0.0·Initial import from project-snapshot via Phase 8.1 bundling·by phase-8-1-bundle-project-snapshot
- 2026-04-22·v2.0.0·Phase 9 consequence-first restructure — moved to new section slug, added news/incident references, severity reviewed.·by phase-9-stack-scan-v3