Files
felhom.eu/documentation/audits/night-burndown-2026-10-06/design-R-822.md
T

6.2 KiB
Raw Blame History

R-822 — fake snapshots steering the off-site retention — design proposal (burn-down night 2026-10-06, no code)

Baselines read: felhom.eu 8e2dc204, felhom-controller 5e7522e023 (v0.301.0), hub v0.140.0. Architecture: 07-backup-architecture.md §246 (off-site deletion custody, decisions 68–69) and threat row 10 (§1298); audits/offsite-append-only-2026-10-03/DESIGN.md §3 Option 1.

1. The problem

The box's off-site key can only ADD (decision 69). Someone holding that key can add snapshots with any date and make the box's own honest pruner delete the real ones. Measured in the lab 2026-10-03 (restic 0.14.0, rclone --append-only): 13 empty future-dated snapshots made the policy --keep-daily 7 --keep-weekly 4 --keep-monthly 6 select all 3 real snapshots (audits/offsite-append-only-2026-10-03/lab/C3-retention-poisoning.txt). The guard (controller v0.289.0, tuned v0.290.0 and v0.294.0) refuses that shape. What is left (read in source tonight, not measured): a fake dated in the PAST, inside a week or month that already holds a real keep, and newer than that real snapshot, wins the bucket. The real snapshot then falls out of the plan as an ordinary old removal. The guard cannot tell it from honest retention.

2. What the code does today (read in source)

  • The guard runs on the box before any forget, inside a hub window (felhom-controller/controller/internal/backup/offbox_window.go:21-38, offsiteGuard :131-158). It refuses: a future date (:133-135); a date after the window opened (:136-138); a plan that removes a snapshot younger than 7 calendar days and not superseded the same day (:141-149); a plan above MaxRemove (:152-154).
  • A YOUNG real snapshot superseded the same day by a newer one is only EXCLUDED, then removed in a later window once old (:142-145, R-824). So a same-day fake, planted after the night run, also wins the daily bucket — later.
  • MaxRemove = half of the count, at least 5 (felhom.eu/hub/internal/offsitekeys/service.go:240-246). So the old real history goes in about two windows.
  • The hub's after-window check compares counts the BOX reports: countBefore comes with the box's request (service.go:284), CountAfter with its close report (service.go:343). Fakes added between windows keep the count level, so the drop alarm (EventWindowDrop, :350) does not fire.
  • The same exposure already exists without any fake: during a window the box's own key may delete (service.go:303, the deleting line). A box that is broken into at window time can delete everything and report any counts. Decision 68 accepted that, with the count check as the backstop.

Result, inferred from the above: an attacker who held the box's key once can, over about two weekly windows, shrink the real off-site history to the last 7 days. Nobody is alarmed. It needs the attacker to plant fakes; it does not need the attacker to still be there.

3. Options

A. Accept the residual and close the row. Record it in 07 §246 as a stated limit of decision 68.

  • Costs: nothing to build.
  • Can go wrong: the slow, silent loss above. It is the same class of loss decision 68 already accepts for a broken-into box in a window (box-reported counts), so A adds no NEW weakness — it names one.

B. The hub keeps its own snapshot list (no repository password needed). The hub already logs in to each sub-account daily (the authorized_keys audit). It also lists <repo>/snapshots/: file names (= snapshot ids) and their upload times on the provider. The hub then: (1) counts before and after each window ITSELF, not from the box; (2) alarms when snapshot files appear that no box run explains (a run reports its time; a file uploaded outside a run, or more files than runs, is the injection signature); (3) refuses to open a window while such files exist.

  • Costs: hub only, about one day. One more SFTP listing per customer per day. Custody unchanged (07 §8a): the hub sees file names and sizes, never contents.
  • Can go wrong: a manual run, a catch-up run or a crash retry must be counted as "a run", or false alarms. Not measured: whether the provider's restricted shell gives upload times (ls -l on port 23) — measure on the scratch sub-account.
  • It also closes the box-trusted count in decision 68's backstop — worth having on its own.

C. A stricter box guard (refuse when a keep is empty or tiny). Rejected: a fake can point at a real snapshot's tree with another date, at no cost. The guard runs on the box that the attacker held.

4. The pick — PROPOSAL for the operator, not a decision

A now, B as the next build. Close R-822 as a stated, bounded limit of decision 68 (the loss is the last-7-days floor, at ≤ half the snapshots per weekly window), and file B as its own row: "the hub trusts the box's own count before and after a window". B is the real defence for both the fake-snapshot case and the broken-into-box-in-a-window case. It needs a measurement first, so it is not tonight's work.

5. First slice and its proof (for B)

  • Measure first, on the scratch sub-account: ls -l <repo>/snapshots/ through the password login on port 23 — does it list names and upload times? (Read only.)
  • Build: hub offsitekeys gets ListSnapshots(ctx, target, pw) ([]SnapshotFile, error); OpenWindowFor and CloseWindowFor take the counts from it, not from the box (the box's numbers are logged beside).
  • Red test first (must FAIL today): a fake registrar whose listing holds 17 files before and 3 after; the box reports 17 → 15. Assert EventWindowDrop fires. Today it does not (the hub uses the box's 15).
  • Live proof on scratch 9202's sub-account: open a one-shot window; the hub's own before/after counts appear in its log and match restic snapshots run on the box. Control from a different channel: the provider's listing read by hand over SFTP.

6. Open questions for the operator

  1. Accept the residual (an attacker who once held a box's key can shrink its off-site history to 7 days over about two weeks, silently) and close R-822? If you do nothing: the row stays open; nothing changes on the boxes.
  2. Build B (the hub counts the snapshots itself, about a day of work, after one read-only measurement)? If you do nothing: the window's alarm keeps trusting the numbers the box sends.