Files
felhom.eu/REPORT-offsite-append-only-2026-10-03.md
T
admin 9268d9933b
gates / gates (push) Successful in 29s
R-436 measured on the provider: append-only forced key HOLDS (403 on every delete), but the sub-account password defeats it (R-820); design proposal + ep0 options
Spike, no product change. Venue u629488-sub4 (tester-1, operator ruling); scratch repo removed,
authorized_keys restored byte-identical. Closed R-436 (due-check cleared), R-430. Opened R-820,
R-821, R-822. R-95 and R-342 updated. 07 §D [FACT] block. STATUS: two operator decisions.
Register 326 -> 327.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-03 13:40:35 +02:00

6.3 KiB
Raw Permalink Blame History

REPORT — off-site backup safety, step 1: the append-only lock measured on the provider (2026-10-03)

A spike. No product code changed, no release. Evidence, exit test and design: documentation/audits/offsite-append-only-2026-10-03/. Architecture read: 07-backup-architecture.md §8a, threat row 10, §D; 06-offsite-connectivity.md (PBS/tunnel only — it does not describe the restic tier, so the facts went to 07 §D). Baselines (re-verified): felhom.eu f4c5466, controller 0945332 (v0.288.0), register 326 rows, highest id R-819.

The Part table

Part done / not done / changed why
0 — venue changed — u629488-sub4 (tester-1) instead of a new scratch customer operator ruled "use Tester1" in-session. tester-1's box was deleted 2026-09-30; nothing writes there. Credential: the hub's stored tester-1 value, read from a copy of the hub DB on a second operator ruling (copy deleted, value never printed or written to a committed file). A new repo dir spike-r436 only; felhom-repo never read or written
A — the lock done, exit test written first (EXIT-TEST.md) E1–E8 and C1–C2 as stated; locks measured
B — the attacker done, one item lab-only raw-HTTP path escape through the pinned server measured in the lab only — a live HTTP/2 bridge over the forced ssh could not be made to work in the time box
C — design done — DESIGN.md, STATUS decision 0
D — ep0 done — PART-D-ep0-safeguard.md, STATUS decision 0b read only; ep0 not touched
E — records done below

Claims in the brief (and the register) that turned out wrong

  1. "The box holds no sub-account password" — it does not STORE one, but it can obtain it at will: declare needs_credential twice → the hub re-arms the stored value → the box consumes it (R-820).
  2. "A forced command cannot be bypassed by the sub-account itself" — the pinned key cannot; the password can (logs in on ports 22 and 23, rewrote authorized_keys this session).
  3. "The hub cannot prune because of custody" — true for pruning; but the hub can delete: it holds every sub-account password in the clear (R-821).
  4. "Both forget sites must change together" — there are four deleting features on the box: both forget sites, the orphan move-aside (mv) and the abandonment (rm -rf).
  5. rclone in the image (R-436 row: "rclone is not in the controller image today", implying it is needed) — not needed; restic 0.14.0 with -o rclone.program="ssh … rclone" is enough.
  6. R-342's first candidate, a Hetzner Volume snapshot — does not exist.
  7. R-430's model (a locks dir where deletion is refused) — does not describe this transport; the append-only server allows lock deletion and unlock --remove-all works.
  8. The vendor's cited blog (fluix.one) shows the line WITHOUT --append-only; only Hetzner's ticket reply adds it. Copying the blog would give a deleting key.
  9. Held: restic is 0.14.0 (0.14.0-1+b5); restore works through the add-only key.

Part A — results (verbatim refusal)

blob not removed, server response: 403 Forbidden (403) for forget d807418c --prune, forget --keep-last 1 and a real prune (each ~45–48 s of retries, rc=1); snapshot count unchanged; control key: 1 / 1 files deleted. Crash lock: blocks check, not backup; plain unlock prints success and removes nothing; --remove-all removes it. Files: live/E1-E3…, live/E4-E6…, live/C2-A5….

Part B — the attacker table

Route Tried how Result What closes it
Password, port 23 sshpass ssh -p 23 logs in; authorized_keys read and rewritten box never receives it (hub = key registrar)
Password, port 22 sshpass sftp -P 22 logs in (SFTP), .ssh listed same
Box obtains the password source read yes, at will (self-heal re-arm + consume) same — R-820
Pinned key: shell / rm -rf ssh … 'ls', 'rm -rf spike-r436' runs the forced rclone; repo intact — (holds)
Pinned key: sftp / scp / rsync each refused / protocol error — (holds)
Pinned key: port forward -L, then connect administratively prohibited — (holds)
Pinned key: other path, no flag rclone serve restic --stdio felhom-repo pinned dir served, append-only — (holds)
Pinned key: ../ escapes, overwrite raw HTTP (lab) 400 / 403 — (holds; lab rclone)
Pinned key: add junk / new keys/ raw HTTP (lab) allowed quota fills — R-431/quota alarms
Pinned key: future-dated snapshots restic (lab) allowed → retention erases real history poisoning guard — R-822
Any key on port 22 both test keys refused (port 22 takes no OpenSSH key) —
Hetzner API / panel box code read nothing on the box reaches either —
Hub DB operator-tier every sub-account password in clear R-821

A route defeats the lock: the password (R-820). The lock alone is not protection until it is closed.

Records

  • Closed: R-436 (measured; the 2026-10-06 due-check is cleared — the block is now empty), R-430.
  • Opened: R-820 (P2, Security), R-821 (P2, Security), R-822 (P2, Backup). None is P1 by the scale: today the box's own key can already delete (R-95), so none adds harm today.
  • Updated: R-95 (the measurement, the four sites, the proposal; rank untouched), R-342 (options costed).
  • Register: 326 → 327 (register_shape_gate). All felhom.eu gates green.
  • 07 §D: one [FACT] block. STATUS: two decisions in the operator's format.
  • unproven.py --summary: NOT WALKED 35 of 55 — unchanged.

Teardown

  • Provider: authorized_keys restored — sha256 795e7153… before and after, identical; spike-r436 removed; ~/.config/rclone/ (created by the provider's rclone during the test) removed; home is back to .ssh, felhom-repo. Both test keys refused afterwards. (live/TEARDOWN.txt)
  • DooPlex: lab container, network and image removed; test keys, the password file, the hub DB copy and hub page copies deleted from the scratchpad.
  • Hub: nothing changed (two reads).
  • Left as is, on purpose: the tester-1 sub-account password was NOT rotated — the next tester-1 install needs the stored value. R-821 covers why that is itself a risk.