Project-Template/docs/security/SECURITY.md

65 lines
2.2 KiB
Markdown
Raw Normal View History

# Security — <Project>
```
Status: Current
Owner: <who maintains this>
Last reviewed: <YYYY-MM-DD>
Governs: authentication, secret handling, data at rest and in transit
Review trigger: Any new secret, any new external service, any change to auth or storage
```
## What this protects, and from whom
<Name the assets and the adversary. "Security" with no threat model produces
effort spent in the wrong places — a hobby project's real adversaries are a
leaked repository and a stolen laptop, not a nation state, and the controls
differ.>
| Asset | Where it lives | What would it cost to lose |
| --- | --- | --- |
| <> | <> | <> |
## Secrets
- **Nothing secret is committed.** Not in source, not in config, not in a test
fixture, not in a screenshot.
- Secrets arrive from the environment or a secret store, and are read at the
boundary rather than passed around.
- A key stored beside the data it protects protects nothing. If data is
encrypted at rest, say here where the key lives and how it is rotated.
**Check before every release:** no private keys, keystores, `.env` files,
service-account JSON, or real credentials in the tracked tree — including in the
history, which a `git log -p` search covers and a directory listing does not.
## Authentication and authorisation
<Who can do what, and where that is enforced. If the answer is "in the UI", it
is not enforced — say so and fix it.>
## Data in transit
<TLS everywhere it applies. Certificate pinning if it applies. What is sent to
third parties, and what is deliberately not.>
## Third parties
| Service | What it receives | Why that is acceptable |
| --- | --- | --- |
| <> | <> | <> |
Every row is a decision to send someone else's data somewhere. An empty table is
a good table.
## Deliberately out of scope
<What is not defended against, and why that is a reasonable call. Writing it
down converts an unknown gap into a known one, which is the whole difference.>
## Logging
Logs are read by people and shipped to places. Nothing sensitive goes into one —
not a token, not a credential in a URL, not the contents of a user's record. An
error's *name* is almost always enough; its message, from a failed HTTP request,
is often the request URL.