Skip to content
Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.

Input Validation Rules

Validating untrusted input at every trust boundary: server-side enforcement, allowlists over blocklists, schema and range checks, canonicalization, and safe error messages.

Mby @markdownersPublished August 21, 2026 · ~4 min read

0 downloads · Used by 0 stacks

Every input crossing a trust boundary — a request body, a query param, a file, a message from another service — is attacker-controlled until validated. Client-side validation is UX, not security; the server must independently re-validate everything, with no exceptions for "the UI already checks this."

Trust boundaries

  • Validate at every boundary where data enters a new trust context: HTTP request handlers, message queue consumers, webhook receivers, CLI args, environment variable parsing, and data read back from your own database if it could have been written by another, less-trusted code path.
  • Never assume a value is safe because it came from your own frontend, your own database, or an internal service — an internal service today can become externally reachable tomorrow, and a value written to the DB by one code path can be read by another that assumes stricter invariants.
  • Re-validate on every mutation, not just on create — an update endpoint that skips validation because "the row already passed validation once" lets an attacker corrupt a previously-valid record field by field.

Allowlist over blocklist

  • Define what is permitted (a fixed set of enum values, a regex anchored on both ends, a whitelist of allowed characters) rather than trying to enumerate what's forbidden — blocklists are provably incomplete; there is always another encoding, another special character, another bypass the blocklist author didn't think of.
  • For free-form text, allowlist by structure (max length, character class, no control characters) rather than trying to blocklist specific dangerous substrings — substring blocklists are trivially defeated by encoding, casing, or splitting the substring across an allowed boundary.
  • For file types, MIME types, and URL schemes, allowlist the exact set your application handles (^https?://, image/png|image/jpeg) — never accept "anything not explicitly blocked."

Schema validation

  • Validate type, range, length, and format together as one schema (Zod or equivalent) per endpoint, not as scattered ad hoc if checks — a schema is auditable in one place and can't silently drift out of sync the way hand-rolled checks across a codebase do.
  • Enforce both a minimum and a maximum for every bounded field (string length, numeric range, array size, upload size) — an unbounded maximum is a resource-exhaustion vector even when the field is otherwise "valid."
  • Validate nested structures fully (array element shape, object property types) — a schema that checks the outer envelope but leaves any/unknown on a nested field just moves the trust boundary one level deeper without closing it.
  • Keep validation schemas as the single source of truth mirrored against DB constraints (CHECK constraints, column types) — a Zod schema and a DB constraint that silently drift apart means either invalid data reaches the DB or valid data is wrongly rejected.

Canonicalization

  • Canonicalize before validating: decode URL/HTML entities, normalize Unicode (NFC), resolve relative path segments (../), and lowercase where case-insensitivity is the real rule — validating an un-canonicalized value lets an attacker encode around a filter that would catch the canonical form.
  • Canonicalize exactly once, immediately before validation, and validate the canonical form — repeated or inconsistent canonicalization at different layers reintroduces the exact bypass you were trying to close.
  • Be explicit about which encoding is authoritative (UTF-8) and reject input that fails to decode cleanly rather than replacing invalid bytes and continuing.

Reject, don't fix

  • Reject invalid input with a clear error; never silently "fix" it (truncating an over-length string, stripping unexpected characters, coercing a malformed value to a default) and continue processing — silent correction hides bugs from the caller and can turn a validation gap into a security gap when the "fixed" value is still attacker-influenced.
  • The one exception is well-defined canonicalization (trimming whitespace, case normalization) applied consistently as part of the schema itself, not ad hoc cleanup sprinkled into business logic.
  • Fail closed: on any validation error, ambiguous input, or unexpected exception during validation, reject the request — never fall through to a default-allow path.

Error messages

  • Return error messages that identify which field failed and why in terms the caller can act on ("email must be a valid address"), without leaking internal details: stack traces, SQL fragments, file paths, library versions, or the exact regex/schema used.
  • Never let a validation error message echo back other users' data or reveal whether a resource exists when the caller shouldn't be able to tell (e.g. distinguishing "invalid format" from "already taken" on a field where enumeration is a real risk).
  • Log the full validation failure (including the rejected payload, scrubbed of secrets) server-side for debugging, even though the client only sees the sanitized message.
Badge

Link back to this module from your own README.

Get it on Markdowners
[![Get it on Markdowners](https://markdowners.com/mdstack-badge.svg)](https://markdowners.com/m/markdowners/input-validation-rules)

Comments (0)

Sign in to comment. Sign in

No comments yet. Be the first to add one.

Discussions about this module

No discussions about this module yet.

Start a discussion