Skip to content

Input Validation & Output Encoding

  • Treat all input from outside your trust boundary as untrusted: request bodies, query params, headers, cookies, file uploads, webhook payloads, and data from third-party APIs.
  • Validate at the boundary (as early as possible), and encode/escape at the point of use (as close to the sink as possible). Validation and encoding solve different problems and neither substitutes for the other.
  • Prefer allow-lists (accept known-good patterns) over deny-lists (block known-bad patterns).
  • SQL — use parameterized queries or an ORM’s query builder. Never build SQL via string concatenation with user input.
  • Command execution — avoid shelling out with user-controlled input. If unavoidable, use an API that takes arguments as an array rather than a single interpolated string, and validate strictly.
  • NoSQL / query languages — the same rule applies: use the driver’s parameterized/query-builder API, don’t interpolate.
  • Use your framework’s default templating/escaping (React, Vue, and most modern frameworks escape by default) — don’t disable it (dangerouslySetInnerHTML, v-html, |safe) unless the content is trusted and sanitized.
  • If you must render user-supplied HTML, sanitize it with a maintained library (e.g. DOMPurify) using an allow-list of tags/attributes.
  • Set a Content-Security-Policy header as defense in depth.
  • Validate file type by content (magic bytes), not just the extension or the client-supplied MIME type.
  • Store uploads outside the web root or in object storage, and serve them from a separate domain/subdomain if they can contain user-controlled HTML/JS.
  • Enforce a maximum file size and scan for malware where feasible.
  • Avoid deserializing untrusted data with formats/libraries that support arbitrary object instantiation or code execution (e.g. Python pickle, PHP unserialize, Java native serialization) unless the source is fully trusted.
  • Prefer plain data formats (JSON, protobuf with a fixed schema) for untrusted input.