defense-in-depth.md
Defense-in-Depth Validation
Section titled “Defense-in-Depth Validation”Overview
Section titled “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
Section titled “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
Section titled “The Four Layers”Layer 1: Entry Point Validation
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “Applying the Pattern”When you find a bug:
- Trace the data flow - Where does bad value originate? Where used?
- Map all checkpoints - List every point data passes through
- Add validation at each layer - Entry, business, environment, debug
- Test each layer - Try to bypass layer 1, verify layer 2 catches it
Example from Session
Section titled “Example from Session”Bug: Empty projectDir caused git init in source code
Data flow:
- Test setup → empty string
Project.create(name, '')WorkspaceManager.createWorkspace('')git initruns inprocess.cwd()
Four layers added:
- Layer 1:
Project.create()validates not empty/exists/writable - Layer 2:
WorkspaceManagervalidates projectDir not empty - Layer 3:
WorktreeManagerrefuses 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
Section titled “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.
Original Markdown source
# 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
```typescriptfunction 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
```typescriptfunction 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
```typescriptasync 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
```typescriptasync 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 through3. **Add validation at each layer** - Entry, business, environment, debug4. **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 string2. `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.