Input Validation and Schema Enforcement

By Davy Rogers

Validation alone won't save you. But it makes everything else work better.

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 or e + combining accent
  • URL encoding: %2e%2e%2f = ../
  • Case: Allowlist has admin? Attacker tries ADMIN

The takeaway

Allowlists over denylists. Enforce types and lengths. Use schema libs. Reject unknown fields. Canonicalise before validating. Always server-side.

Want a professional to look at it?Get an AppSec Health Check.