[libtacky / Calls] Disabled “Calls from Unknown” still leaks presence via <ringing/> #15

Open
opened 2026-11-05 09:51:55 +00:00 by fmodf · 1 comment

Moved from another-im/anotherim-swift#12, reported by @asemeniv on 2026-11-04. The evidence archive is attached below.

[libtacky / Calls] Disabled “Calls from Unknown” still leaks presence via
Severity: Middle
Issue ID: CL-01r · QA cases: SEC-126, CONF-006
Date: November 3, 2026

Summary
When “Calls from Unknown” is disabled, an unknown caller can still receive a Jingle Message Initiation (JMI) response before the call is rejected.

This reveals that the target device is currently online and exposes its full JID, including the resource. The behavior can be repeated.

No IP address, ICE candidates, media, or Jingle session details were disclosed, and the call was not displayed to the user.

The issue affects:

libtacky: tacky/xmpp/modules/calls.tcl
iOS application: AnotherIM/Calls/CallsModel.swift
Environment
Item Details
Application anotherim-swift c5fdeae, branch feat/ui-redesign, build 93
libtacky 2f42a2f2 / v1.2.0-beta2
Server ejabberd 26.09 with configuration from anotherim-server b9e675d
Target main@example.local, libtacky application profile
Caller stranger@example.local, not in the target's roster
Test environment Level 1 cloud-based environment
Note: The issue was reproduced in the test environment but has not yet been reproduced on a physical device running TestFlight build 93.

Preconditions
“Calls from Unknown” is disabled:

AppPrivacySettings.callsFromUnknown = false

messages_from_strangers 1 is enabled.

The caller is not in the target's roster.
In the test environment, the agent immediately rejects every incoming call after receiving calls , matching CallsModel.swift:594-600.

Steps to Reproduce
Run:

bash setup/run_all.sh calls

Or:

python3 checks/run_calls.py
An unknown caller sends an audio JMI (XEP-0353) to the target's bare JID.
The target receives calls and immediately rejects the call with decline.
Inspect rig/stanzas_full.log.
Expected Behavior
With “Calls from Unknown” disabled, an unknown caller should not receive a response that reveals whether the target device is online.

A silent drop would be consistent with the existing behavior when messages_from_strangers 0, where the proposal is dropped without a response (CL-02 PASS).

This is a security recommendation, not an explicitly defined product requirement. However, XEP-0353 notes that exchanging messages with another entity can effectively leak presence information, and is intended for a device that has started ringing.

Actual Behavior
Run b3f825a1 shows:

Lines 128–129: the proposal is delivered to main@example.local/another.im (iOS).cA6y9cKn7V.
Lines 130–131: the target sends:

The from attribute exposes the target's full JID:

main@example.local/another.im (iOS).cA6y9cKn7V
Lines 132–133: the call is subsequently rejected with .
Agent events confirm the sequence:

call → call -outcome declined → callreject done
No Jingle session, ICE candidates, or IP addresses were sent.

Reproduction rate: 2/2 (b3f825a1 and 6b6e900b).

Evidence:

evidence/stanzas_str_b3f825a1.txt
evidence/events_b3f825a1.txt
evidence/stanzas_str_6b6e900b.txt
Root Cause
In tacky/xmpp/modules/calls.tcl:2253-2256, HandleJmiPropose sends :

SendJmi $from $sid [BuildJmiMessage $from ringing $sid 0]
Only afterwards does libtacky notify the application via emit calls .

The application's “Calls from Unknown” check occurs later in AnotherIM/Calls/CallsModel.swift:594-600, when it receives the incoming-call event. By that point, has already been sent.

The existing stranger-handling logic in calls.tcl:2154-2179 is controlled by messages_from_strangers; libtacky has no equivalent call-specific privacy setting.

Relevant source excerpts:

evidence/calls.tcl_2140-2257.txt
evidence/CallsModel.swift_575-605.txt
Suggested Fix
Either:

Pass a call-specific stranger policy to libtacky (for example, calls_from_strangers 0) and silently drop unknown proposals before sending ; or
Defer sending until after the application has evaluated the caller and accepted the incoming call.
The implementation choice is left to the development team.

Verification After Fix
With “Calls from Unknown” disabled:

an unknown caller should not trigger a visible incoming call;
the caller should not receive ;
the response should not expose the target's full JID.
Test from an account outside the target's roster and inspect the caller's XML console.

Evidence and Status
Test report:

autotests/metadata-audit/results/2026-11-03_calls_auth_contacts.md, section 2.

Complete evidence archive:

_claude_tmp/evidence_calls_auth_contacts_2026-11-03.tgz

Verified: stanza behavior and agent events from two runs; relevant source code in libtacky 2f42a2f2 and anotherim-swift c5fdeae.

Not verified: behavior on a physical iPhone; video-call behavior; behavior when the application is not running.

> Moved from another-im/anotherim-swift#12, reported by @asemeniv on 2026-11-04. The evidence archive is attached below. [libtacky / Calls] Disabled “Calls from Unknown” still leaks presence via <ringing/> Severity: Middle Issue ID: CL-01r · QA cases: SEC-126, CONF-006 Date: November 3, 2026 Summary When “Calls from Unknown” is disabled, an unknown caller can still receive a Jingle Message Initiation (JMI) <ringing/> response before the call is rejected. This reveals that the target device is currently online and exposes its full JID, including the resource. The behavior can be repeated. No IP address, ICE candidates, media, or Jingle session details were disclosed, and the call was not displayed to the user. The issue affects: libtacky: tacky/xmpp/modules/calls.tcl iOS application: AnotherIM/Calls/CallsModel.swift Environment Item Details Application anotherim-swift c5fdeae, branch feat/ui-redesign, build 93 libtacky 2f42a2f2 / v1.2.0-beta2 Server ejabberd 26.09 with configuration from anotherim-server b9e675d Target main@example.local, libtacky application profile Caller stranger@example.local, not in the target's roster Test environment Level 1 cloud-based environment Note: The issue was reproduced in the test environment but has not yet been reproduced on a physical device running TestFlight build 93. Preconditions “Calls from Unknown” is disabled: AppPrivacySettings.callsFromUnknown = false messages_from_strangers 1 is enabled. The caller is not in the target's roster. In the test environment, the agent immediately rejects every incoming call after receiving calls <Incoming>, matching CallsModel.swift:594-600. Steps to Reproduce Run: bash setup/run_all.sh calls Or: python3 checks/run_calls.py An unknown caller sends an audio JMI <propose/> (XEP-0353) to the target's bare JID. The target receives calls <Incoming> and immediately rejects the call with decline. Inspect rig/stanzas_full.log. Expected Behavior With “Calls from Unknown” disabled, an unknown caller should not receive a response that reveals whether the target device is online. A silent drop would be consistent with the existing behavior when messages_from_strangers 0, where the proposal is dropped without a response (CL-02 PASS). This is a security recommendation, not an explicitly defined product requirement. However, XEP-0353 notes that exchanging messages with another entity can effectively leak presence information, and <ringing/> is intended for a device that has started ringing. Actual Behavior Run b3f825a1 shows: Lines 128–129: the proposal is delivered to main@example.local/another.im (iOS).cA6y9cKn7V. Lines 130–131: the target sends: <ringing xmlns='urn:xmpp:jingle-message:0' id='str-b3f825a1'/> The from attribute exposes the target's full JID: main@example.local/another.im (iOS).cA6y9cKn7V Lines 132–133: the call is subsequently rejected with <reject><reason><decline/></reason></reject>. Agent events confirm the sequence: call <Incoming> → call <Ended> -outcome declined → callreject done No Jingle session, ICE candidates, or IP addresses were sent. Reproduction rate: 2/2 (b3f825a1 and 6b6e900b). Evidence: evidence/stanzas_str_b3f825a1.txt evidence/events_b3f825a1.txt evidence/stanzas_str_6b6e900b.txt Root Cause In tacky/xmpp/modules/calls.tcl:2253-2256, HandleJmiPropose sends <ringing/>: SendJmi $from $sid [BuildJmiMessage $from ringing $sid 0] Only afterwards does libtacky notify the application via emit calls <Incoming>. The application's “Calls from Unknown” check occurs later in AnotherIM/Calls/CallsModel.swift:594-600, when it receives the incoming-call event. By that point, <ringing/> has already been sent. The existing stranger-handling logic in calls.tcl:2154-2179 is controlled by messages_from_strangers; libtacky has no equivalent call-specific privacy setting. Relevant source excerpts: evidence/calls.tcl_2140-2257.txt evidence/CallsModel.swift_575-605.txt Suggested Fix Either: Pass a call-specific stranger policy to libtacky (for example, calls_from_strangers 0) and silently drop unknown proposals before sending <ringing/>; or Defer sending <ringing/> until after the application has evaluated the caller and accepted the incoming call. The implementation choice is left to the development team. Verification After Fix With “Calls from Unknown” disabled: an unknown caller should not trigger a visible incoming call; the caller should not receive <ringing/>; the response should not expose the target's full JID. Test from an account outside the target's roster and inspect the caller's XML console. Evidence and Status Test report: autotests/metadata-audit/results/2026-11-03_calls_auth_contacts.md, section 2. Complete evidence archive: _claude_tmp/evidence_calls_auth_contacts_2026-11-03.tgz Verified: stanza behavior and agent events from two runs; relevant source code in libtacky 2f42a2f2 and anotherim-swift c5fdeae. Not verified: behavior on a physical iPhone; video-call behavior; behavior when the application is not running.
Owner

Fixed in libtacky 1.2.0-rc3. A new installation setting calls_from_strangers (0 or 1) is the stranger rule for a call alone; unset, it follows messages_from_strangers, so nothing changes until the app writes it. At 0 a propose from a stranger (the same judgement as for a message: no roster subscription, not a user of a gateway we are subscribed to, not a server, component or room of ours) is dropped before anything goes back: no <ringing/>, no <reject/>, no calls <Incoming>, no missed-call row; it is reported as message <Blocked> with reason stranger, as a message is. An app with a "Calls from unknown" switch should write the setting from it. The call tier (the phone locked, the store shut) still rings everyone: it has no roster to judge by, and the push already rang the phone.

Fixed in libtacky 1.2.0-rc3. A new installation setting `calls_from_strangers` (`0` or `1`) is the stranger rule for a call alone; unset, it follows `messages_from_strangers`, so nothing changes until the app writes it. At 0 a propose from a stranger (the same judgement as for a message: no roster subscription, not a user of a gateway we are subscribed to, not a server, component or room of ours) is dropped before anything goes back: no `<ringing/>`, no `<reject/>`, no `calls <Incoming>`, no missed-call row; it is reported as `message <Blocked>` with reason `stranger`, as a message is. An app with a "Calls from unknown" switch should write the setting from it. The call tier (the phone locked, the store shut) still rings everyone: it has no roster to judge by, and the push already rang the phone.
Sign in to join this conversation.
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/libtacky#15
No description provided.