Test and operate
Build a security test plan
Turn routes, roles, data flows, and threat stories into a repeatable plan that covers prevention and safe failure.
A generic checklist helps a little, but your application’s own trust boundaries should drive the test plan. Start from what would actually hurt if it broke, not from a list someone else wrote for a different app.
Begin by mapping the surface: public routes, authenticated actions, roles, sensitive records, uploads, outbound requests, background workers, and admin functions. Each of those is a boundary worth a test.
Turn boundaries into concrete rows
For each feature, write a row that fixes the precondition, the request, and every expected outcome. A test is only repeatable when the expected state and expected log line are written down, not judged by eye.
Feature: update note
Precondition: Alice logged in, note 42 owned by Bob
Request: PUT /notes/42 { "title": "x" }
Expected: 403, note 42 unchanged, one authorization-failure event
Test more than the happy path. For one feature, cover expected use, misuse, missing proof, alternate encodings, and limit boundaries.
- valid update by owner -> 200, row changed
- update by non-owner -> 403, row unchanged
- update with no session -> 401
- oversized body -> 413
- id as ../ or SQL characters -> rejected, no error leak
A test plan covers login and form validation but misses the background export worker. That worker trusts a stored user ID and writes another tenant’s data into the archive.
A huge generic checklist can hide this product-specific boundary. Start from assets, routes, roles, queues, files, and outbound requests. Keep evidence without storing live secrets.
Build a table with precondition, request, expected response, expected state, and expected event for one feature. Include a background or failure path, run the highest-impact rows, and attach their evidence.
Lesson completed