feat(security): ten standing checks the list did not have

Grep across docs/** found zero mentions of security headers, token storage,
account enumeration, audit trail, test environment or rate limiting. The
standing list had four entries and stopped at the boundary of the repository.

Grouped by the question each group answers rather than listed flat, because the
grouping is the argument:

- Authorisation, the three questions login does not answer. Logged-out callers
  refused, objects and lists scoped to the caller, privileged routes checking a
  role. Login is the front door; every room inside needs its own lock.
- What the browser is handed. No secret in the built bundle, session tokens in
  HttpOnly cookies rather than localStorage, a CSP and a frame policy with
  nothing on plain HTTP.
- What a stranger can learn or exhaust. Responses that do not confirm whether an
  account exists, and *(precautionary)* rate limits on authentication and on
  anything costing money per request.
- The compliance bar, which is not the launch bar: a record of who changed what
  and when, and an environment that is not production to test against. Called
  out as a different bar on purpose -- the rest of the list gets a release out
  of the door, those two get it through the first compliance review.

Each entry says what it proves, per this file's own rule that a check whose
purpose is unstated gets skipped the first time it is inconvenient.

closes #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
null 2026-08-17 22:54:48 -05:00
parent 92cc7d148f
commit 9ab57f6c72
1 changed files with 27 additions and 0 deletions

View File

@ -32,6 +32,33 @@ unstated gets skipped the first time it is inconvenient.>
- [ ] Every input that reaches a query or a filesystem path is validated at the boundary
- [ ] Dependencies audited, and any accepted advisory recorded with a reason
### Authorisation — the three questions login does not answer
- [ ] Every authenticated endpoint refuses a logged-out caller — proves the check is on the server, not a hidden button
- [ ] Every object fetched by id is scoped to the caller, and every list endpoint returns only that caller's rows — proves no IDOR, and that a response is not quietly serving the whole table
- [ ] Every privileged route checks a role rather than a session — proves *authenticated* has not been mistaken for *permitted*
### What the browser is handed
- [ ] No secret in the built client bundle, not merely none in the repository — proves the scan reached the artifact users actually receive
- [ ] Session tokens in `HttpOnly` cookies rather than `localStorage` — proves one XSS is not an account takeover
- [ ] Response headers carry a CSP and a frame policy, and nothing is served over plain HTTP — proves clickjacking and injection meet resistance
### What a stranger can learn or exhaust
- [ ] Authentication and reset responses do not reveal whether an account exists — proves the login path cannot be used to enumerate users
- [ ] *(precautionary)* Authentication is rate-limited, as is anything costing money per request, and spend alerts somewhere a person reads — proves a brute-force or a bill cannot run unattended overnight
### The compliance bar, which is not the launch bar
- [ ] There is a record of who changed what, and when — proves the question an auditor or a customer eventually asks can be answered at all
- [ ] There is an environment that is not production to test against
The last two are a different bar from everything above them. The rest of this
list gets a release out of the door; those two get it through the first
compliance review, and it is worth knowing which one this project is building
for before somebody asks.
## What got past this list
<Add an entry whenever a real finding was not caught here, and then add the check