Input validation is a supporting layer. Catches bad data early. Shrinks attack surface. Not a silver bullet.
SQL injection? Parameterised queries. XSS? Output encoding. Validation helps, but never rely on it as sole defence.
Client-side validation is UX. Server-side validation is security.
Strategies
Allowlists over denylists:
ALLOWED_STATUS = {"active", "inactive", "pending"}
if status not in ALLOWED_STATUS:
raise ValidationError("Invalid status")
Type enforcement:
order_id = int(request.params.get("order_id"))
SQLi string can't be a valid integer.
Length limits: Every string needs max length.
Range validation:
if quantity < 1 or quantity > 1000:
raise ValidationError("Quantity out of range")
Schema enforcement
Enforce type, format, length in one declaration.
JSON Schema:
{
"type": "object",
"required": ["name", "email"],
"properties": {
"name": { "type": "string", "maxLength": 100 },
"email": { "type": "string", "format": "email" }
},
"additionalProperties": false
}
additionalProperties: false - rejects extra fields, prevents mass assignment.
Zod:
const CreateUserSchema = z.object({
name: z.string().max(100),
email: z.string().email(),
}).strict();
Pydantic:
class CreateUser(BaseModel):
name: str = Field(max_length=100)
email: EmailStr
class Config:
extra = "forbid"
Canonicalisation
Normalise before validating:
- Unicode:
é= single code point ore+ combining accent - URL encoding:
%2e%2e%2f=../ - Case: Allowlist has
admin? Attacker triesADMIN
The takeaway
Allowlists over denylists. Enforce types and lengths. Use schema libs. Reject unknown fields. Canonicalise before validating. Always server-side.
