Input validation
Input validation is the wall between the outside world and code that trusts
its arguments. It fails when validation is scattered, negative (blocklists),
or run after the value has already been used. Do it once, at the boundary,
before anything downstream reads the value.
Method
- Validate at the boundary, not deep in the stack. Check input where it
enters: the request handler, the deserializer, the file parser. Once a
value passes the wall, downstream code may assume it is well-formed.
Validation buried three calls deep leaves the first two calls exposed.
- Prefer allowlists over blocklists. Define what is legal (a set of
country codes,
^[a-z0-9_]{3,32}$ for a username, an enum of statuses)
and reject the rest. Blocklists lose: attackers find the encoding, the
Unicode homoglyph, or the case you did not enumerate.
- Canonicalize before you check. Normalize first, validate second, or
an attacker slips past with an alternate encoding. Decode
percent-encoding, resolve
.. and symlinks in paths, apply Unicode
NFC, lowercase the host. Validating the raw form then using the decoded
form is the classic path-traversal bug.
- Cap length, count, and depth. Set a maximum on every string, array,
upload, and nested-JSON depth. A 2 GB "username" or a 10,000-level nested
object is a denial of service you accepted by omission. Reject at the
limit with a clear error.
- Check type and range, then parse to a typed value. Confirm the
integer is an integer within bounds, the date parses, the enum is a
member. Convert to the real type at the boundary so downstream code
handles
int and Date, not str.
- Fail closed with a specific error. On invalid input, reject and stop;
do not coerce, truncate silently, or "fix" it. Say which field failed and
why, but never echo the raw bad value into an error page unescaped.
Checks
- Does each field have an explicit rule, or does some input reach logic only
because nothing rejected it?
- Do you canonicalize before validating everywhere a value is later decoded?
- Is there a length or size cap on every unbounded input, including request
bodies?
Boundaries
Input validation shrinks attack surface; it does not replace output encoding
or parameterized queries. A validated string can still be dangerous in a SQL
statement or an HTML page, so defend those sinks separately (see
sql-injection-defense and xss-defense). Ownership checks (does this user own
this order?) belong with authorization, not here.
1---2name: input-validation3description: Validate untrusted input at every trust boundary with allowlists, canonicalization, and hard limits so malformed data never reaches logic. Use when accepting data from requests, files, uploads, or third-party APIs.4---56# Input validation78Input validation is the wall between the outside world and code that trusts9its arguments. It fails when validation is scattered, negative (blocklists),10or run after the value has already been used. Do it once, at the boundary,11before anything downstream reads the value.1213## Method14151. **Validate at the boundary, not deep in the stack.** Check input where it16 enters: the request handler, the deserializer, the file parser. Once a17 value passes the wall, downstream code may assume it is well-formed.18 Validation buried three calls deep leaves the first two calls exposed.192. **Prefer allowlists over blocklists.** Define what is legal (a set of20 country codes, `^[a-z0-9_]{3,32}$` for a username, an enum of statuses)21 and reject the rest. Blocklists lose: attackers find the encoding, the22 Unicode homoglyph, or the case you did not enumerate.233. **Canonicalize before you check.** Normalize first, validate second, or24 an attacker slips past with an alternate encoding. Decode25 percent-encoding, resolve `..` and symlinks in paths, apply Unicode26 NFC, lowercase the host. Validating the raw form then using the decoded27 form is the classic path-traversal bug.284. **Cap length, count, and depth.** Set a maximum on every string, array,29 upload, and nested-JSON depth. A 2 GB "username" or a 10,000-level nested30 object is a denial of service you accepted by omission. Reject at the31 limit with a clear error.325. **Check type and range, then parse to a typed value.** Confirm the33 integer is an integer within bounds, the date parses, the enum is a34 member. Convert to the real type at the boundary so downstream code35 handles `int` and `Date`, not `str`.366. **Fail closed with a specific error.** On invalid input, reject and stop;37 do not coerce, truncate silently, or "fix" it. Say which field failed and38 why, but never echo the raw bad value into an error page unescaped.3940## Checks4142- Does each field have an explicit rule, or does some input reach logic only43 because nothing rejected it?44- Do you canonicalize before validating everywhere a value is later decoded?45- Is there a length or size cap on every unbounded input, including request46 bodies?4748## Boundaries4950Input validation shrinks attack surface; it does not replace output encoding51or parameterized queries. A validated string can still be dangerous in a SQL52statement or an HTML page, so defend those sinks separately (see53sql-injection-defense and xss-defense). Ownership checks (does this user own54this order?) belong with authorization, not here.