# Threat model This page documents what restic-manager defends against, what it doesn't, and the trust assumptions a deployment is making. The canonical version lives in [`spec.md`](https://gitea.dcglab.co.uk/steve/restic-manager/src/branch/main/spec.md) §11; the summary here is shaped for operators rather than implementers. ## Trust boundaries ``` ┌──────────────────────────────────────────┐ │ TRUSTED zone │ │ ┌─────────────┐ ┌──────────────┐ │ │ │ Operator's │ │ Reverse │ │ │ │ browser │◄──►│ proxy │ │ TLS terminates here │ └─────────────┘ └──────┬───────┘ │ └────────────────────────────┼─────────────┘ │ HTTP, plaintext │ (loopback or trusted LAN) ┌────────────────────────────▼─────────────┐ │ Server (control plane) │ └────────────┬─────────────────────────────┘ │ outbound WebSocket (TLS to clients via proxy) │ — bearer-authenticated ┌────────────▼──────────────┐ │ Agent (per host) │ ◄── attacker model: assume one └────────────┬──────────────┘ endpoint can be compromised │ subprocess ▼ restic ──▶ repository (rest-server / S3 / SFTP / …) ``` ## What we defend against ### Network attacker between operator and server - HTTPS via the reverse proxy is the only operator-facing surface on a sane deployment. - `RM_COOKIE_SECURE=true` (default) means the session cookie refuses to ride a non-HTTPS connection. - `RM_TRUSTED_PROXY` gates whether `X-Forwarded-*` is honoured; a bypassing request can't spoof the client IP. ### Compromised agent host - The agent's bearer token can dispatch commands **only on its own host**. It can't read other hosts' state, dispatch jobs on other hosts, or escalate within the control plane. - If you suspect a host compromise: 1. Disable the agent's host row from **Hosts → Delete** (cascades the bearer hash). 2. Rotate the repo credential at the rest-server / object store side. 3. Audit-log lists every action that bearer ever drove. ### DB compromise without the secret key - Repo credentials are AEAD-encrypted at rest. A DB dump alone doesn't expose them. - Agent bearer **hashes** are leaked; that's enough to authenticate as any agent until you revoke. A rotation procedure is just "delete + re-enrol" today. - Operator passwords are bcrypt-hashed; OIDC users have no password to leak. - Session tokens are hashed; an attacker can't replay a session from a DB dump. ### DB compromise WITH the secret key The attacker can decrypt every credential. Treat `secret.key` with the same care as a password manager database. Back it up to a separate vault, not to the same Docker volume as the database. ### Forget/prune as a DoS vector - The everyday backup credential cannot prune (append-only). - The admin credential is only pushed to the agent at the moment of dispatch and discarded after the job ends. - Compromise of a single agent host does **not** grant prune rights — at worst the attacker gets fresh write access until the credential is rotated. ### Operator-side typo or bad copy-paste - Repo credentials are stored encrypted; mis-typed creds fail fast on the next `restic` invocation rather than silently corrupting state. - NS-03 added auto-init: the first dispatched job after creds change runs `restic init`, surfaces the error eagerly under the host's vitals strip if the creds are bad, and resets the host's `repo_status` so the operator can retry without hunting through job logs. ## What we don't defend against - **Insider threat at the maintainer level.** A malicious maintainer can publish a backdoored container; SBOM / signing infrastructure (Phase 6 candidate) would help here but isn't shipped today. - **Supply chain.** We pin module versions (`go.sum`) and pin the Tailwind binary's release tag, but a compromise in one of those upstreams would land here. - **Side-channel via restic itself.** A bug in restic that enables snapshot-content disclosure is restic's problem; the control plane doesn't see snapshot bytes either way. - **DoS via resource exhaustion** without the recommended reverse-proxy / rate-limit in front. Don't expose the server's HTTP port to the public internet directly.