ProtocolReader discarded the entire chunk containing the newline
delimiter while draining an oversized message, including bytes after
the newline that belong to the next pipelined message. This corrupted
framing for the rest of the connection (affects server and client).
Salvaged bytes are now kept in a _leftover buffer that read_message()
consumes before touching the stream.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- db: cleanup_old_messages(days) purges messages older than N days in
batches; recipients/reads/deliveries/reactions follow via ON DELETE
CASCADE. Returns attachment file_ids no longer referenced by any
surviving message (forwarded copies keep their files) and removes
their image_uploads rows
- server: MESSAGE_RETENTION_DAYS env var (default 0 = keep forever);
hourly cleanup deletes expired messages and securely removes orphaned
attachment blobs from the upload dir
- schema: email_visible now defaults to 0 — previously any logged-in
user who knew a UUID could read another user's email via get_profile
- migrations: SQL script to apply the new default and reset the flag on
existing databases (run manually, see file header)
- docker-compose: document MESSAGE_RETENTION_DAYS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- chat_core: defer one-time-prekey deletion until the first message
decrypts successfully; deleting it on load made the SPK grace-period
retry derive a wrong shared secret and lose the message permanently
- chat_core: fix get_deleted_since params (since -> since_ts) and
response field (message_ids -> deleted_ids) so incremental deletion
sync actually works
- chat_core: route keys_updated pushes into the notification queue
- server: notify contacts with keys_updated when a user uploads a new
SPK or logs in with a new device, so clients invalidate cached key
bundles instead of waiting for the TTL
- server: rate-limit download_stream like other heavy handlers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>