[iOS / libtacky / Disappearing Messages] Expired messages remain stored and readable through Edit History #19
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
[iOS / libtacky / Disappearing Messages] Expired messages remain stored and readable through Edit History
Severity: Major
Issue ID: EPH-01, EPH-02, EPH-APP
Date: November 4, 2026
Summary
AnotherIM's Disappearing Messages feature does not permanently delete expired message content from the local database.
Although messages disappear from the chat after their expiration timer, their original text remains accessible through libtacky's
message revisionsAPI, which the iOS application uses for Info → Edit history.This affects both:
The application promises:
However, libtacky defaults to
preserve_history = 1, which retains expired message content as revision history. The iOS application does not override this setting.Anyone with access to an unlocked device may therefore be able to recover supposedly disappeared conversations.
Testing confirmed:
preserve_history = 0removes expired message content in the tested scenario.preserve_history.Important: Local database retention was reproduced in automated tests. Actual visibility through the iOS Edit History interface has not yet been verified on a physical device.
Affected components:
AppEnvironment.swift,MessageCell.swift,ChatDetailsView.swifttacky/xmpp/modules/ephemeral.tcl,messagestore.tclRelated issues: RT-20, RT-21 (deleted messages retained in local history).
Environment
8f62071, branchfeat/ui-redesign, build 942f42a2f2/ v1.2.0-beta2b9e675dmain@example.local, libtacky application profilepeer2@example.local, OMEMO-enabled contactpreserve_historyunset (default:1)Preconditions
mainandpeer2are contacts with OMEMO enabled.ephemeral set_timer, matching the application's Disappearing Messages feature.preserve_historyat its default value.Steps to Reproduce
Run:
SWIFT_SRC=<anotherim-swift-build-94> SERVER_SRC=<anotherim-server> bash setup/run_all.sh run_ephemeralmainsendEPH01-<run>-own secret.message rows,message revisions, andmessagestore archived.peer2sendEPH02-<run>-their secret.mark_own_read.preserve_history.preserve_history 0.Expected Behavior
Once the expiration timer completes:
XEP-0466 v0.1.0 §3.2 requires expired messages to be discarded, while §6 specifies best-effort removal from memory and disk.
This expectation also follows the application's Disappearing Messages description.
Actual Behavior
Run
d09e839c(evidence/run_ephemeral_1.out,evidence/events_main_d09e839c) confirmed the following.EPH-01 — Sent message
The message expired approximately 5 seconds after sending and was retracted from the active chat view.
However, after 9 seconds,
message revisionsstill returned:The original message body remains intact in the revision history.
The iOS application uses this API for Info → Edit history (
MessageCell.swift:155, 237-246).EPH-02 — Received message
After the received message was marked as read, it expired approximately 5 seconds later.
The active message was retracted, but its original text (
EPH02-…-their secret) remained accessible throughmessage revisions.EPH-APP — Application configuration
Build 94 does not configure
preserve_historyin the application code.The relevant libtacky settings are initialized in
AppEnvironment.swift:404-411, leaving the default history-preservation behavior enabled.EPH-OFF — Control test
Repeating the scenario with:
preserve_history 0removed the expired message content from the inspected storage APIs.
This confirms that the retention is caused by the history-preservation configuration rather than a failure of the expiration timer itself.
Reproduction Rate
2/2 successful automated runs:
d09e839c8f7ef5beBoth runs confirmed expiration after approximately 5 seconds and continued availability of original message text through
message revisions.Root Cause
libtacky intentionally retains retracted message content when
preserve_historyis enabled.ephemeral.tcl:9-10, 88-99documents that expired messages are retracted while their original content remains in history whenpreserve_historyis enabled.messagestore.tcl:2033setspreserve_historyto1by default.messagestore.tcl:1250-1255applies retraction while retaining the message row (kept 1).AppEnvironment.swift:404-411).MessageCell.swift:155, 237-246exposes revision history through Info → Edit history, without an identified exception for expired messages.Consequently, expiration hides the active message body but does not erase its original content from local storage.
A related discrepancy exists in expiration timing: libtacky expires sent messages based on their sending timestamp, while the UI description suggests expiration after reading.
Suggested Fix
Preferred approach: Disable history preservation in the iOS libtacky configuration.
Set:
preserve_history = "0"in
AppEnvironmentalongside the other libtacky settings.The EPH-OFF control test confirmed that this removes expired message content from the inspected local storage APIs.
This change also affects message retractions and cleared conversation history (RT-19a, RT-20, RT-21), where equivalent removal behavior was previously confirmed by RT-OFF tests.
Alternative: If preserving revision history is intentional, prevent expired and deleted message content from being displayed through Edit History and revise the UI description accordingly.
However, hiding revision history in the UI alone would not remove sensitive content from the local database.
Verification After Fix
message revisionsand local storage no longer contain expired message bodies.Evidence and Status
Evidence:
evidence/run_ephemeral_1.outevidence/run_ephemeral_2.outevidence/ephemeral_<run>.jsonevidence/events_main_<run>evidence/stanzas_ephemeral_<run>.logevidence/ephemeral.tcl_1-30.txtevidence/ephemeral.tcl_85-100.txtevidence/messagestore.tcl_1248-1256_2030-2034.txtevidence/ChatDetailsView.swift_412-426.txtevidence/MessageCell.swift_155-158_237-248.txtevidence/AppEnvironment.swift_400-412.txtAutomated test:
autotests/metadata-audit/checks/run_ephemeral.pyTest report:
results/2026-11-04_invite_ephemeral.md, section 3.Complete evidence archive:
_claude_tmp/evidence_invite_ephemeral_2026-11-04.tgzVerified:
preserve_history 0removes expired content in the control test.8f62071and libtacky2f42a2f2.Not verified:
4832765(product decision): preserve_history = 0. Edits, deletions and expired messages keep nothing; the first launch after the update clears what earlier builds kept.