Project-Template/docs/security/SECURITY.md

2.2 KiB

Security —

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.