Never trust input. Every field arriving from a client, partner API, or message queue is a potential attacker — until your server has parsed, type-checked, and constrained it.
← Back to Server Side| Layer | Responsibility | Tools |
|---|---|---|
| Network edge / WAF | Shape attacks: huge bodies, malformed JSON, known bad payloads. | Cloudflare, AWS WAF, ModSecurity. |
| API contract | Type, required-ness, enum values, format (UUID, email, ISO date). | Zod, Pydantic, Joi, class-validator, FluentValidation, JSON Schema. |
| Domain / business rules | "End date after start date," "discount ≤ subtotal," "user owns this resource." | Hand-written invariants in the domain layer. |
| Persistence | NOT NULL, CHECK, UNIQUE, foreign keys — last line of defense. | Database constraints. |
Each layer catches what the layer above missed. Skipping DB constraints because "the app validates it" is how data corruption ships.
Define a schema, infer the type. Parsing fails loudly with structured errors.
The de-facto standard; powers FastAPI's request/response models.
Declarative rules, integrated with model binding.
JSR-380 — annotate fields with @NotNull, @Email, @Size.
Struct tags drive validation rules.
Single source of truth — generate validators, clients, and docs from one spec.
Most "sanitization" bugs are actually output-encoding bugs. The same string is safe in one context and dangerous in another. Encode at the moment you cross the boundary, never earlier.
< > & " '. Use templating that escapes by default; use DOMPurify for rich-text input.execFile-style APIs that take an arg array, never exec with a string.encodeURIComponent for path/query, allow-list scheme & host for redirects..., anchor under a known root.Reject NaN, Infinity, and negatives where they don't make sense. Use decimal types (Postgres numeric, Java BigDecimal) for currency — never float. Pin currency code; a missing one defaults to nothing safe.
Accept ISO-8601 only. Store UTC; convert at the edge. Reject dates outside a sensible window — a birthday in the year 3024 is almost certainly an attack or a bug.
safe_load / load(SafeConstructor) — default loaders can instantiate arbitrary types.__proto__, constructor, prototype in JS object merges.Don't bind a request body straight onto your DB model. The client may send { isAdmin: true }. Use an explicit allow-list — DTOs, "strong parameters," permit(), or schema .strip() / .strict().