Inventory where plaintext can come to rest (relay#17) #109

Merged
paulhitt merged 1 commits from security/plaintext-at-rest-inventory into main 2026-09-04 21:51:47 +00:00
paulhitt commented 2026-09-04 21:51:25 +00:00 (Migrated from gitlab.com)

Three of the four leads were clean; the second was real and is now fixed.

197 dead tuples held plaintext that an UPDATE had blanked — 'we
deleted it' was true of the live row and false of the heap. buddies 19
against 2 live, group_members 9 against 0. That last one is why lead 1
looked clean: both live groups ARE sealed, and the plaintext member rows
were still in the heap underneath.

The root cause is an autovacuum default worth knowing generally.
Autovacuum triggers at 50 dead rows + 20% of the table, so a two-row table
needs fifty dead tuples before it is ever looked at — meaning the SMALLEST
tables, which here are the ones holding account and contact data, were
never vacuumed at all. buddies had an empty last_autovacuum.

Fixed in two parts because neither suffices alone: per-table thresholds
(5 rows, scale factor 0) bound the accumulation, and a weekly VACUUM FULL
erases it. Plain autovacuum only marks space reusable — the bytes
survive until overwritten. Only a rewrite removes them.

197 -> 0, relay healthy throughout.

Clean: attachments has no content column at all, all 20 on-disk blobs are
opaque under a magic-number scan, the legacy non-E2E upload route no
longer exists (one upload route remains), and the legacy messages table is
empty.

Recorded as NOT covered: the WAL archive and base backups hold the old
tuples and the vacuum does not reach them. They are encrypted, so it is
bounded rather than open — but retention, not the vacuum, is what decides
when that ends.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

Three of the four leads were clean; the second was real and is now fixed. **197 dead tuples held plaintext that an UPDATE had blanked** — 'we deleted it' was true of the live row and false of the heap. `buddies` 19 against 2 live, `group_members` 9 against 0. That last one is why lead 1 looked clean: both live groups ARE sealed, and the plaintext member rows were still in the heap underneath. **The root cause is an autovacuum default worth knowing generally.** Autovacuum triggers at 50 dead rows + 20% of the table, so a two-row table needs fifty dead tuples before it is ever looked at — meaning the SMALLEST tables, which here are the ones holding account and contact data, were never vacuumed at all. `buddies` had an empty `last_autovacuum`. Fixed in two parts because neither suffices alone: per-table thresholds (5 rows, scale factor 0) bound the accumulation, and a weekly VACUUM FULL erases it. Plain autovacuum only marks space reusable — the bytes survive until overwritten. Only a rewrite removes them. 197 -> 0, relay healthy throughout. Clean: attachments has no content column at all, all 20 on-disk blobs are opaque under a magic-number scan, the legacy non-E2E upload route no longer exists (one upload route remains), and the legacy messages table is empty. Recorded as NOT covered: the WAL archive and base backups hold the old tuples and the vacuum does not reach them. They are encrypted, so it is bounded rather than open — but retention, not the vacuum, is what decides when that ends. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
paulhitt commented 2026-09-04 21:51:25 +00:00 (Migrated from gitlab.com)

assigned to @paulhitt

assigned to @paulhitt
paulhitt (Migrated from gitlab.com) merged commit 045112d1b6 into main 2026-09-04 21:51:47 +00:00
paulhitt commented 2026-09-04 21:51:48 +00:00 (Migrated from gitlab.com)

mentioned in commit 045112d1b6

mentioned in commit 045112d1b6c6169671c0ea56d801bfa9c4166c92
paulhitt commented 2026-09-04 21:51:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #17

mentioned in issue #17
paulhitt commented 2026-09-09 18:59:02 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
Sign in to join this conversation.