Test and operate
Complete a web security review
Review architecture, code, dependencies, configuration, tests, and recovery as one release decision instead of one final scanner run.
A security review is a release decision, not a scanner run at the end. A clean dependency scan proves one thing: no known-bad package shipped. It says nothing about whether your authorization logic is right, and that’s where most real web flaws live.
The bug no scanner sees
A team ships a release. npm audit reports zero vulnerabilities. The deployment checks pass. But the release includes a new POST /notes/bulk-delete route, and the handler checks ownership for the first ID in the list only, then deletes all of them. Send [myNote, bobsNote, alicesNote] and two other people’s notes are gone.
No tool flags it, because the code is valid JavaScript calling a valid query. The logic is wrong. Only a person who asks “does every item get checked?” finds it. That question is what the review is for.
Walk the boundaries that changed
You don’t re-review the whole app every release. You walk the trust boundaries this release touched: where new input enters, where authorization decisions live, and which dependencies or configuration moved. A short checklist keeps it honest:
[ ] threat model revisited for changed boundaries
[ ] authorization checked on every new read and write, including bulk
[ ] negative tests run (wrong user, missing proof, bad input)
[ ] dependency and deployment findings triaged
[ ] logs and alerts fire for the new paths
[ ] rollback rehearsed
That “including bulk” is there because of the story above. Every line in a checklist like this comes from a bug someone shipped. Add your own as you find them.
The negative tests are the ones people skip. For the bulk-delete route, that’s one curl with mixed owners:
curl -s -o /dev/null -w '%{http_code}\n' -b 'session=alice' -X POST \
-H 'Content-Type: application/json' -d '{"ids":[42,43]}' \
https://app.flaviocopes.com/notes/bulk-delete
# 403, and both notes still exist
If that returns 200, the release doesn’t ship.
Write every finding as a decision
A finding that lives in someone’s head gets forgotten. Write each one down with a path to reproduce it, the impact, a decision, an owner, and a date:
Finding: bulk-delete authorizes only the first item
Path: POST /notes/bulk-delete with mixed-owner ids
Impact: high: cross-tenant deletion
Decision: fix before release Owner: flavio Date: 2026-08-03
The decision can be fix, ship and fix next, or accept. All three are fine when they’re written down with a name attached. What’s not fine is a review that produces a list of concerns and no decisions.
Don’t block a release over every low-risk finding either. That trains the team to route around the review. Block when a credible high-impact path is open, like the one above. Let the rest ship with an owner and a date.
Try this on your own project: take your next release and walk it through the six checklist items. Write each finding in the format above. After you deploy the fix, rerun the one negative test that found the bug and keep the output as proof the release is closed.
Lesson completed