Defense-in-Depth Validation

Multi-layered validation pattern preventing bugs by implementing redundant checks across entry points, business logic, environment guards, and debug instrumentation.

Defense-in-Depth Validation

Multi-layered validation pattern that prevents bugs by implementing redundant checks across all data flow layers, making errors structurally impossible.

Critical for preventing bugs caused by invalid data that bypasses single-point validation, especially in complex codebases with multiple entry points.

Core Principle

"Validate at EVERY layer data passes through."

Single validation points fail because:

  • New code paths bypass checks
  • Refactoring removes guards
  • Assumptions become invalid
  • Context is lost between layers

Four Validation Layers

Redundant Defense

Layer 1: Entry Points

  • API endpoints
  • CLI arguments
  • User inputs
  • External data sources

Layer 2: Business Logic

  • Function parameters
  • Method inputs
  • Service boundaries

Layer 3: Environment Guards

  • Test vs. production checks
  • Directory safety validation
  • Operation restrictions

Layer 4: Debug Instrumentation

  • Logging with stack traces
  • Assertion checks
  • Runtime verification

Layer 1: Entry Point Validation

Validate ALL Inputs:

JAVASCRIPT
// API endpoint
app.post('/api/create', (req, res) => {
  if (!req.body.directory) {
    return res.status(400).json({ 
      error: 'directory required'
    });
  }
  
  if (typeof req.body.directory !== 'string') {
    return res.status(400).json({
      error: 'directory must be string'
    });
  }
  
  if (req.body.directory.trim() === '') {
    return res.status(400).json({
      error: 'directory cannot be empty'
    });
  }
  
  // ... proceed with valid data
});

Layer 2: Business Logic Validation

Never Trust Callers:

JAVASCRIPT
function createProject(directory) {
  // Validate even if caller "should" have checked
  if (!directory || typeof directory !== 'string') {
    throw new Error(
      'createProject: directory must be non-empty string'
    );
  }
  
  if (!path.isAbsolute(directory)) {
    throw new Error(
      'createProject: directory must be absolute path'
    );
  }
  
  return initializeProject(directory);
}

function initializeProject(directory) {
  // Validate again at next layer
  if (!directory) {
    throw new Error(
      'initializeProject: directory required'
    );
  }
  
  // ... implementation
}

"Redundant" validation catches:

  • Code paths that skip entry validation
  • Refactoring that removes checks
  • Internal function calls
  • Unit tests calling functions directly

This redundancy is intentional and valuable.

Layer 3: Environment Guards

Prevent Dangerous Operations in Wrong Context:

JAVASCRIPT
function gitInit(directory) {
  // Validate data
  if (!directory) {
    throw new Error('gitInit: directory required');
  }
  
  // Environment guard: never in tests
  if (process.env.NODE_ENV === 'test') {
    throw new Error(
      'gitInit: Cannot run git operations during tests'
    );
  }
  
  // Safety guard: must be in temp directory
  const tempDir = os.tmpdir();
  if (!directory.startsWith(tempDir)) {
    throw new Error(
      `gitInit: Directory must be in temp dir ${tempDir}`
    );
  }
  
  // Safe to proceed
  execSync(`git init ${directory}`);
}

Common Guards:

Environment Checks

Test Environment:

JAVASCRIPT
if (process.env.NODE_ENV === 'test') {
  throw new Error('Operation not allowed in tests');
}

Development Only:

JAVASCRIPT
if (process.env.NODE_ENV === 'production') {
  throw new Error('Debug operation in production');
}

Directory Safety:

JAVASCRIPT
if (!directory.includes('/tmp/') && 
    !directory.includes('/temp/')) {
  throw new Error('Must use temporary directory');
}

Layer 4: Debug Instrumentation

Log for Forensics:

JAVASCRIPT
function processDirectory(dir) {
  // Log with context
  console.error('DEBUG processDirectory:', {
    dir,
    type: typeof dir,
    length: dir?.length,
    isEmpty: dir === '',
    stack: new Error().stack,
    env: process.env.NODE_ENV,
    cwd: process.cwd()
  });
  
  // Assertion
  console.assert(dir, 'dir must be truthy');
  console.assert(dir.length > 0, 'dir must be non-empty');
  
  // ... proceed
}

Debug Logging Safety:

  • Use environment variables: if (process.env.DEBUG_TRACE)
  • Never commit debug logs
  • Scrub sensitive data
  • Remove before production

Real Example: Empty Directory Bug

Problem: git init executed on empty path created .git in wrong location

Single-Layer "Fix" (Wrong):

JAVASCRIPT
// Only check at git operation
function gitInit(dir) {
  if (!dir) throw new Error('dir required');
  execSync(`git init ${dir}`);
}

Defense-in-Depth (Right):

JAVASCRIPT
// Layer 1: Entry point
function createProject(config) {
  if (!config.dir) {
    throw new Error('config.dir required at entry');
  }
  return setupProject(config);
}

// Layer 2: Business logic
function setupProject(config) {
  if (!config.dir) {
    throw new Error('config.dir required in setup');
  }
  return initializeGit(config.dir);
}

// Layer 3: Environment + operation
function initializeGit(dir) {
  if (!dir) {
    throw new Error('dir required in gitInit');
  }
  
  if (process.env.NODE_ENV === 'test') {
    throw new Error('No git operations in tests');
  }
  
  if (!fs.existsSync(dir)) {
    throw new Error(`dir does not exist: ${dir}`);
  }
  
  execSync(`git init ${dir}`);
}

// Layer 4: Debug instrumentation
function initializeGit(dir) {
  console.error('DEBUG gitInit:', {
    dir,
    exists: fs.existsSync(dir),
    isTemp: dir.includes('/tmp/'),
    stack: new Error().stack
  });
  
  // ... validation and execution
}

Benefits of Defense-in-Depth

Why Multiple Layers Work

Catches:

  • Direct function calls bypassing entry validation
  • Refactoring that removes guards
  • Test code calling internal functions
  • Race conditions and timing issues
  • Unexpected code paths

Provides:

  • Clear error messages with context
  • Stack traces showing exact path
  • Multiple chances to catch bugs
  • Forensic data for debugging

Testing the Pattern

Verify Each Layer:

JAVASCRIPT
// Test entry validation
test('rejects missing directory at entry', () => {
  expect(() => createProject({}))
    .toThrow('directory required');
});

// Test business logic validation
test('rejects empty directory in logic', () => {
  expect(() => setupProject({ dir: '' }))
    .toThrow('directory required');
});

// Test environment guards
test('blocks git operations in tests', () => {
  process.env.NODE_ENV = 'test';
  expect(() => initializeGit('/tmp/test'))
    .toThrow('No git operations in tests');
});

Common Patterns

File System Operations:

JAVASCRIPT
if (!path) throw new Error('path required');
if (!path.isAbsolute(path)) throw new Error('path must be absolute');
if (!fs.existsSync(path)) throw new Error('path does not exist');

Type Validation:

JAVASCRIPT
if (value === undefined) throw new Error('value required');
if (typeof value !== 'string') throw new Error('value must be string');
if (value.trim() === '') throw new Error('value cannot be empty');

Numeric Ranges:

JAVASCRIPT
if (count === undefined) throw new Error('count required');
if (typeof count !== 'number') throw new Error('count must be number');
if (count < 0) throw new Error('count must be non-negative');
if (!Number.isInteger(count)) throw new Error('count must be integer');

About This Skill

This skill was created by obra as part of the Superpowers Skills Collection.

Explore the collection for complementary debugging and development methodology skills!


Multi-layered validation pattern preventing bugs by implementing redundant checks across entry points, business logic, environment guards, and debug instrumentation.