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:
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
Bug: Empty
projectDir
caused
git init
in source code
Data flow:
Test setup → empty string
Project.create(name, '')
WorkspaceManager.createWorkspace('')
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.
defense-in-depth
安装
npx skills add https://github.com/zenobi-us/dotfiles --skill defense-in-depth