ADR-006: Security ceiling for secrets at rest
Status: Accepted — (date: 2026-05-30)
Scope note: this ADR records the maintainer-facing rationale for the secrets posture and its honest limit. The user-facing disclosure —
SECURITY.mdand the in-product warning + loud SD-disposal / threat-model guidance in onboarding — is a separate, release-blocking deliverable (Story 6.2 / AR-22). This file is the why; that one is the telling-the-user.
Context
hamradio holds three secrets at rest on the Pi’s SD card: the device password (gates the config Depth), the BrandMeister hotspot/repeater password, and the BrandMeister API key (which grants full SelfCare control — the highest-value secret; pre-mortem M4). The device runs headless, unattended, and must survive power loss and reboot without an operator present — so secrets must be recoverable by the daemon at boot, with no human to type a passphrase.
That reboot-without-a-human requirement collides with strong at-rest secrecy: in v1 there is no TPM/secure element wired in and no boot passphrase. Whatever key protects the secrets has to itself live on the same disk the daemon can read at boot. This is an honest, bounded trade — recorded in architecture.md §"Authentication & Security".
Decision
-
Device password:
argon2id(Argon2id, OWASP-tuned to ~100—250 ms on the Pi); only the PHC string is stored, in redb. Never recoverable — password reset is factory reset (boot-partitionRESETsentinel). -
BM password + API key:
systemd-credsencrypted credentials (LoadCredentialEncrypted=, AES-256-GCM at rest, root-0600host key, plaintext only in RAM), wrapped insecrecy::SecretStringin memory (noDebugleak; never logged, never serialized into config or errordetails). -
BM API key specifically: held opt-in, transient-by-default — kept only for the duration of a SelfCare operation, re-entry preferred over storage;
systemd-credspersistence is used only when the user opts in (M4 — minimize the blast radius of the highest-value secret). -
Optional hardening: TPM2-HAT sealing offered as an opt-in for users who want to raise the ceiling.
Consequences
The honest ceiling, stated plainly (this is the load-bearing sentence of the ADR):
Without a TPM or a boot passphrase, this protects against casual disk-read, accidental backup leakage, and a misplaced/binned SD image — it does NOT protect against physical SD-card theft. Anyone with the card and root can read the
systemd-credshost key and decrypt the stored secrets.
Therefore:
-
The protection is real against the common leak paths (a shared backup, a sold/ discarded Pi, a casually copied image) and explicitly not against an adversary in physical possession of the card.
-
The BM API key’s opt-in/transient default means the worst-case secret is usually not on the disk at all — the strongest mitigation is non-persistence.
-
This boundary is disclosed, not hidden — see Story 6.2 (release blocker) and the onboarding SD-disposal guidance.
Reversal-Trigger
-
Adopt TPM2-HAT sealing or a boot-passphrase flow → the ceiling rises (secrets no longer decryptable from the bare card); this ADR is superseded by the one recording that mechanism.
-
Threat-model change that brings physical SD-card theft in-scope (e.g. shipping pre-provisioned devices into untrusted hands) → v1’s posture is insufficient, and the secrets-at-rest design must be redesigned, not patched — hardware-backed sealing becomes a requirement rather than an option.
Why-this-matters-to-a-future-maintainer
"An undocumented safety boundary is a defect with a friendly face." The ceiling is a deliberate, disclosed engineering trade — not an oversight to quietly fix or, worse, to quietly overstate. Two ways a maintainer can damage this:
-
Overstate it. Don’t let copy, UI, or docs imply the secrets are safe against a stolen card. They are not, by design. The user-facing disclosure (Story 6.2) and the SD-disposal guidance are paired commitments to this decision — if you change the posture, change the disclosure in the same breath.
-
Quietly persist the BM API key. The opt-in/transient default is the single most important mitigation (M4) — it keeps the full-SelfCare-control key off the disk in the common case. "Just cache it to save a re-entry" silently removes that protection.
The paired user-facing work lives in Story 6.2 / AR-22 and the onboarding flow; the risk this ADR parachutes is M4 in risk-seam-map.md.