[iOS / XMPP Links] External pre-authorized invitation links silently add mutual contacts and expose presence and device IP #18
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 / XMPP Links] External pre-authorized invitation links silently add mutual contacts and expose presence and device IP
Severity: Critical
Issue ID: INV-APP, INV-02, INV-04
Date: November 4, 2026
Summary
When AnotherIM opens an external XMPP invitation link (
xmpp:…?roster;preauth=…), it immediately accepts the invitation without displaying a confirmation dialog.A single tap on a link from Safari, another application, or a QR code can therefore establish a mutual contact relationship without explicit user approval.
Once added, the link creator can:
The issue originates in the iOS link handler introduced in build 94. libtacky performs the requested
roster accept_inviteoperation as designed.Testing confirmed:
Important: The invitation and subsequent effects were reproduced in the test environment. Opening external links on a physical iPhone has not yet been tested.
Affected components:
AnotherIM/Shell/AppShell.swift,XMPPLink.swifttacky/xmpp/modules/roster.tcl,jinglefile.tcl,file.tclEnvironment
8f62071, branchfeat/ui-redesign, build 942f42a2f2/ v1.2.0-beta2b9e675dmain@example.local, libtacky application profileinv@example.local, initially absent from recipient's roster192.0.2.2(test environment)Preconditions
mainandinvare not contacts.invgenerates a pre-authorized invitation usingroster invite, equivalent to Add Contact → Invite with a Link.The generated URI has the following format:
xmpp:inv@example.local?roster;preauth=<token>Steps to Reproduce
Run:
SWIFT_SRC=<anotherim-swift-build-94> SERVER_SRC=<anotherim-server> bash setup/run_all.sh run_inviteroster invite.mainusingroster accept_invite -uri <URI>, matching the operation invoked by the iOS external-link handler.inv.invsend a 3,000-byte JET file over Jingle/SOCKS5. Do not perform any action onmain.AppShell.swift.Expected Behavior
Before accepting an external invitation, the application should display the inviter's JID and require explicit confirmation, with an option to cancel.
This would match the existing behavior for:
?rosterlinks, which open the Add Contact sheet.?joinlinks, which display a confirmation dialog.XEP-0379 v0.3.3 §3.4 describes a roster-addition dialog allowing the user to edit or cancel, while §5.4 recommends displaying the JID and proposed display name.
This is a security recommendation based on XEP-0379, not an explicitly documented product requirement.
Actual Behavior
INV-APP — No confirmation in the iOS link handler
AppShell.swift:124-125:The invitation is accepted immediately without presenting a confirmation sheet or dialog.
The application also selects the first configured account (
AppShell.swift:118-120) without asking the user.INV-02 — Mutual contact relationship established
Run
23351bd8(evidence/stanzas_invite_23351bd8.log) recorded:mainsends<presence type='subscribe'>containing the pre-authorization token.mainsends<presence type='subscribed'>without an additional user action.both/bothsubscriptions.Neither account had the other in its roster before invitation acceptance.
INV-03 — Presence disclosure
Line 246 confirms that
invreceives the recipient's available presence after the mutual subscription is established.INV-04 — Automatic file acceptance and IP disclosure
The inviter initiated a JET file transfer:
session-initiate.session-acceptcontaining:The recipient's events immediately transitioned from
activetodone, without anofferedstate requiring user confirmation.This reproduces the IP disclosure mechanism documented in P2P-06.
Reproduction Rate
2/2 successful automated runs:
23351bd8c393904bBoth runs confirmed mutual subscription, presence disclosure, and automatic Jingle acceptance with the recipient's direct IP candidate.
Root Cause
The external-link handler treats a pre-authorized invitation as already approved by the user.
XMPPLink.swift:47classifies?rosterand?subscribelinks containingpreauthas.invitation.AppShell.swift:124-125immediately invokesacceptInvitewithout confirmation.roster.tcl:805-821adds the inviter to the roster, sends a subscription request with the token, and pre-authorizes the reciprocal subscription.jinglefile.tcl:1058-1059, 1075-1080file.tcl:158-159, 681-687The underlying libtacky invitation behavior is intentional for an invitation the user has chosen to accept. The issue is that the iOS external-link handler skips that decision.
Related behavior: The in-app Add Contact → Scan Code flow also accepts invitations immediately (
ComposeModels.swift:189-205), but it is initiated explicitly from the contact-addition screen and includes a warning about mutual contact creation. This report concerns externally opened links only.Suggested Fix
acceptInviteonly after the user selects Add.The existing confirmation behavior for ordinary roster and group invitation links can serve as a reference.
Verification After Fix
Evidence and Status
Evidence:
evidence/stanzas_invite_23351bd8.txtevidence/stanzas_invite_c393904b.txtevidence/stanzas_invite_<run>.logevidence/events_main_<run>evidence/events_inv_<run>evidence/invite_<run>.jsonevidence/AppShell.swift_100-130.txtevidence/XMPPLink.swift.txtevidence/roster.tcl_805-821.txtevidence/jinglefile.tcl_1052-1083.txtAutomated test:
autotests/metadata-audit/checks/run_invite.pyTest report:
results/2026-11-04_invite_ephemeral.md, section 2.Complete evidence archive:
_claude_tmp/evidence_invite_ephemeral_2026-11-04.tgzVerified:
8f62071and libtacky2f42a2f2.Not verified:
onOpenURL.4832765: an external ?roster;preauth= link asks "Add X to your contacts?" with Cancel/Add, and the invitation is accepted only after Add. The account is still the first one; choosing among several remains open.