| title | Security Model and Controls | |
|---|---|---|
| status | Draft | |
| version | 0.2.0 | |
| updated | 2026-07-05 | |
| authors |
|
License notice: This project is licensed under AGPL-3.0-only. See the top-level LICENSE file.
This document defines threat model assumptions, trust boundaries, security controls, and release-gated security criteria.
In scope:
- Unauthorized rig control attempts.
- Session/token abuse and replay.
- Malformed WebSocket payloads and protocol misuse.
- Excessive command rates and resource abuse.
- Misconfiguration risks in split-host deployments.
- GPIO misuse on Raspberry Pi (unsafe pin selection or startup states).
Out of scope for MVP:
- Physical access attacks on Raspberry Pi hardware.
- Nation-state level side-channel attacks.
- Full internet-exposed deployment without VPN/proxy hardening.
- Browser client boundary: untrusted input source.
- Frontend host boundary: trusted deployment component, untrusted network path.
- Backend boundary: authority for authz, validation, rig and GPIO actions.
- Rig/hamlib boundary: external control interface requiring strict command validation.
- GPIO boundary: hardware control surface requiring allowlist and safe default states.
- Network boundary: TLS/WSS and private tunnel constraints for split-host deployment.
- LAN-only: backend and frontend on trusted local network.
- Split-host private network: frontend and backend on separate hosts over WireGuard/Tailscale.
- SSH fallback mode: operator-enabled temporary maintenance path only, disabled by default.
- TLS-only communication for API and WebSocket channels.
- Short-lived session tokens and controlled refresh behavior.
- RBAC enforcement (Admin, Operator, Observer).
- Input validation and bounded payload/frame sizes.
- Rate limiting on control endpoints.
- Tamper-evident audit logs for state-changing operations.
- Split-host private interface binding by default.
- GPIO pin allowlist enforcement and safe startup states.
- Only configured allowlisted GPIO pins may be controlled.
- Non-allowlisted pins are inaccessible to API callers.
- Service startup initializes GPIO to configured safe states.
- GPIO actions are role-gated and auditable.
Mapped IDs:
- FR-GPIO-01
- NFR-SEC-16
- TC-GPIO-01
- TC-SEC-15
A release is blocked if any applicable security test fails.
| Gate | Requirement IDs | Test IDs |
|---|---|---|
| Transport security | NFR-SEC-01 | TC-SEC-01 |
| Session and token safety | NFR-SEC-02, NFR-SEC-12 | TC-SEC-02, TC-SEC-10 |
| Command and payload hardening | NFR-SEC-04, NFR-SEC-05, NFR-SEC-08 | TC-SEC-04, TC-SEC-05, TC-SEC-08, TC-SEC-11 |
| Access control and safety controls | NFR-SEC-07, FR-AUTH-04 | TC-SEC-07, TC-AUTH-04 |
| Split-host security posture | NFR-SEC-13, NFR-SEC-14, NFR-SEC-15 | TC-SEC-12, TC-SEC-13, TC-SEC-14 |
| GPIO safety controls | NFR-SEC-16 | TC-SEC-15 |
- Secrets and private keys are stored in files with mode 0600, owned by the
service user. The backend fails closed on a group/world-accessible
config.toml(seeconfig::Config::load), and the deployment installs it 0600. - No credentials appear in URLs or logs (NFR-SEC-12): the audit log stores only the username/action/params (never passwords), the WS auth token travels in the message body rather than the query string, and tracing never emits secrets.
- No secrets are embedded in container image layers — the config is mounted at runtime, not built in (NFR-SEC-10).
| Secret | Where | Cadence | Procedure |
|---|---|---|---|
| JWT signing secret | In-memory, generated per process start (256-bit CSPRNG) | Every restart; force with a rolling restart | Restart the service; all existing access tokens become invalid and clients re-authenticate. When a persisted signing key is later introduced, rotate it on the same cadence as the TLS key and restart. |
| User password hashes | config.toml (argon2) |
On compromise, on operator change, and at least every 180 days | Regenerate with landline_backend::auth::hash_password, update config.toml (0600), reload. |
| TLS private key | /etc/landline/tls/privkey.pem (0600, nginx) |
Per certificate lifetime (≤ 90 days with Let's Encrypt) | Renew the certificate, replace key+chain (0600), nginx -t && systemctl reload nginx. |
| WireGuard/Tailscale keys | Tunnel host config (0600) | On host decommission or compromise; annual review | Regenerate the keypair, update both peers, systemctl restart wg-quick@wg0. |
Triggers for immediate rotation (any secret): suspected compromise, operator departure, or a lost/retired device. Rotations are recorded in the operations log; a rotation is verified by confirming old material no longer authenticates.
- Add incident response workflow and severity model.
- Add security exceptions register template.
- Add secure default configuration examples per deployment profile (partially
covered by
backend/config.example.tomland thedeploy/profiles).
| Version | Date | Author | Summary |
|---|---|---|---|
| 0.2.0 | 2026-07-05 | DC0SK | Added §8.2 secrets rotation policy (BL-012, closing the last Phase-0 remainder); expanded §8 storage with the enforced 0600 config check and NFR-SEC-10/12 notes. |
| 0.1.1 | 2026-06-26 | DC0SK | Migrated to area-coded FR/NFR/TC ids and new doc-tree frontmatter. |
| 0.1.0 | 2026-05-13 | - | Initial security model and controls document |