Skip to main content
skill CX Codex

defense-in-depth-validation — Claude Code skill: validate every layer

A skill that pushes your agent to validate data at every layer it passes through, so the bug becomes structurally impossible. Useful when bad data fails deep in execution.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add secondsky/claude-skills --skill defense-in-depth-validation -a claude-code
Open on GitHub

What defense-in-depth-validation does

The skill lays out four validation layers: entry point at the API boundary, business logic, environment guards, and debug instrumentation. Each layer catches a different class of failure, so one unchecked code path does not leave a hole. Your agent gets TypeScript and JavaScript examples and works through a real bug with them.

The workflow is: trace where the bad value originates and where it is used, list every checkpoint the data passes, add validation at each one, then try to bypass layer 1 and confirm layer 2 catches it. In the worked example an empty projectDir ran git init in the source tree, and all four layers closed that path.

A separate section covers security: rate-limit and filter at the edge, re-derive authorization server-side instead of trusting a client-supplied userId, and enforce the ownership clause inside the SQL query. There are no libraries to wire up and no API keys — it is a written guide the agent reads while debugging. Worth installing if you have already hit bugs that only surfaced on one execution path.

What it helps with

  • close an invalid-data bug so it stops reproducing
  • map every checkpoint data passes before it is used
  • stop mocks and alternate paths from skipping entry validation
  • refuse dangerous operations like git init outside a temp dir in tests
  • re-check ownership in the SQL query instead of trusting client claims

Example prompts for the agent

  • Walk through this empty projectDir bug and add validation at every layer the data passes through
  • Check whether the test mocks bypass entry validation and add a business logic guard
  • Add the ownership clause to the SQL query so a handler bug can't cross tenants

How to install defense-in-depth-validation

1Skills CLI (Vercel)

npx skills add secondsky/claude-skills --skill defense-in-depth-validation -a claude-code

2Manually: put the files in place

~/.claude/skills/defense-in-depth-validation/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/defense-in-depth-validation/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about defense-in-depth-validation

How do I install defense-in-depth-validation in Claude?

Run npx skills add secondsky/claude-skills --skill defense-in-depth-validation -a claude-code. The skill lands at ~/.claude/skills/defense-in-depth-validation/SKILL.md.

What does defense-in-depth-validation do?

It describes four validation layers: entry, business logic, environment guards, and debug logging. The agent applies them when invalid data causes failures deep in execution.

How is it different from a single input check?

One check can be bypassed by another code path, a refactor, or a mock. The skill asks for validation at every layer so the bug becomes structurally impossible.

Source text

SKILL.md · MIT
---
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.
metadata:
  version: "1.1.0"
license: MIT
---

# 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

```typescript
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

```typescript
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

```typescript
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

```typescript
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.

```javascript
// 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.

© secondsky · MIT · secondsky/claude-skills

Similar skills

Everything for Testing and QA →

From the same set89

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor