Files
claudekit/skills/defense-in-depth/references/validation-layers.md
T
2026-04-19 14:10:38 +07:00

6.2 KiB

Validation Layers Reference

Multi-layer validation strategy ensuring no single point of failure.

Overview

Request -> [Layer 1: Input] -> [Layer 2: Business] -> [Layer 3: Persistence] -> [Layer 4: Output] -> Response

Each layer validates independently. A failure at any layer should produce a clear, actionable error. Never rely on a single layer.

Layer 1: Input Boundary

Purpose: Reject malformed, oversized, or obviously invalid data at the edge.

What to Validate

  • Data types and shapes (string, number, object structure)
  • Required vs optional fields
  • String length, numeric ranges, allowed values
  • Format patterns (email, URL, UUID, date)
  • Content-Type headers, encoding
  • File upload size and MIME type
  • Request rate and authentication tokens

Python (FastAPI + Pydantic)

from pydantic import BaseModel, Field, EmailStr
from fastapi import FastAPI, Query

class CreateUserRequest(BaseModel):
    email: EmailStr
    name: str = Field(min_length=1, max_length=200)
    age: int = Field(ge=0, le=150)
    role: Literal["admin", "user", "viewer"]

@app.post("/users")
async def create_user(req: CreateUserRequest):
    # req is already validated by Pydantic
    ...

TypeScript (Zod + Express)

import { z } from "zod";

const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(200),
  age: z.number().int().min(0).max(150),
  role: z.enum(["admin", "user", "viewer"]),
});

app.post("/users", (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.issues });
  }
  // result.data is typed and validated
});

Tools

Language Library Purpose
Python Pydantic, marshmallow, cerberus Schema validation
TypeScript Zod, Yup, io-ts, Ajv Schema validation
Any JSON Schema Language-agnostic schema

Layer 2: Business Logic

Purpose: Enforce domain rules, state transitions, and authorization.

What to Validate

  • Business rules (e.g., "cannot cancel a shipped order")
  • State machine transitions (e.g., draft -> published, not draft -> archived)
  • Cross-field dependencies (e.g., "end_date must be after start_date")
  • Authorization (e.g., "only the owner can modify this resource")
  • Resource existence (e.g., "referenced entity must exist")
  • Idempotency and duplicate detection

Python

class OrderService:
    def cancel_order(self, order_id: str, user_id: str) -> Order:
        order = self.repo.get(order_id)
        if order is None:
            raise NotFoundError(f"Order {order_id} not found")
        if order.owner_id != user_id:
            raise ForbiddenError("Only the order owner can cancel")
        if order.status not in ("pending", "confirmed"):
            raise BusinessRuleError(
                f"Cannot cancel order in '{order.status}' status"
            )
        order.status = "cancelled"
        return self.repo.save(order)

TypeScript

class OrderService {
  cancelOrder(orderId: string, userId: string): Order {
    const order = this.repo.get(orderId);
    if (!order) throw new NotFoundError(`Order ${orderId} not found`);
    if (order.ownerId !== userId) throw new ForbiddenError("Only the order owner can cancel");

    const cancellableStatuses = ["pending", "confirmed"] as const;
    if (!cancellableStatuses.includes(order.status)) {
      throw new BusinessRuleError(`Cannot cancel order in '${order.status}' status`);
    }
    order.status = "cancelled";
    return this.repo.save(order);
  }
}

Guidelines

  • Keep validation logic in the service/domain layer, not in controllers
  • Use custom exception types that map to HTTP status codes
  • Business rules should be testable independently of HTTP/DB

Layer 3: Data Persistence

Purpose: Enforce data integrity at the database level as the last line of defense.

What to Validate

  • NOT NULL constraints
  • UNIQUE constraints (email, username)
  • FOREIGN KEY constraints (referential integrity)
  • CHECK constraints (value ranges, enums)
  • Data types and precision
  • Default values

PostgreSQL Examples

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(200) NOT NULL CHECK (char_length(name) > 0),
    age INTEGER CHECK (age >= 0 AND age <= 150),
    role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'user', 'viewer')),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
    status VARCHAR(20) NOT NULL DEFAULT 'pending'
        CHECK (status IN ('pending', 'confirmed', 'shipped', 'cancelled')),
    total_cents INTEGER NOT NULL CHECK (total_cents >= 0)
);

Guidelines

  • Mirror constraints in your ORM (SQLAlchemy CheckConstraint, Prisma @unique, etc.)
  • Database constraints are the safety net; they catch bugs in application code
  • Always handle constraint violation errors gracefully (unique violation -> 409 Conflict)
  • Use migrations to manage schema changes

Layer 4: Output Boundary

Purpose: Ensure responses are safe, well-formed, and contain only intended data.

What to Validate

  • Strip sensitive fields (passwords, internal IDs, tokens)
  • HTML-encode user-generated content to prevent XSS
  • Validate response schema (catch accidental data leaks)
  • Set security headers (Content-Type, X-Content-Type-Options)
  • Limit response size

Techniques

  • Python: Use Pydantic response_model to exclude fields not in the response schema
  • TypeScript: Create explicit mapper functions (toUserResponse()) that pick only safe fields
  • Headers: Set X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Content-Security-Policy
  • Encoding: HTML-encode user-generated content before rendering

Layer Interaction Summary

Layer Catches If Missing
Input Malformed data, injection attempts Bad data flows into business logic
Business Invalid operations, auth bypass Violated business rules, data corruption
Persistence Constraint violations, duplicates Inconsistent data in database
Output Data leaks, XSS Sensitive data exposed to clients