Input Validation Patterns
Tiffin's order form limits quantity to 1–20 with a nice dropdown. Then someone sends quantity=-5 straight to the API with a command-line tool, and gets a refund-shaped order. The dropdown never ran. Every input that crosses into your system needs checking where the attacker cannot skip it.
The rule
Validate on the server, at the boundary, against what you expect.
- On the server. Browser checks are for user comfort. Anyone can send a request without a browser.
- At the boundary. Check input the moment it enters: API request, file upload, message from a queue, webhook. Then the rest of the code can trust its types.
- Against what you expect. An allow-list describes what is valid ("an integer from 1 to 20"). A deny-list tries to name what is bad ("no
<script>") and always misses something.
Check two layers:
- Syntactic: type, length, format. Is
delivery_datea real date? - Semantic: business rules. Is it not in the past? Does
dish_idexist on today's menu?
When input fails, reject it with a clear error. Do not quietly "fix" it: silently trimming or rewriting input hides attacks and bugs.
A schema library makes this declarative:
from datetime import date
from pydantic import BaseModel, Field, field_validator
class OrderIn(BaseModel):
dish_id: int = Field(gt=0)
quantity: int = Field(ge=1, le=20)
delivery_date: date
note: str = Field(default="", max_length=200)
@field_validator("delivery_date")
@classmethod
def not_in_past(cls, d: date) -> date:
if d < date.today():
raise ValueError("delivery date is in the past")
return dValidation is not a substitute for parameterized queries or output encoding. A note that passes every rule may still contain quotes and angle brackets. Validation narrows what gets in; the other defences handle it safely where it is used.
The shortcut
For each field, fill in one sentence: "It must be a ___, between ___ and ___, and it must make sense because ___." If you cannot finish the sentence, you do not know what valid means yet.
quantity = -5
"An integer between 1 and 20." Fails. Reject with 422.
Drills
Answer fast — trust the rule.
Tiffin validates quantity only in the browser's JavaScript. What is the problem?
Which rule is an allow-list?
"The delivery date must not be in the past" is what kind of check?
A note passes validation and contains O'Brien <3. What should happen next?
Remember
- Validate on the server, at the boundary, against what you expect.
- Finish the sentence: a ___, between ___ and ___, that makes sense because ___.
- Reject, don't silently fix; validation never replaces parameters or encoding.
