[libtacky / OMEMO] Disappearing message timer is exposed outside OMEMO 2 encryption #20

Closed
opened 2026-11-04 20:59:51 +00:00 by asemeniv · 1 comment
Member

[libtacky / OMEMO] Disappearing message timer is exposed outside OMEMO 2 encryption

Severity: Minor
Issue ID: EPH-01i, EPH-CTRL
Date: November 4, 2026

Summary

libtacky sends the disappearing-message timer (<ephemeral timer='N'/>) as a plaintext child of the XMPP <message/> stanza, outside the OMEMO 2 encrypted payload.

Although the message content remains encrypted, the XMPP server and intermediaries can observe:

  • Whether disappearing messages are enabled.
  • The configured expiration interval.
  • Timer changes and expiration settings associated with individual messages.

XEP-0466 §5 recommends placing the ephemeral element inside the encryption wrapper when one is available. OMEMO 2 supports such a wrapper through XEP-0420, but libtacky does not use it for the timer.

Testing confirmed:

  • The timer is transmitted outside OMEMO 2 encryption.
  • Timer configuration messages also expose the interval in plaintext.
  • The actual message text remains encrypted.
  • The behavior is intentional in the current libtacky implementation.

Affected components:

  • libtacky: tacky/xmpp/modules/ephemeral.tcl
  • iOS: Disappearing Messages settings in chat details

Related issue: EPH-01 (expired message content retained in local history). This is a separate metadata-disclosure issue.

Environment

Item Details
Application anotherim-swift 8f62071, build 94
libtacky 2f42a2f2 / v1.2.0-beta2
Test server ejabberd 26.09, anotherim-server b9e675d
Client main@example.local, libtacky application profile
Peer peer2@example.local
Encryption OMEMO 2 (urn:xmpp:omemo:2)
Verification Level 1 cloud-based test, server-side XMPP stanza logs

Preconditions

  1. main and peer2 are contacts with OMEMO enabled.
  2. The conversation uses OMEMO 2 with XEP-0420 envelope support.
  3. Disappearing Messages is available through the application's chat details.

Steps to Reproduce

  1. Run:

    bash setup/run_all.sh run_ephemeral

  2. Set a 5-second disappearing-message timer using ephemeral set_timer.

  3. Send an OMEMO-encrypted message from main to peer2.

  4. Inspect the outgoing XMPP stanzas in the server log.

  5. Check whether <ephemeral/> appears inside or outside the encrypted payload.

Expected Behavior

For OMEMO 2, the <ephemeral/> element should be placed inside the encrypted XEP-0420 envelope so that the expiration interval is not exposed to the XMPP server.

XEP-0466 v0.1.0 §5 recommends placing this element inside the encryption wrapper when using XEP-0373 or XEP-0420.

This is a privacy recommendation, not an explicitly documented product requirement.

Behavior for OMEMO 0.3, which lacks the same envelope mechanism, may require a separate design decision.

Actual Behavior

Run d09e839c (evidence/stanzas_ephemeral_main_d09e839c.txt) captured the following.

Timer configuration — 14:18:42 UTC

The client sent a plaintext timer configuration stanza containing:

<ephemeral xmlns='urn:xmpp:ephemeral:0' timer='5'/>
<store xmlns='urn:xmpp:hints'/>

Encrypted message — 14:18:44 UTC

The subsequent OMEMO 2 message contained:

<encrypted xmlns='urn:xmpp:omemo:2'>
  ...
</encrypted>
<encryption xmlns='urn:xmpp:eme:0'
            name='OMEMO'
            namespace='urn:xmpp:omemo:2'/>
<ephemeral xmlns='urn:xmpp:ephemeral:0' timer='5'/>
<body>I sent you an OMEMO encrypted message but your client doesn't...</body>

The <ephemeral/> element is a direct child of <message/>, appearing after </encrypted> rather than inside the encrypted envelope.

The visible <body/> contains the standard OMEMO fallback text, not the actual message content.

The test confirmed that the original message text was not transmitted in plaintext.

Reproduction Rate

2/2 successful automated runs:

  • d09e839c
  • 8f7ef5be

Both runs confirmed plaintext exposure of the disappearing-message timer alongside OMEMO 2 encryption.

Root Cause

libtacky deliberately places the ephemeral timer outside the encrypted payload.

ephemeral.tcl:12-24 documents that:

  • <ephemeral/> is sent as a direct child of <message/>.
  • OMEMO 0.3 lacks an encryption envelope suitable for this element.
  • Both OMEMO versions therefore transmit the timer in plaintext.
  • The timer is treated as non-secret metadata, unlike the message content.

The same implementation already supports reading the timer from either the encrypted OMEMO 2 envelope or the outer message stanza.

Consequently, the receiving side supports the recommended placement, but the sending side does not follow the XEP-0466 §5 recommendation for OMEMO 2.

Suggested Fix

For OMEMO 2: Move <ephemeral/> into the encrypted XEP-0420 envelope, following XEP-0466 §5.

The existing receiving implementation already supports extracting the timer from the encrypted envelope.

For OMEMO 0.3 and unencrypted conversations: Preserve the current behavior or document the metadata exposure as a protocol limitation, subject to a separate design decision.

Verification After Fix

  1. Enable Disappearing Messages in an OMEMO 2 conversation.
  2. Send a message with an active expiration timer.
  3. Inspect the outgoing XMPP stanza.
  4. Verify that <ephemeral/> is absent from the plaintext portion of the message.
  5. Verify that the recipient correctly receives and applies the timer from the encrypted envelope.
  6. Repeat after changing the timer value.
  7. Confirm that expiration and message retraction continue to work.
  8. Test interoperability with OMEMO 0.3 clients separately.

For physical-device verification, use build 94 on two iPhones and inspect outgoing stanzas through Settings → Account → Server → XML Console.

Evidence and Status

Evidence:

  • evidence/stanzas_ephemeral_main_d09e839c.txt
  • evidence/stanzas_ephemeral_main_8f7ef5be.txt
  • evidence/run_ephemeral_1.out
  • evidence/run_ephemeral_2.out
  • evidence/ephemeral.tcl_12-24.txt

Automated test:

autotests/metadata-audit/checks/run_ephemeral.py

Test report:

results/2026-11-04_invite_ephemeral.md, section 3.

Verified:

  • Plaintext timer exposure in two automated runs.
  • OMEMO 2 encryption of the actual message content.
  • Relevant implementation in libtacky 2f42a2f2.
  • XEP-0466 §5 recommendation for encryption-wrapper placement.

Not verified:

  • Behavior on a physical iPhone running build 94.
  • Timer handling by OMEMO 0.3 clients such as Conversations or Monal.
  • Whether XEP-0100 gateways expose or process the timer.
# [libtacky / OMEMO] Disappearing message timer is exposed outside OMEMO 2 encryption **Severity:** Minor **Issue ID:** EPH-01i, EPH-CTRL **Date:** November 4, 2026 ## Summary libtacky sends the disappearing-message timer (`<ephemeral timer='N'/>`) as a plaintext child of the XMPP `<message/>` stanza, outside the OMEMO 2 encrypted payload. Although the message content remains encrypted, the XMPP server and intermediaries can observe: - Whether disappearing messages are enabled. - The configured expiration interval. - Timer changes and expiration settings associated with individual messages. XEP-0466 §5 recommends placing the ephemeral element inside the encryption wrapper when one is available. OMEMO 2 supports such a wrapper through XEP-0420, but libtacky does not use it for the timer. **Testing confirmed:** - The timer is transmitted outside OMEMO 2 encryption. - Timer configuration messages also expose the interval in plaintext. - The actual message text remains encrypted. - The behavior is intentional in the current libtacky implementation. **Affected components:** - libtacky: `tacky/xmpp/modules/ephemeral.tcl` - iOS: Disappearing Messages settings in chat details Related issue: EPH-01 (expired message content retained in local history). This is a separate metadata-disclosure issue. ## Environment | Item | Details | |---|---| | Application | anotherim-swift `8f62071`, build 94 | | libtacky | `2f42a2f2` / v1.2.0-beta2 | | Test server | ejabberd 26.09, anotherim-server `b9e675d` | | Client | `main@example.local`, libtacky application profile | | Peer | `peer2@example.local` | | Encryption | OMEMO 2 (`urn:xmpp:omemo:2`) | | Verification | Level 1 cloud-based test, server-side XMPP stanza logs | ## Preconditions 1. `main` and `peer2` are contacts with OMEMO enabled. 2. The conversation uses OMEMO 2 with XEP-0420 envelope support. 3. Disappearing Messages is available through the application's chat details. ## Steps to Reproduce 1. Run: `bash setup/run_all.sh run_ephemeral` 2. Set a 5-second disappearing-message timer using `ephemeral set_timer`. 3. Send an OMEMO-encrypted message from `main` to `peer2`. 4. Inspect the outgoing XMPP stanzas in the server log. 5. Check whether `<ephemeral/>` appears inside or outside the encrypted payload. ## Expected Behavior For OMEMO 2, the `<ephemeral/>` element should be placed inside the encrypted XEP-0420 envelope so that the expiration interval is not exposed to the XMPP server. XEP-0466 v0.1.0 §5 recommends placing this element inside the encryption wrapper when using XEP-0373 or XEP-0420. This is a privacy recommendation, not an explicitly documented product requirement. Behavior for OMEMO 0.3, which lacks the same envelope mechanism, may require a separate design decision. ## Actual Behavior Run `d09e839c` (`evidence/stanzas_ephemeral_main_d09e839c.txt`) captured the following. **Timer configuration — 14:18:42 UTC** The client sent a plaintext timer configuration stanza containing: ```xml <ephemeral xmlns='urn:xmpp:ephemeral:0' timer='5'/> <store xmlns='urn:xmpp:hints'/> ``` **Encrypted message — 14:18:44 UTC** The subsequent OMEMO 2 message contained: ```xml <encrypted xmlns='urn:xmpp:omemo:2'> ... </encrypted> <encryption xmlns='urn:xmpp:eme:0' name='OMEMO' namespace='urn:xmpp:omemo:2'/> <ephemeral xmlns='urn:xmpp:ephemeral:0' timer='5'/> <body>I sent you an OMEMO encrypted message but your client doesn't...</body> ``` The `<ephemeral/>` element is a direct child of `<message/>`, appearing after `</encrypted>` rather than inside the encrypted envelope. The visible `<body/>` contains the standard OMEMO fallback text, not the actual message content. The test confirmed that the original message text was not transmitted in plaintext. ## Reproduction Rate **2/2 successful automated runs:** - `d09e839c` - `8f7ef5be` Both runs confirmed plaintext exposure of the disappearing-message timer alongside OMEMO 2 encryption. ## Root Cause libtacky deliberately places the ephemeral timer outside the encrypted payload. `ephemeral.tcl:12-24` documents that: - `<ephemeral/>` is sent as a direct child of `<message/>`. - OMEMO 0.3 lacks an encryption envelope suitable for this element. - Both OMEMO versions therefore transmit the timer in plaintext. - The timer is treated as non-secret metadata, unlike the message content. The same implementation already supports reading the timer from either the encrypted OMEMO 2 envelope or the outer message stanza. Consequently, the receiving side supports the recommended placement, but the sending side does not follow the XEP-0466 §5 recommendation for OMEMO 2. ## Suggested Fix **For OMEMO 2:** Move `<ephemeral/>` into the encrypted XEP-0420 envelope, following XEP-0466 §5. The existing receiving implementation already supports extracting the timer from the encrypted envelope. **For OMEMO 0.3 and unencrypted conversations:** Preserve the current behavior or document the metadata exposure as a protocol limitation, subject to a separate design decision. ## Verification After Fix 1. Enable Disappearing Messages in an OMEMO 2 conversation. 2. Send a message with an active expiration timer. 3. Inspect the outgoing XMPP stanza. 4. Verify that `<ephemeral/>` is absent from the plaintext portion of the message. 5. Verify that the recipient correctly receives and applies the timer from the encrypted envelope. 6. Repeat after changing the timer value. 7. Confirm that expiration and message retraction continue to work. 8. Test interoperability with OMEMO 0.3 clients separately. For physical-device verification, use build 94 on two iPhones and inspect outgoing stanzas through **Settings → Account → Server → XML Console**. ## Evidence and Status Evidence: - `evidence/stanzas_ephemeral_main_d09e839c.txt` - `evidence/stanzas_ephemeral_main_8f7ef5be.txt` - `evidence/run_ephemeral_1.out` - `evidence/run_ephemeral_2.out` - `evidence/ephemeral.tcl_12-24.txt` Automated test: `autotests/metadata-audit/checks/run_ephemeral.py` Test report: `results/2026-11-04_invite_ephemeral.md`, section 3. **Verified:** - Plaintext timer exposure in two automated runs. - OMEMO 2 encryption of the actual message content. - Relevant implementation in libtacky `2f42a2f2`. - XEP-0466 §5 recommendation for encryption-wrapper placement. **Not verified:** - Behavior on a physical iPhone running build 94. - Timer handling by OMEMO 0.3 clients such as Conversations or Monal. - Whether XEP-0100 gateways expose or process the timer.
Contributor

Moved to another-im/libtacky#17: the fix belongs to libtacky. The evidence archive went with it.

Moved to another-im/libtacky#17: the fix belongs to libtacky. The evidence archive went with it.
fmodf closed this issue 2026-11-05 09:52:15 +00:00
Sign in to join this conversation.
No labels
done
fixed
returned
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
another-im/anotherim-swift#20
No description provided.