The event-DB sweep marker is monotonic and trusts itself, so a downgrade re-leaks target rows permanently #238

Open
opened 2026-08-20 07:49:37 +02:00 by clawbot · 0 comments
Collaborator

Raised during the third review of #223, which closes #206 by sweeping leaked targets rows out of each per-webhook event DB and recording the sweep in the file's user_version.

The sweep is gated on that marker: user_version >= 1 means swept, and the open returns immediately without looking at the file's contents.

The failure sequence:

  1. This build opens events-{uuid}.db, deletes the leaked targets rows, vacuums, stamps user_version = 1.
  2. An operator runs a PRE-FIX binary against the same DATA_DIR. It has no association-omitting callback, so it writes targets rows — with plaintext credential-bearing config — back into a file that is already marked as swept.
  3. Returning to this build, the marker still reads 1, so the sweep is skipped. Forever. The credentials sit in the event DB and nothing will ever look again.

The old RowsAffected > 0 gate would have caught this, because it inspected the file rather than a marker. The marker is what fixed the round-2 self-healing defect, so this is a genuine trade rather than a regression to undo.

Why this is not milestoned: the README's Downgrading section already declares running an older binary against a newer DATA_DIR unsupported, and names silent divergence as the consequence. This is an instance of exactly that documented hazard. Recording it so the trade is explicit rather than accidental.

Possible remedy, noted as partial: a SELECT count(*) FROM targets even on marked files would catch re-leaked ROWS at trivial cost — one cheap query per open. It would NOT catch recoverable BYTES, since those need the vacuum, and it does not detect a downgrade that wrote rows and then had them deleted by something else. So it narrows the window rather than closing it.

Definition of done (if taken):

  • a downgrade that re-leaks rows into a marked file is detected on the next open by this build, and the file is re-swept
  • the detection cost in steady state is stated and measured, not assumed
  • the README's Downgrading section says explicitly what is and is not recovered
  • a test writes targets rows into a file already at user_version = 1 and asserts the next open removes them and re-vacuums
Raised during the third review of https://git.eeqj.de/sneak/webhooker/pulls/223, which closes https://git.eeqj.de/sneak/webhooker/issues/206 by sweeping leaked `targets` rows out of each per-webhook event DB and recording the sweep in the file's `user_version`. The sweep is gated on that marker: `user_version >= 1` means swept, and the open returns immediately without looking at the file's contents. The failure sequence: 1. This build opens `events-{uuid}.db`, deletes the leaked `targets` rows, vacuums, stamps `user_version = 1`. 2. An operator runs a PRE-FIX binary against the same `DATA_DIR`. It has no association-omitting callback, so it writes `targets` rows — with plaintext credential-bearing `config` — back into a file that is already marked as swept. 3. Returning to this build, the marker still reads 1, so the sweep is skipped. **Forever.** The credentials sit in the event DB and nothing will ever look again. The old `RowsAffected > 0` gate would have caught this, because it inspected the file rather than a marker. The marker is what fixed the round-2 self-healing defect, so this is a genuine trade rather than a regression to undo. Why this is not milestoned: the README's Downgrading section already declares running an older binary against a newer `DATA_DIR` unsupported, and names silent divergence as the consequence. This is an instance of exactly that documented hazard. Recording it so the trade is explicit rather than accidental. Possible remedy, noted as partial: a `SELECT count(*) FROM targets` even on marked files would catch re-leaked ROWS at trivial cost — one cheap query per open. It would NOT catch recoverable BYTES, since those need the vacuum, and it does not detect a downgrade that wrote rows and then had them deleted by something else. So it narrows the window rather than closing it. Definition of done (if taken): - a downgrade that re-leaks rows into a marked file is detected on the next open by this build, and the file is re-swept - the detection cost in steady state is stated and measured, not assumed - the README's Downgrading section says explicitly what is and is not recovered - a test writes `targets` rows into a file already at `user_version = 1` and asserts the next open removes them and re-vacuums
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#238