# 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.
