Keep off-the-record messages out of the outbox on disk #170

Merged
paulhitt merged 1 commits from fix/off-the-record-outbox into main 2026-09-22 11:53:30 +00:00
paulhitt commented 2026-09-22 11:47:14 +00:00 (Migrated from gitlab.com)

An outbox entry carries to and text. That is the message, not a note
about it, and saveOutbox writes the whole queue to the vault on every
enqueue, so an off-the-record send queued for later was on disk in
plaintext the moment it was typed. §6.28 says the conversation lives in
memory while the app is open and is gone when it closes.

Two paths reach it: Session.scheduleSend for a send with a time on it,
and Session.enqueue for one the recipient could not be handed yet.
Neither checked, and neither had to: the filter belongs on the write, the
same place and for the same reason as saveUnreadThreads.

A group entry carries thread and goes to a member whose 1:1 thread may
not exist, and a group has no off-the-record setting, so it passes
through.

Dropping a queued send is the promise rather than the cost of it. The
thread is not kept either, so a message that survived would arrive into a
conversation the app no longer has, and it retries for as long as the app
is open, which is as long as the conversation exists.

Falsified: without the filter two of the four tests go red, report XML
tests="4" skipped="0". Engine 711 and client 469 green, with a
flamenet-e2e checkout linked beside the worktree so the vector tests can
find the published vectors.

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

An outbox entry carries `to` and `text`. That is the message, not a note about it, and `saveOutbox` writes the whole queue to the vault on every enqueue, so an off-the-record send queued for later was on disk in plaintext the moment it was typed. §6.28 says the conversation lives in memory while the app is open and is gone when it closes. Two paths reach it: `Session.scheduleSend` for a send with a time on it, and `Session.enqueue` for one the recipient could not be handed yet. Neither checked, and neither had to: the filter belongs on the write, the same place and for the same reason as `saveUnreadThreads`. A group entry carries `thread` and goes to a member whose 1:1 thread may not exist, and a group has no off-the-record setting, so it passes through. Dropping a queued send is the promise rather than the cost of it. The thread is not kept either, so a message that survived would arrive into a conversation the app no longer has, and it retries for as long as the app is open, which is as long as the conversation exists. Falsified: without the filter two of the four tests go red, report XML `tests="4" skipped="0"`. Engine 711 and client 469 green, with a flamenet-e2e checkout linked beside the worktree so the vector tests can find the published vectors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
paulhitt commented 2026-09-22 11:47:14 +00:00 (Migrated from gitlab.com)

assigned to @paulhitt

assigned to @paulhitt
paulhitt commented 2026-09-22 11:53:30 +00:00 (Migrated from gitlab.com)

mentioned in commit bafb44352c

mentioned in commit bafb44352c41f250b16340d2e6b42bdbeb2b2ab7
paulhitt (Migrated from gitlab.com) merged commit bafb44352c into main 2026-09-22 11:53:30 +00:00
Sign in to join this conversation.