Hermes Wiki
Developer/Security/InputValidation/Fundamentals/allowlist-vs-denylist-input-validation

Allowlist vs. Denylist Input Validation

Concept

Input validation is the practice of checking data at the boundary — before it reaches business logic, a database query, or an output-rendering path — and rejecting anything that doesn't conform to what's expected. It's the root defense against the entire class of injection vulnerabilities (SQL injection, XSS, command injection, path traversal): all of them work by getting attacker-controlled data to be interpreted as code or as a structural part of a query/command instead of as inert data, and validation at the boundary is what stops malformed input from ever reaching the point where that reinterpretation could happen.

There are two structurally opposite ways to define "conforms to what's expected," and the choice between them matters more than almost any other detail of how validation is implemented.

Denylist (blocklist) validation enumerates known-bad patterns and rejects input matching any of them — block the literal string <script>, block ' OR '1'='1, block a list of dangerous characters. The fundamental problem is that a denylist can only block patterns its author already thought of. Attackers routinely evade denylists trivially: a mixed-case <ScRiPt> bypasses a case-sensitive block on <script>; a URL-encoded or double-encoded payload bypasses a filter checking the raw string; a semantically-equivalent SQL construct bypasses a filter tuned to one specific injection syntax. Every denylist is a snapshot of the attacks its author anticipated, not a definition of what's actually safe — it's inherently reactive and incomplete by construction.

Allowlist (whitelist) validation does the opposite: define the set of known-good values or patterns, and reject everything that doesn't match. A US state field only accepts one of the 50 valid two-letter codes; a username field only accepts a fixed character set and length range; a numeric ID field only accepts digits. Because the allowlist defines what's acceptable rather than what's dangerous, it doesn't need to anticipate every possible attack variant — anything outside the known-good shape is rejected by default, including attack patterns nobody has thought of yet. This is why OWASP's Input Validation Cheat Sheet treats allowlisting as the recommended minimum approach, not one option among equals.

The two aren't strictly exclusive in practice: a denylist check for well-known dangerous patterns can be layered on top of an allowlist as an extra defense-in-depth signal (catching an attack pattern even if it somehow slipped through unusual allowlist logic), but it's explicitly a supplement, never a substitute for the allowlist doing the real rejection work.

Tradeoffs

Approach Coverage against novel attacks Maintenance burden Where it fits well Where it breaks down
Denylist alone Weak — only blocks patterns the author already anticipated Grows forever — every new bypass technique needs a new rule Free-text fields where "known good" can't be enumerated (e.g. a comment body) — as a coarse extra layer, not the primary defense Any field where the valid shape is actually enumerable — denylisting there is strictly worse than allowlisting
Allowlist alone Strong — anything outside the known-good shape is rejected by default, attack pattern or not Front-loaded — defining the valid shape once, then low ongoing cost Structured fields: enums, IDs, dates, codes, anything with an enumerable or pattern-describable valid form Genuinely free-form input (rich text, file uploads, natural-language fields) where "known good" isn't a fixed shape
Allowlist + supplementary denylist Strongest practical combination for high-risk free-text fields Allowlist's low baseline cost plus denylist's ongoing pattern updates High-value free-text inputs (search boxes, comment fields on sensitive systems) needing defense in depth Still weaker than allowlisting for anything that could have been made structured instead

When to use / when not to

  • Use allowlist validation as the default for every field that has an enumerable or pattern-describable valid shape — which is most application input: IDs, enum-like fields, dates, emails, phone numbers, structured codes. This is the large majority of real-world input fields, even though free-text examples dominate the conversation about validation.
  • Reach for a denylist only as a supplementary layer on genuinely free-form fields where an allowlist can't describe the valid shape (rich text, natural-language search, comment bodies) — and treat it as catching some attacks, not all of them.
  • Validate at the boundary — the layer where input first enters the system (a schema/model layer like Pydantic, a request-validation middleware) — and reject before the data reaches business logic or a query, not after.
  • Don't rely on client-side validation for security purposes at all — it's a UX convenience an attacker can trivially bypass by calling the API directly; server-side validation at the boundary is the actual control.

Common pitfall

Reaching for a denylist first because it feels more intuitive — "block the bad stuff" — without recognizing that this requires correctly anticipating every variant of an attack, including ones that don't exist yet. This is exactly backwards from how the field with an enumerable valid shape should be validated: for a US-state field, a two-character code from a fixed list of 50 is trivial to allowlist and closes off the entire attack surface at once, whereas trying to denylist "not a valid state code" requires reasoning about every conceivable malformed input rather than just the one known-good shape.

Engineering Lens

The design-review question for any input field isn't "do we validate this" — it's "can this field's valid values be enumerated or pattern-described, and if so, why isn't it allowlisted." A denylist-only defense on a field that could have been allowlisted is a standing bet that the author has already thought of every attack variant an adversary will ever try — a bet that loses eventually, and loses silently until it's exploited.

Sources

Hermes Wiki