Files
restic-manager/docs/book/src/security/threat-model.md
T
steve bb4ed3502d P5: OSS readiness — docs site, contributor onboarding, e2e harness
P5-01 — Documentation site under docs/book/ rendered with mdBook
(downloaded via Makefile, same static-binary pattern as Tailwind).
Structured chapters: getting started, concepts, operations,
security, reference. `make docs` / `make docs-watch`. Generated
output gitignored.

P5-02 — CONTRIBUTING.md rewritten from placeholder to a full
guide. CODE_OF_CONDUCT.md adapted from Contributor Covenant for a
single-maintainer project. .gitea/issue_template/{bug,feature}.md
and PULL_REQUEST_TEMPLATE.md.

P5-04 — Six README screenshots captured live from a fresh server
bootstrap (login, empty dashboard, add-host, alerts, settings,
audit log). README rewritten to centre the screenshot grid and
link out to the docs site.

P5-05 — SECURITY.md with disclosure policy (3-day ack, 30-day
default window), scope in/out, threat-model summary, operator
hardening checklist. Mirrored as a docs-site chapter.

P5-06 — End-to-end test harness. e2e/compose.e2e.yml brings up
server + sibling Linux agent (alpine + restic) + restic/rest-server.
Agent uses announce-and-approve so Playwright can drive the full
operator flow: bootstrap → login → accept pending → backup →
verify terminal status. Second spec scrapes /metrics to assert
the P6-04 endpoint surface. .gitea/workflows/e2e.yml runs on every
PR; local how-to in docs/e2e.md.
2026-05-08 20:08:23 +01:00

4.9 KiB

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