Protect by default

Use defense in depth

Combine independent preventive, detective, and recovery controls so one failed safeguard does not become a complete compromise.

One perfect defense does not exist. Good systems assume a control can fail, and arrange for the failure to be survivable.

Defense in depth means combining controls that catch different failures: input validation, database constraints, authorization checks, safe output handling, logs, and backups. Each solves a different part of the problem. Prevention stops the bad action, detection tells you it was attempted, and recovery limits the damage when both fall short.

Layers in a real endpoint

Take a notes API. Three controls guard the same cross-user read:

// layer 1: the route checks ownership
const note = await getNote(id)
if (note.ownerId !== session.userId) return res.status(403).end()
-- layer 2: the query itself is scoped
SELECT * FROM notes WHERE id = $1 AND owner_id = $2;
layer 3: an audit event records the denial
event=note.read.denied user=u_9412 note=n_1841 request=req_77af

Now suppose a refactor deletes the route check. The scoped query still returns zero rows, so the read stays blocked. And the audit event reveals the attempt, so you learn the first layer is gone before an attacker teaches you. That is what independence buys: the second control does not care that the first one vanished.

Fake depth

Layers help only when they are independent. Repeating the same assumption in five places is still one weakness.

The common version: the client sends an owner ID in the request body, and the route, the service layer, the query builder, and the audit log all use that client-supplied value. Five checks, one assumption — that the client tells the truth. One forged request defeats all of them at once. The server-side session is the only trustworthy source of identity here, so every layer must derive ownership from it.

Prove the depth exists

Pick one sensitive operation and name its preventive, detective, and recovery controls. If you cannot name all three, you have already found your gap.

Then rehearse the failure. In a test environment, disable the first control — comment out the route check — and confirm the query still denies the read and the event still records the attempt. Depth you have never tested is depth you only hope you have.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →