Skill Tracer
Skills › Superpowers › systematic-debugging › defense-in-depth.md

defense-in-depth.md

122 lines · skills/systematic-debugging/defense-in-depth.md at 2ce8aa1

Open on GitHub
1# Defense-in-Depth Validation
2

Defense-in-Depth Validation

Raw lines 1 to 2
3## Overview
4
5When 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.
6
7**Core principle:** Validate at EVERY layer data passes through. Make the bug structurally impossible.
8

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.

Raw lines 3 to 8
9## Why Multiple Layers
11Single validation: "We fixed the bug"
12Multiple layers: "We made the bug impossible"
14Different layers catch different cases:
15- Entry validation catches most bugs
16- Business logic catches edge cases
17- Environment guards prevent context-specific dangers
18- Debug logging helps when other layers fail

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
Raw lines 9 to 19
20## The Four Layers

The Four Layers

Raw lines 20 to 21
22### Layer 1: Entry Point Validation
23**Purpose:** Reject obviously invalid input at API boundary
25```typescript
26function createProject(name: string, workingDirectory: string) {
27 if (!workingDirectory || workingDirectory.trim() === '') {
28 throw new Error('workingDirectory cannot be empty');
29 }
30 if (!existsSync(workingDirectory)) {
31 throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
32 }
33 if (!statSync(workingDirectory).isDirectory()) {
34 throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
35 }
36 // ... proceed
38```

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
}
Raw lines 22 to 39
40### Layer 2: Business Logic Validation
41**Purpose:** Ensure data makes sense for this operation
43```typescript
44function initializeWorkspace(projectDir: string, sessionId: string) {
45 if (!projectDir) {
46 throw new Error('projectDir required for workspace initialization');
47 }
48 // ... proceed
50```

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
}
Raw lines 40 to 51
52### Layer 3: Environment Guards
53**Purpose:** Prevent dangerous operations in specific contexts
55```typescript
56async function gitInit(directory: string) {
57 // In tests, refuse git init outside temp directories
58 if (process.env.NODE_ENV === 'test') {
59 const normalized = normalize(resolve(directory));
60 const tmpDir = normalize(resolve(tmpdir()));
62 if (!normalized.startsWith(tmpDir)) {
63 throw new Error(
64 `Refusing git init outside temp dir during tests: ${directory}`
65 );
66 }
67 }
68 // ... proceed
70```

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
}
Raw lines 52 to 71
72### Layer 4: Debug Instrumentation
73**Purpose:** Capture context for forensics
75```typescript
76async function gitInit(directory: string) {
77 const stack = new Error().stack;
78 logger.debug('About to git init', {
79 directory,
80 cwd: process.cwd(),
81 stack,
82 });
83 // ... proceed
85```

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
}
Raw lines 72 to 86
87## Applying the Pattern
89When you find a bug:
911. **Trace the data flow** - Where does bad value originate? Where used?
922. **Map all checkpoints** - List every point data passes through
933. **Add validation at each layer** - Entry, business, environment, debug
944. **Test each layer** - Try to bypass layer 1, verify layer 2 catches it

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
Raw lines 87 to 95
96## Example from Session
98Bug: Empty `projectDir` caused `git init` in source code
100**Data flow:**
1011. Test setup → empty string
1022. `Project.create(name, '')`
1033. `WorkspaceManager.createWorkspace('')`
1044. `git init` runs in `process.cwd()`
106**Four layers added:**
107- Layer 1: `Project.create()` validates not empty/exists/writable
108- Layer 2: `WorkspaceManager` validates projectDir not empty
109- Layer 3: `WorktreeManager` refuses git init outside tmpdir in tests
110- Layer 4: Stack trace logging before git init
112**Result:** All 1847 tests passed, bug impossible to reproduce

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

Raw lines 96 to 113
114## Key Insight
116All four layers were necessary. During testing, each layer caught bugs the others missed:
117- Different code paths bypassed entry validation
118- Mocks bypassed business logic checks
119- Edge cases on different platforms needed environment guards
120- Debug logging identified structural misuse
122**Don't stop at one validation point.** Add checks at every layer.

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.

Raw lines 114 to 122