Silent loss of messages when merging overlapping chat exports in whatstk
Description
Incomplete Comparison with Missing Factors (CWE-1023) in merge_chats() (whatstk/utils/chat_merge.py), reached from BaseChat.merge(), in whatstk from 0.3.0 before 0.9.0. Merging two exports of the same conversation can leave messages out of the merged chat with no indication that anything was removed.
The merge keeps whole the chat whose first message is oldest and appends from the other only the messages that are strictly newer than the last message of the first. Every message of the second chat that falls inside the date range already covered by the first is therefore discarded, whether or not the first chat contains that message.
The two chats are compared on their date ranges and not on the identity of the individual messages, so a message held by only one of the two exports is dropped whenever its date falls inside the range of the other. Both branches of _merge_two_chats() behave this way, and the merged result is returned without a warning and without a count of what was removed.
The party who supplies the exports decides which ranges overlap and what each file contains, so messages can be made to disappear from the merged record by handing over two exports whose windows overlap. Nothing needs to be malformed: both files are valid exports and each is complete on its own.
This matters for the uses the project documents. Two exports of one conversation, taken at different times or from different participants' phones, are exactly the case the function exists for, and the merged output is what a forensic review or a training corpus would then be built on.
Vulnerability type (CWE)
- CWE-1023: Incomplete Comparison with Missing Factors
Affected versions
whatstk from 0.3.0 before 0.9.0. chat_merge.py ships for the first time in 0.3.0, and the discarding behaviour is present from that release: the original implementation compared the DataFrame index and from 0.4.0 it compares the date column, with the same effect either way. Default status: unaffected.
Reproduced on 0.8.1 from PyPI with two exports of one chat overlapping by five days, where the shadowed export held three messages the other did not: 11 distinct messages across the two inputs, 8 rows in the merged output, 3 messages lost. The same inputs on 0.9.0 produce all 11.
Score (CVSS 4.0)
Low (1.8)
CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
The attack vector is Local because the library reads export files on the machine that runs it. Privileges required are None, since the party supplying the exports needs no access to that machine. User interaction is Active because the merge has to be invoked on the two files. Attack requirements are Present because several conditions outside the attacker's control have to coincide: two exports of the same conversation must be merged rather than read separately, the messages to be suppressed must be absent from the attacker's own export, and the attacker's export must begin earlier and end later than those messages, which depends on the window of a file produced on someone else's device. Confidentiality is None: nothing is disclosed that the inputs did not already contain. Integrity on the vulnerable system is Low because the merged output is incomplete while both source exports remain intact and the merge can be repeated once the library is updated, so the loss is confined to the derived record. Availability is None because nothing stops working. The subsequent system metrics are None, since what is produced downstream of the merged chat is a consequence of the output rather than an impact on another system.
Solution
Upgrade to whatstk 0.9.0 or later.
Merging the exports with the fixed version reproduces the complete record from the same input files, so a merge performed on an affected version can be redone rather than being permanently lost.
Patch
Released in 0.9.0, through pull request #185 and the release commit 01d66d8. merge_chats() now returns the deduplicated union of every message across the given chats ordered by date, treating two messages as the same when their date, username and text are all equal. Repeated messages are numbered within each chat before concatenating, so a message a user genuinely sent twice is kept twice while the copy of it that a second export of the same conversation contains is collapsed.
Credits
- Robert Mihaila - finder
- Amirreza Fadaeizadeh Bidari - finder
- Xoán M. Otero Jorge - analyst
- Darío Rivas - analyst
- Secur0 CNA - coordinator
- Lucas Rodés-Guirao - remediation developer
Discovery source: External
Official record: CVE-2026-121564