---
# === IDENTITY ===
id: software/security/input-validation/2026
canonical_question: "What are the best input validation patterns?"
aliases:
  - "input validation best practices"
  - "server-side input validation"
  - "allowlist vs blocklist validation"
  - "data validation security patterns"
  - "CWE-20 input validation"
  - "Pydantic Zod Joi validation"
  - "how to validate user input securely"
  - "syntactic vs semantic validation"
entity_type: software_reference
domain: software > security > Input Validation
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.94
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Pydantic v2 (2023) broke v1 validator API; Zod 3.x stable since 2022; Jakarta Validation 3.1 (2024)"
  next_review: 2026-08-26
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "ALWAYS validate on the server side -- client-side validation is for UX only and is trivially bypassed"
  - "Use allowlist (accept known good) over blocklist (reject known bad) -- blocklists are incomplete by definition"
  - "Canonicalize and decode input BEFORE validation -- double-encoding bypasses validators"
  - "Validate BOTH syntactic correctness (format, type, length) AND semantic correctness (business rules, cross-field consistency)"
  - "NEVER use regex as the sole validation mechanism -- combine with type coercion, length limits, and range checks"
  - "Pin validation library versions -- breaking changes in Pydantic v1→v2, Joi v16→v17 changed APIs"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need to prevent XSS specifically (output encoding, CSP)"
    use_instead: "software/security/xss-prevention/2026"
  - condition: "Need to prevent SQL injection specifically (parameterized queries)"
    use_instead: "software/security/sql-injection-prevention/2026"
  - condition: "Need file upload validation only"
    use_instead: "software/security/file-upload-validation/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "language"
    question: "What programming language or framework are you using?"
    type: choice
    options: ["Python", "TypeScript/JavaScript", "Java/Kotlin", "Go", "C#/.NET", "Other"]
  - key: "input_source"
    question: "Where does the input come from?"
    type: choice
    options: ["HTTP request body", "URL parameters", "Form fields", "File upload", "API payload", "CLI arguments"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/input-validation/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention Guide"
    - id: "software/security/sql-injection-prevention/2026"
      label: "SQL Injection Prevention"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention (output encoding, not input validation)"
    - id: "software/security/csrf-prevention/2026"
      label: "CSRF Prevention (token-based, not input validation)"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Input Validation Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
    type: community_resource
    published: 2024-06-15
    reliability: authoritative
  - id: src2
    title: "CWE-20: Improper Input Validation"
    author: MITRE
    url: https://cwe.mitre.org/data/definitions/20.html
    type: official_docs
    published: 2024-12-01
    reliability: authoritative
  - id: src3
    title: "Pydantic Validators Documentation"
    author: Pydantic
    url: https://docs.pydantic.dev/latest/concepts/validators/
    type: official_docs
    published: 2025-01-15
    reliability: high
  - id: src4
    title: "Zod: TypeScript-first Schema Validation"
    author: Colin McDonnell
    url: https://zod.dev/
    type: official_docs
    published: 2025-06-01
    reliability: high
  - id: src5
    title: "Jakarta Bean Validation Specification"
    author: Eclipse Foundation
    url: https://beanvalidation.org/
    type: official_docs
    published: 2024-03-01
    reliability: high
  - id: src6
    title: "Go Playground Validator v10"
    author: Go Playground
    url: https://github.com/go-playground/validator
    type: official_docs
    published: 2025-01-01
    reliability: high
  - id: src7
    title: "C3: Validate all Input & Handle Exceptions"
    author: OWASP Foundation
    url: https://top10proactive.owasp.org/the-top-10/c3-validate-input-and-handle-exceptions/
    type: community_resource
    published: 2024-09-01
    reliability: authoritative
---

# Input Validation: Secure Data Validation Patterns

## TL;DR

- **Bottom line**: Always validate input server-side using allowlist (accept known good) validation with strict type coercion, length limits, and semantic checks -- blocklist approaches are fundamentally incomplete and bypassable.
- **Key tool/command**: `z.string().email().max(254)` (Zod/TS), `EmailStr` + `Field(max_length=254)` (Pydantic/Python), `@Email @Size(max=254)` (Jakarta/Java), `validate:"required,email,max=254"` (Go validator).
- **Watch out for**: Validating on the client side only -- any JavaScript validation is bypassed in seconds with browser DevTools or a proxy.
- **Works with**: All server-side languages and frameworks. Libraries: Pydantic v2+ (Python), Zod 3.x (TypeScript), Joi 17.x (Node.js), Jakarta Validation 3.1 (Java), go-playground/validator v10 (Go).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- ALWAYS validate on the server side -- client-side validation is for UX only and is trivially bypassed
- Use allowlist (accept known good) over blocklist (reject known bad) -- blocklists are incomplete by definition
- Canonicalize and decode input BEFORE validation -- double-encoding bypasses validators
- Validate BOTH syntactic correctness (format, type, length) AND semantic correctness (business rules, cross-field consistency)
- NEVER use regex as the sole validation mechanism -- combine with type coercion, length limits, and range checks
- Pin validation library versions -- breaking changes in Pydantic v1 to v2, Joi v16 to v17 changed APIs

## Quick Reference

| # | Validation Pattern | Description | When to Use | Risk if Skipped |
|---|---|---|---|---|
| 1 | Allowlist (accept known good) | Define exactly what is valid; reject everything else | All discrete inputs (enums, choices, known formats) | Injection attacks bypass incomplete checks |
| 2 | Type coercion | Convert input to expected type (int, float, bool, date) | Numeric fields, dates, booleans | Type confusion, integer overflow, NaN propagation |
| 3 | Length limits | Enforce min/max length on strings, arrays, file sizes | All string and collection inputs | Buffer overflow, DoS via oversized payloads, ReDoS |
| 4 | Range checks | Validate numeric values fall within expected bounds | Prices, quantities, ages, coordinates | Negative quantities, overflow, business logic abuse |
| 5 | Regex patterns | Match input against format patterns (anchored: `^...$`) | Emails, phone numbers, postal codes, IDs | Malformed data, injection via unexpected characters |
| 6 | Encoding/normalization | Canonicalize Unicode, decode URL-encoding before validation | All text inputs, especially multi-byte | Double-encoding bypasses, homoglyph attacks |
| 7 | Schema validation | Validate entire request structure (JSON Schema, Zod, Pydantic) | API payloads, complex nested objects | Missing fields, extra fields, type mismatches |
| 8 | Semantic validation | Cross-field consistency (start < end, total = sum of parts) | Business logic, date ranges, financial data | Logic bugs, data corruption, fraud |
| 9 | Sanitization | Strip or encode dangerous characters AFTER validation | Rich text, HTML inputs (as defense-in-depth) | XSS, injection if validation alone is insufficient |
| 10 | File validation | Check MIME type, magic bytes, size, extension, filename | File uploads | Arbitrary code execution, path traversal |

**Language/Framework Quick Map:**

| Language | Primary Library | Schema Example | Key Feature |
|---|---|---|---|
| Python | Pydantic v2 | `class User(BaseModel): email: EmailStr` | Type-safe, fast Rust core, auto-coercion |
| TypeScript | Zod 3.x | `z.object({ email: z.string().email() })` | Static type inference from schema |
| Node.js | Joi 17.x | `Joi.object({ email: Joi.string().email() })` | Fluent API, detailed error messages |
| Java | Jakarta Validation 3.1 | `@Email @NotBlank String email;` | Annotation-driven, framework-integrated |
| Go | validator v10 | `Email string \`validate:"required,email"\`` | Struct tag-based, Gin integration |
| C# | FluentValidation | `RuleFor(x => x.Email).NotEmpty().EmailAddress()` | LINQ-like, testable rules |

## Decision Tree

```
START: What kind of input are you validating?
├── Discrete/enumerated value (country, status, category)?
│   ├── YES → Allowlist: check against exact set of valid values
│   └── NO ↓
├── Structured data type (email, URL, phone, date, UUID)?
│   ├── YES → Use library-provided validators (EmailStr, z.string().email(), @Email)
│   │         + length limits + semantic checks
│   └── NO ↓
├── Numeric value (price, quantity, age)?
│   ├── YES → Type coerce to number + range check (min/max) + reject NaN/Infinity
│   └── NO ↓
├── Free-form text (name, comment, description)?
│   ├── YES → Length limit + Unicode normalization + encoding on OUTPUT (not input filtering)
│   │         For rich HTML: allowlist tags with DOMPurify
│   └── NO ↓
├── File upload?
│   ├── YES → Validate extension + MIME type + magic bytes + size limit + rename file
│   └── NO ↓
├── Complex nested object (API payload)?
│   ├── YES → Schema validation (Pydantic model, Zod schema, JSON Schema)
│   │         + semantic cross-field validation
│   └── NO ↓
└── DEFAULT → Type coerce + length limit + allowlist characters + server-side only
```

## Step-by-Step Guide

### 1. Define your validation schema at the boundary

Define validation schemas at the point where data enters your application (API endpoints, form handlers, CLI parsers). Never validate deep inside business logic. [src1]

```python
# Python: Pydantic v2 -- define schema at API boundary
from pydantic import BaseModel, Field, EmailStr, field_validator
from datetime import date

class CreateUserRequest(BaseModel):
    email: EmailStr                               # Built-in email validation
    name: str = Field(min_length=1, max_length=100)  # Length limits
    age: int = Field(ge=13, le=150)               # Range check
    role: str = Field(pattern=r'^(admin|user|viewer)$')  # Allowlist via regex

    @field_validator('name')
    @classmethod
    def normalize_name(cls, v: str) -> str:
        return v.strip()                          # Normalize whitespace
```

**Verify**: `CreateUserRequest(email="bad", name="", age=5, role="hacker")` should raise `ValidationError` with specific field errors.

### 2. Implement allowlist validation for discrete values

For any input that should be one of a known set of values, validate against an explicit allowlist. Never use blocklists for enumerated data. [src1]

```typescript
// TypeScript: Zod -- allowlist via enum
import { z } from 'zod';  // ^3.22.0

const StatusSchema = z.enum(['active', 'inactive', 'pending']);
const RoleSchema = z.enum(['admin', 'user', 'viewer']);

const CreateUserSchema = z.object({
  email: z.string().email().max(254),
  name: z.string().min(1).max(100).trim(),
  age: z.number().int().min(13).max(150),
  role: RoleSchema,
  status: StatusSchema.default('pending'),
});

type CreateUser = z.infer<typeof CreateUserSchema>;  // Auto-inferred type
```

**Verify**: `CreateUserSchema.safeParse({ role: 'superadmin' })` returns `{ success: false }` with an error indicating invalid enum value.

### 3. Add type coercion with strict error handling

Convert inputs to expected types early. Reject values that cannot be cleanly coerced rather than silently truncating or converting. [src2]

```go
// Go: validator v10 -- struct tag validation with type safety
package main

import (
    "fmt"
    "github.com/go-playground/validator/v10"  // v10.x
)

type CreateUserRequest struct {
    Email  string `json:"email" validate:"required,email,max=254"`
    Name   string `json:"name" validate:"required,min=1,max=100"`
    Age    int    `json:"age" validate:"required,gte=13,lte=150"`
    Role   string `json:"role" validate:"required,oneof=admin user viewer"`
}

func ValidateRequest(req CreateUserRequest) error {
    validate := validator.New()
    if err := validate.Struct(req); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }
    return nil
}
```

**Verify**: `ValidateRequest(CreateUserRequest{Email: "bad", Age: -1, Role: "hacker"})` returns validation errors for email, age, and role.

### 4. Validate at the semantic level

After syntactic validation passes, check business rules: cross-field consistency, temporal logic, and domain constraints. [src7]

```python
# Python: Pydantic -- semantic (cross-field) validation
from pydantic import BaseModel, model_validator
from datetime import date

class BookingRequest(BaseModel):
    check_in: date
    check_out: date
    guests: int = Field(ge=1, le=20)
    rooms: int = Field(ge=1, le=10)

    @model_validator(mode='after')
    def validate_dates_and_capacity(self):
        if self.check_out <= self.check_in:
            raise ValueError('check_out must be after check_in')
        nights = (self.check_out - self.check_in).days
        if nights > 365:
            raise ValueError('booking cannot exceed 365 nights')
        if self.guests > self.rooms * 4:
            raise ValueError('too many guests for number of rooms')
        return self
```

**Verify**: `BookingRequest(check_in=date(2026,3,1), check_out=date(2026,2,1), guests=1, rooms=1)` raises `ValidationError` for date order.

### 5. Canonicalize input before validation

Decode and normalize input before applying validation rules. This prevents double-encoding attacks and Unicode normalization bypasses. [src2]

```python
import unicodedata

def canonicalize(value: str) -> str:
    """Normalize Unicode and strip control characters before validation."""
    # NFC normalization: combine decomposed characters
    normalized = unicodedata.normalize('NFC', value)
    # Remove control characters (except newline, tab)
    cleaned = ''.join(
        c for c in normalized
        if unicodedata.category(c) != 'Cc' or c in ('\n', '\t')
    )
    return cleaned.strip()
```

**Verify**: `canonicalize('caf\u0065\u0301')` returns `'cafe\u0301'` (NFC-normalized). Control characters like `\x00` are stripped.

## Code Examples

### Python (Pydantic v2): API Request Validation

> Full script: [python-pydantic-v2-api-request-validation.py](scripts/python-pydantic-v2-api-request-validation.py) (26 lines)

```python
# Input:  Raw JSON request body from HTTP POST
# Output: Validated, typed Python object or ValidationError
from pydantic import BaseModel, Field, EmailStr, field_validator
from pydantic import ConfigDict
from enum import Enum
# ... (see full script)
```

### TypeScript (Zod 3.x): Form and API Validation

```typescript
// Input:  Unknown data from request body or form submission
// Output: Typed object or ZodError with field-level details

import { z } from 'zod';  // ^3.22.0

const CreateUserSchema = z.object({
  email: z.string().email().max(254).toLowerCase(),
  name: z.string().min(1).max(100).trim(),
  age: z.coerce.number().int().min(13).max(150),
  role: z.enum(['admin', 'user', 'viewer']).default('user'),
  bio: z.string().max(500).optional(),
}).strict();  // Reject unknown keys

type CreateUser = z.infer<typeof CreateUserSchema>;

// Safe parsing (no throw):
const result = CreateUserSchema.safeParse(requestBody);
if (!result.success) {
  // result.error.issues has field-level error details
  return res.status(400).json({ errors: result.error.flatten() });
}
const user: CreateUser = result.data;  // Fully typed
```

### Java (Jakarta Validation 3.1): Bean Validation

```java
// Input:  Request DTO from Spring MVC / JAX-RS
// Output: Validated bean or ConstraintViolationException

import jakarta.validation.constraints.*;
import jakarta.validation.Valid;

public record CreateUserRequest(
    @NotBlank @Email @Size(max = 254)
    String email,

    @NotBlank @Size(min = 1, max = 100)
    @Pattern(regexp = "^[\\p{L} '-]+$", message = "Invalid name characters")
    String name,

    @NotNull @Min(13) @Max(150)
    Integer age,

    @NotNull @Pattern(regexp = "^(admin|user|viewer)$")
    String role
) {}

// In Spring Controller:
// @PostMapping("/users")
// public ResponseEntity<?> create(@Valid @RequestBody CreateUserRequest req) {
//     ...  // req is validated by framework
// }
```

### Go (validator v10): Struct Tag Validation

> Full script: [go-validator-v10-struct-tag-validation.go](scripts/go-validator-v10-struct-tag-validation.go) (33 lines)

```go
// Input:  JSON-decoded struct from HTTP request
// Output: nil (valid) or validator.ValidationErrors
package main
import (
    "github.com/go-playground/validator/v10"  // v10.x
# ... (see full script)
```

## Anti-Patterns

### Wrong: Blocklist-based validation

```python
# BAD -- blocklist filtering is trivially bypassed
def validate_input(value):
    dangerous = ['<script>', 'DROP TABLE', 'eval(', '../']
    for d in dangerous:
        if d.lower() in value.lower():
            raise ValueError('Dangerous input detected')
    return value
# Bypassed by: <scr<script>ipt>, DR/**/OP TABLE, e\x76al(, ..%2f
```

### Correct: Allowlist with type coercion

```python
# GOOD -- define what is valid, reject everything else
from pydantic import BaseModel, Field
from enum import Enum

class Status(str, Enum):
    active = "active"
    inactive = "inactive"

class UpdateRequest(BaseModel):
    status: Status                    # Only "active" or "inactive"
    count: int = Field(ge=0, le=1000) # Typed + range-checked
    name: str = Field(max_length=100) # Length-limited
```

### Wrong: Client-side validation only

```javascript
// BAD -- client-side validation provides zero security
function submitForm() {
  const email = document.getElementById('email').value;
  if (!email.includes('@')) {
    alert('Invalid email');
    return;
  }
  // Attacker bypasses this with: curl -X POST -d 'email=<script>alert(1)</script>'
  fetch('/api/users', { method: 'POST', body: JSON.stringify({ email }) });
}
```

### Correct: Server-side validation with client-side UX

```typescript
// GOOD -- server-side validation is authoritative; client-side is UX only
import { z } from 'zod';

// Server-side (Express middleware):
const EmailSchema = z.string().email().max(254);

app.post('/api/users', (req, res) => {
  const result = EmailSchema.safeParse(req.body.email);
  if (!result.success) {
    return res.status(400).json({ error: result.error.flatten() });
  }
  // result.data is validated
});
```

### Wrong: Regex-only validation without length limits

```python
# BAD -- regex without length limit enables ReDoS
import re
email_pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')

def validate_email(email):
    if email_pattern.match(email):  # No length check!
        return True  # 10MB string of 'a' causes catastrophic backtracking
    return False
```

### Correct: Length limit before regex

```python
# GOOD -- check length BEFORE applying regex
def validate_email(email: str) -> bool:
    if not email or len(email) > 254:  # RFC 5321 limit
        return False
    if len(email.split('@')[0]) > 64:  # Local part limit
        return False
    return bool(re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email))
# Or better: use a validation library (Pydantic EmailStr, Zod z.string().email())
```

### Wrong: Trusting deserialized data

```python
# BAD -- deserializing untrusted input without validation
import json
data = json.loads(request.body)
user_id = data['user_id']        # No type check -- could be string, list, None
quantity = data['quantity']       # No range check -- could be -999999
db.execute(f"UPDATE orders SET qty={quantity} WHERE user={user_id}")  # Injection!
```

### Correct: Schema validation then parameterized queries

```python
# GOOD -- validate schema, then use parameterized queries
from pydantic import BaseModel, Field

class UpdateOrderRequest(BaseModel):
    user_id: int = Field(gt=0)
    quantity: int = Field(ge=1, le=9999)

req = UpdateOrderRequest.model_validate_json(request.body)
db.execute("UPDATE orders SET qty=%s WHERE user=%s", (req.quantity, req.user_id))
```

## Common Pitfalls

- **Validating input but not encoding output**: Input validation and output encoding solve different problems. Validate input for correctness; encode output for context-specific safety (HTML, SQL, URL). Neither replaces the other. [src1]
- **ReDoS via unbounded regex**: Complex regex patterns (nested quantifiers, alternation) on long input cause catastrophic backtracking. Fix: Always enforce length limits before regex; use RE2-compatible patterns; set regex timeouts. [src2]
- **Forgetting Unicode normalization**: Equivalent Unicode representations bypass character allowlists. `caf\u00E9` and `cafe\u0301` are visually identical but byte-different. Fix: Apply `unicodedata.normalize('NFC', input)` before validation. [src1]
- **Validating only at the API gateway**: Middleware validates structure but business logic receives raw data from internal services. Fix: Validate at every trust boundary, including internal service-to-service calls. [src7]
- **Pydantic v1 to v2 migration breakage**: `@validator` replaced by `@field_validator`; `class Config` replaced by `model_config`; validators must use `@classmethod`. Fix: Follow the Pydantic v2 migration guide; use `bump-pydantic` tool. [src3]
- **Zod `.parse()` vs `.safeParse()`**: Using `.parse()` throws exceptions on invalid input, which crashes Express if uncaught. Fix: Use `.safeParse()` in request handlers and check `result.success`. [src4]
- **Silently coercing bad data**: Automatic type coercion (e.g., `"123abc"` to `123`) hides bugs. Fix: Use strict mode (`Pydantic strict=True`, `z.number()` without `z.coerce`); reject inputs that don't exactly match the expected type. [src3]
- **Not validating array/collection sizes**: Accepting unbounded arrays allows memory exhaustion DoS. Fix: Always set `max_length` on list/array fields (e.g., `Field(max_length=100)`, `z.array().max(100)`). [src2]

## Diagnostic Commands

```bash
# Test Pydantic validation in Python REPL
python -c "
from pydantic import BaseModel, EmailStr, Field
class T(BaseModel):
    email: EmailStr
    age: int = Field(ge=0, le=150)
try: T(email='bad', age=-1)
except Exception as e: print(e)
"

# Test Zod validation in Node.js
node -e "
const {z} = require('zod');
const s = z.object({email: z.string().email(), age: z.number().int().min(0)});
console.log(s.safeParse({email:'bad', age:-1}));
"

# Find unvalidated request body usage (Node.js/Express)
grep -rn 'req\.body\.' --include="*.js" --include="*.ts" . | grep -v 'validate\|schema\|parse\|sanitize'

# Find raw SQL string interpolation (Python)
grep -rn 'f".*{.*}.*SELECT\|f".*{.*}.*INSERT\|f".*{.*}.*UPDATE\|f".*{.*}.*DELETE' --include="*.py" .

# Check for missing server-side validation in Express routes
grep -rn 'app\.\(post\|put\|patch\)' --include="*.js" --include="*.ts" . | grep -v 'validate\|schema\|safeParse'

# Audit Java controllers for missing @Valid annotation
grep -rn '@RequestBody' --include="*.java" . | grep -v '@Valid'
```

## Version History & Compatibility

| Library | Version | Status | Key Change |
|---|---|---|---|
| Pydantic | v2.x | Current | Rust-powered core, `@field_validator`, `model_config`, 5-50x faster |
| Pydantic | v1.x | EOL (2024) | `@validator`, `class Config` -- migration tool: `bump-pydantic` |
| Zod | 3.x | Current/Stable | `z.coerce`, `.pipe()`, `.brand()`, discriminated unions |
| Joi | 17.x | Current | ESM support, TypeScript types, `@hapi/joi` renamed to `joi` |
| Joi | 16.x | Deprecated | Package name was `@hapi/joi` |
| Jakarta Validation | 3.1 | Current | `jakarta.validation.*` namespace (was `javax.validation.*`) |
| Jakarta Validation | 2.0 | Legacy | `javax.validation.*` -- requires namespace migration |
| go-playground/validator | v10 | Current | Custom validators, struct-level validation, dive support |
| FluentValidation (.NET) | 11.x | Current | .NET 8 support, async validators |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Any user-supplied data enters your application | Processing fully trusted internal system data | Basic type assertions may suffice |
| Building APIs that accept JSON/form payloads | Validating output for display (that's encoding) | Output encoding (context-specific escaping) |
| File upload processing | Simple static config file parsing | Config libraries with built-in schema (e.g., TOML) |
| CLI tools accepting user arguments | Data already validated by upstream service in same trust zone | Pass validated types between services |
| Preventing injection attacks at the boundary | Replacing parameterized queries for SQL | Parameterized queries + input validation together |

## Important Caveats

- Input validation is necessary but NOT sufficient for security -- it must be combined with output encoding, parameterized queries, and defense-in-depth measures
- Validation library APIs change between major versions (Pydantic v1 vs v2, Joi 16 vs 17) -- always check the docs for your specific version
- Unicode and encoding edge cases (homoglyphs, bidirectional text, zero-width characters) require explicit handling beyond basic regex patterns
- Regex-based validation must be tested for ReDoS with tools like `rxxr2` or safe-regex -- catastrophic backtracking can cause denial of service
- Client-side and server-side validation should use the same schema definition when possible (Zod enables this for full-stack TypeScript) to avoid drift
- Performance impact: deep validation of large payloads (nested objects, large arrays) adds latency -- validate early and fail fast, set depth limits

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention Guide](/software/security/xss-prevention/2026)
- [SQL Injection Prevention](/software/security/sql-injection-prevention/2026)
- [CSRF Prevention](/software/security/csrf-prevention/2026)
