Project-Template/docs/security/SECURITY_CHECKLIST.md

75 lines
3.6 KiB
Markdown
Raw Permalink Normal View History

# 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
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>
2026-08-17 22:54:48 -05:00
### Authorisation — the three questions login does not answer
feat(docs): two markers for entries that do not apply everywhere This tree is copied into every project and some of what it carries will not apply to all of them. Rather than several templates, or scaffold profiles the website's conformance reader would have to know about before it could tell a legitimately-absent file from a missing one, entries say so inline. Two markers, on axes that are deliberately not merged: applicability -- *(only for a deployed service)*, *(only where money moves)*, *(only where there are accounts)*. Does this project have to do this at all? provenance -- *(precautionary)*. Was the rule earned here, or borrowed? They are independent, and one word cannot say both. Rate limiting is universally applicable and has never bitten us. Money flowing backwards applies only to projects that take money and is the most common defect in the audits it came from. Applicability tells a reader whether to keep a rule; provenance tells them whether to argue with it. The instruction that travels with the marker is ClaudeQAPlan.md's rule about passes, generalised: if it does not apply, delete it. A pass that never applies is noise; a pass that is always skipped is a lie -- and so is a checklist row, and so is a whole document. Deleting is safe because DOC_TRUST_MAP.md is the trust map: what a project keeps is what it meant to keep. Applied where it was already true: the authorisation group is conditional on having accounts, and the header/TLS and test-environment rows on being a deployed service. A library was being told it lacked a CSP. closes #1 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:55:56 -05:00
*(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.
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>
2026-08-17 22:54:48 -05:00
- [ ] 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
feat(docs): two markers for entries that do not apply everywhere This tree is copied into every project and some of what it carries will not apply to all of them. Rather than several templates, or scaffold profiles the website's conformance reader would have to know about before it could tell a legitimately-absent file from a missing one, entries say so inline. Two markers, on axes that are deliberately not merged: applicability -- *(only for a deployed service)*, *(only where money moves)*, *(only where there are accounts)*. Does this project have to do this at all? provenance -- *(precautionary)*. Was the rule earned here, or borrowed? They are independent, and one word cannot say both. Rate limiting is universally applicable and has never bitten us. Money flowing backwards applies only to projects that take money and is the most common defect in the audits it came from. Applicability tells a reader whether to keep a rule; provenance tells them whether to argue with it. The instruction that travels with the marker is ClaudeQAPlan.md's rule about passes, generalised: if it does not apply, delete it. A pass that never applies is noise; a pass that is always skipped is a lie -- and so is a checklist row, and so is a whole document. Deleting is safe because DOC_TRUST_MAP.md is the trust map: what a project keeps is what it meant to keep. Applied where it was already true: the authorisation group is conditional on having accounts, and the header/TLS and test-environment rows on being a deployed service. A library was being told it lacked a CSP. closes #1 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:55:56 -05:00
- [ ] *(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
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>
2026-08-17 22:54:48 -05:00
### 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
feat(docs): two markers for entries that do not apply everywhere This tree is copied into every project and some of what it carries will not apply to all of them. Rather than several templates, or scaffold profiles the website's conformance reader would have to know about before it could tell a legitimately-absent file from a missing one, entries say so inline. Two markers, on axes that are deliberately not merged: applicability -- *(only for a deployed service)*, *(only where money moves)*, *(only where there are accounts)*. Does this project have to do this at all? provenance -- *(precautionary)*. Was the rule earned here, or borrowed? They are independent, and one word cannot say both. Rate limiting is universally applicable and has never bitten us. Money flowing backwards applies only to projects that take money and is the most common defect in the audits it came from. Applicability tells a reader whether to keep a rule; provenance tells them whether to argue with it. The instruction that travels with the marker is ClaudeQAPlan.md's rule about passes, generalised: if it does not apply, delete it. A pass that never applies is noise; a pass that is always skipped is a lie -- and so is a checklist row, and so is a whole document. Deleting is safe because DOC_TRUST_MAP.md is the trust map: what a project keeps is what it meant to keep. Applied where it was already true: the authorisation group is conditional on having accounts, and the header/TLS and test-environment rows on being a deployed service. A library was being told it lacked a CSP. closes #1 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:55:56 -05:00
- [ ] *(only for a deployed service)* There is an environment that is not production to test against
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>
2026-08-17 22:54:48 -05:00
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 |
| --- | --- | --- |
| <> | <> | <> |