75 lines
3.6 KiB
Markdown
75 lines
3.6 KiB
Markdown
# Security checklist — <Project>
|
|
|
|
```
|
|
Status: Current
|
|
Owner: <who maintains this>
|
|
Last reviewed: <YYYY-MM-DD>
|
|
Governs: the checks run before a release, and what each one proves
|
|
Review trigger: A new class of input, a new external service, or a finding that got past this list
|
|
```
|
|
|
|
## Why this is separate from SECURITY.md
|
|
|
|
`SECURITY.md` is the threat model: what is being protected and from whom. It is
|
|
read carefully once and revisited rarely.
|
|
|
|
This is the list somebody actually works through. Keeping them apart means the
|
|
model can stay stable while the checks change, and it means a release checklist
|
|
is short enough to finish rather than a document to skim.
|
|
|
|
## Before a release
|
|
|
|
<Each entry states what it proves, not just what to do. A check whose purpose is
|
|
unstated gets skipped the first time it is inconvenient.>
|
|
|
|
- [ ] <Check> — proves <what>
|
|
- [ ] <Check> — proves <what>
|
|
|
|
## Standing checks
|
|
|
|
- [ ] No secret in the repository, in a log line, or in an error message
|
|
- [ ] Every externally reachable endpoint is either authenticated or deliberately public, and the deliberate ones are listed
|
|
- [ ] 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
|
|
|
|
*(only where there are accounts)* — a project with no users has none of these to
|
|
get wrong, and deletes the group rather than ticking three boxes that mean
|
|
nothing.
|
|
|
|
- [ ] 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
|
|
- [ ] *(only for a deployed service)* 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
|
|
- [ ] *(only for a deployed service)* 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
|
|
that would have caught it. A checklist that never grows is one nobody is honest
|
|
with.>
|
|
|
|
| When | What was missed | The check now added |
|
|
| --- | --- | --- |
|
|
| <> | <> | <> |
|