Agent Skills: Defense-in-Depth Validation

Validate at every layer data passes through to make bugs impossible. Use when invalid data causes failures deep in execution, requiring validation at multiple system layers.

UncategorizedID: secondsky/claude-skills/defense-in-depth-validation

Install this agent skill to your local

pnpm dlx add-skill https://github.com/secondsky/claude-skills/tree/HEAD/plugins/defense-in-depth-validation/skills/defense-in-depth-validation

Skill Files

Browse the full folder contents for defense-in-depth-validation.

Download Skill

Loading file tree…

plugins/defense-in-depth-validation/skills/defense-in-depth-validation/SKILL.md

Skill Metadata

Name
defense-in-depth-validation
Description
Validate at every layer data passes through to make bugs impossible. Use when invalid data causes failures deep in execution, requiring validation at multiple system layers.

Defense-in-Depth Validation

Overview

When you fix a bug caused by invalid data, adding validation at one place feels sufficient. But that single check can be bypassed by different code paths, refactoring, or mocks.

Core principle: Validate at EVERY layer data passes through. Make the bug structurally impossible.

Why Multiple Layers

Single validation: "We fixed the bug" Multiple layers: "We made the bug impossible"

Different layers catch different cases:

  • Entry validation catches most bugs
  • Business logic catches edge cases
  • Environment guards prevent context-specific dangers
  • Debug logging helps when other layers fail

The Four Layers

Layer 1: Entry Point Validation

Purpose: Reject obviously invalid input at API boundary

function createProject(name: string, workingDirectory: string) {
  if (!workingDirectory || workingDirectory.trim() === '') {
    throw new Error('workingDirectory cannot be empty');
  }
  if (!existsSync(workingDirectory)) {
    throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
  }
  if (!statSync(workingDirectory).isDirectory()) {
    throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
  }
  // ... proceed
}

Layer 2: Business Logic Validation

Purpose: Ensure data makes sense for this operation

function initializeWorkspace(projectDir: string, sessionId: string) {
  if (!projectDir) {
    throw new Error('projectDir required for workspace initialization');
  }
  // ... proceed
}

Layer 3: Environment Guards

Purpose: Prevent dangerous operations in specific contexts

async function gitInit(directory: string) {
  // In tests, refuse git init outside temp directories
  if (process.env.NODE_ENV === 'test') {
    const normalized = normalize(resolve(directory));
    const tmpDir = normalize(resolve(tmpdir()));

    if (!normalized.startsWith(tmpDir)) {
      throw new Error(
        `Refusing git init outside temp dir during tests: ${directory}`
      );
    }
  }
  // ... proceed
}

Layer 4: Debug Instrumentation

Purpose: Capture context for forensics

async function gitInit(directory: string) {
  const stack = new Error().stack;
  logger.debug('About to git init', {
    directory,
    cwd: process.cwd(),
    stack,
  });
  // ... proceed
}

Applying the Pattern

When you find a bug:

  1. Trace the data flow - Where does bad value originate? Where used?
  2. Map all checkpoints - List every point data passes through
  3. Add validation at each layer - Entry, business, environment, debug
  4. Test each layer - Try to bypass layer 1, verify layer 2 catches it

Example from Session

Bug: Empty projectDir caused git init in source code

Data flow:

  1. Test setup → empty string
  2. Project.create(name, '')
  3. WorkspaceManager.createWorkspace('')
  4. git init runs in process.cwd()

Four layers added:

  • Layer 1: Project.create() validates not empty/exists/writable
  • Layer 2: WorkspaceManager validates projectDir not empty
  • Layer 3: WorktreeManager refuses git init outside tmpdir in tests
  • Layer 4: Stack trace logging before git init

Result: All 1847 tests passed, bug impossible to reproduce

Key Insight

All four layers were necessary. During testing, each layer caught bugs the others missed:

  • Different code paths bypassed entry validation
  • Mocks bypassed business logic checks
  • Edge cases on different platforms needed environment guards
  • Debug logging identified structural misuse

Don't stop at one validation point. Add checks at every layer.

Security defense-in-depth

The same layered approach is the standard pattern for security controls: trust is never granted at a single point, so that a bug or bypass in any one layer cannot by itself compromise the system. Each request is checked independently at the edge, in the handler, and at the data layer.

  • Layer 1 (edge): rate-limit and filter at the edge (CDN/WAF such as Cloudflare or AWS WAF) so abusive traffic never reaches the origin.
  • Layer 2 (handler): re-derive authorization server-side from the session — never trust a client-supplied claim like a userId in the body.
  • Layer 3 (data): enforce the ownership invariant in the database query so that even a handler bug cannot cross tenant boundaries.
// Layer 2 — re-derive authz server-side, never trust client claims
app.put('/docs/:id', async (req, res) => {
  const user = await verifySession(req.cookies.session); // throws if invalid
  // Layer 3 — DB clause re-checks ownership; a handler bug still can't cross tenants
  const updated = await db.query(
    'UPDATE docs SET title = $1 WHERE id = $2 AND owner_id = $3 RETURNING *',
    [req.body.title, req.params.id, user.id]
  );
  if (!updated.rowCount) return res.status(403).send('Not allowed');
  res.json(updated.rows[0]);
});

Defense in depth means no single layer's failure is sufficient to compromise the system.