name: validating-input description: Validates and sanitizes incoming request data at the service boundary — schema validation, type/length/range/format checks, allowlists, unknown-field rejection, and structured 422 error responses. Use when the user asks to validate request bodies, query params, headers, or uploads, add a pydantic/zod/joi schema, prevent malformed or malicious payloads, or fix a path-traversal or oversized-input issue. Do not use for login and permission decisions (implementing-authentication, implementing-authorization) or for business rules deeper than the request boundary.
Validating Input
When to use / when NOT to use
- Use for: request-boundary validation of bodies, params, headers, and file/path inputs; schema definitions; validation error responses.
- Do NOT use for: authn/authz decisions, domain/business invariants (those live in the domain layer), or output encoding (XSS is an output concern — encode at render time).
Core rules
- Validate at the boundary with the stack's schema library — pydantic (Python), zod (TS), joi (Node legacy). Hand-rolled
ifchains drift. - Every field gets type + length + range + format. Unbounded strings and numbers are bugs.
- ✅
name: str, min_length=1, max_length=200 - ❌
name: str— a 10 MB name is now valid
- ✅
- Allowlist over blocklist. Enumerate what is valid (enum, regex anchored
^...$, closed set); never try to enumerate evil. - Reject unknown fields (
extra="forbid"/.strict()). Silent extras become mass-assignment holes. - Canonicalize before validating: trim whitespace, NFC-normalize unicode, lowercase emails — then check. Validating pre-canonical data lets
admin≠adminbypasses through. - Client-side validation is UX only. The server re-validates everything, always.
- Fail with a structured 422 naming every invalid field and why — one pass, not first-error-only.
- Path and file inputs: resolve to an absolute path and require it start with the allowed base directory; validate content-type by sniffing magic bytes, not the filename; cap upload size before reading the body.
Workflow
- Define one schema per endpoint request (body, query, path params) with rule-2 constraints on every field.
- Enable unknown-field rejection and canonicalization hooks (rules 4–5).
- Wire the validation-error handler to the structured 422 format (rule 7).
- Add path/upload guards where files are involved (rule 8).
- Validate the validator: send a malformed payload per field class (wrong type, over-length, out-of-range, unknown field, path
../../etc/passwd) and confirm each yields a 422/400 naming the field — never a 500. Fix and repeat until all pass.
Edge cases & failure modes
- Numbers as strings (
"42") → decide once: coerce (default for query params) or reject (default for JSON bodies); stay consistent. - Empty vs missing vs null → distinguish explicitly in the schema; PATCH semantics need "absent = unchanged".
- Arrays → cap length (e.g. ≤1000 items) and validate every element, not just the container.
- Validation library throws on deeply nested payloads → cap request size and nesting depth at the web-server layer first.
References
Copy-paste patterns and gotchas: see references/patterns.md.