[iOS / HTTP Upload] Original filenames from Files are exposed to the XMPP server and S3 storage (XEP-0363) #16

Open
opened 2026-11-04 12:32:30 +00:00 by asemeniv · 1 comment
Member

[iOS / HTTP Upload] Original filenames from Files are exposed to the XMPP server and S3 storage (XEP-0363)

Severity: Major
Issue ID: UP-02 · QA case: MD-FL-01
Date: October 26, 2026 · Updated November 4, 2026

Summary

When a user sends an attachment selected from the iOS Files app, AnotherIM includes the original filename in the XEP-0363 HTTP Upload slot request.

The filename is exposed to the XMPP server and embedded in the S3 object URL, even when the file content is protected by OMEMO encryption.

For example, uploading IMG_4821.HEIC produces:

  • An XMPP upload request containing filename='IMG_4821.HEIC'.
  • S3 GET/PUT URLs containing /IMG_4821.HEIC.
  • An attachment URL subsequently included in the chat message.

This can disclose sensitive metadata, such as document names, to infrastructure operators. In chats without OMEMO, the attachment URL is also visible in plaintext to the XMPP server and potentially stored in MAM.

Affected components:

  • Packages/FeatureConversation/Sources/ConversationUI/ConversationView.swift
  • AnotherIM/XMPP/Attachments/AttachmentService.swift
  • AnotherIM/XMPP/Types/Attachments.swift
  • libtacky: tacky/xmpp/modules/message.tcl, file.tcl

The issue affects files selected from Files and drag-and-drop attachments based on the build 93 source code.

Gallery photos/videos and voice recordings use generated filenames. Camera video filename behavior has not been verified.

Environment

Item Details
Application anotherim-swift c5fdeae, branch feat/ui-redesign, build 93
libtacky 2f42a2f2 / v1.2.0-beta2
Test server ejabberd 26.09, mod_s3_upload from anotherim-server b9e675d
Test environment Level 1 cloud-based test, autotests/metadata-audit
Client libtacky application profile
Verification XMPP upload slot requests and server stanza logs

The test server generates signed upload URLs locally; no actual S3 upload was performed.

The iOS application path was verified through source code, not by executing build 93 on a physical device.

Preconditions

  1. Start the test environment:

    PUSH_WINDOW=0 GRACE_PERIOD=0 CLEAN=1 bash setup/up.sh

  2. Build libtacky from the application submodule:

    TACKY_SRC=<anotherim-swift/tacky> bash setup/build_tacky.sh

  3. Configure main@example.local with the upload service upload.example.local.

Steps to Reproduce

Automated test

  1. Run:

    cd checks && python3 tacky_more.py

  2. The test requests an HTTP Upload slot for IMG_4821.HEIC (2,483,117 bytes, image/heic) using message upload_slot.

  3. Inspect the XMPP stanza log for urn:xmpp:http:upload:0.

  4. Check the filename attribute and returned GET/PUT URLs.

Manual verification on iOS (not yet performed)

  1. On build 93, enable Access from Files.
  2. Open an OMEMO chat and select + → File.
  3. Choose a document with a recognizable filename, such as Contract_Test.pdf, and send it.
  4. Inspect DEBUG application logs for upload_slot, or capture the server-side XMPP upload request.
  5. Repeat with a gallery photo to compare filename handling.

The application log is available only in DEBUG builds. Ordinary Charles interception is insufficient for inspecting the upload PUT because the libtacky iOS HTTP client uses bundled trusted root certificates.

Expected Behavior

The HTTP Upload slot request should use a generated filename, such as <UUID>.pdf, instead of the original local filename.

The original filename should remain available only inside the encrypted message payload when OMEMO is enabled.

This follows QA case MD-FL-01, which expects a randomized filename and application/octet-stream content type in the slot request.

Note: This is a security/privacy recommendation, not an explicitly documented product requirement. Whether to conceal the MIME type requires a separate decision.

Actual Behavior

Run fd861f91 (evidence/stanzas_upload_fd861f91.txt) captured the following request:

<request xmlns='urn:xmpp:http:upload:0'
         content-type='image/heic'
         filename='IMG_4821.HEIC'
         size='2483133'/>

The server returned URLs containing the same filename:

https://s3.invalid/rig/e26656dd6ee5982c46bb8d4cad88225b/IMG_4821.HEIC

The slot request size includes the additional 16-byte GCM authentication tag used for OMEMO attachment encryption.

This confirms that the original filename reaches the XMPP upload service and is incorporated into the storage URL.

The filename may be visible to:

  • XMPP server: Upload slot requests and potentially server logs.
  • S3 storage: Object keys and potentially access logs.
  • Non-OMEMO chats: Plaintext attachment URLs, including potentially archived messages.

The attachment content remains encrypted when OMEMO is used, but the filename is not protected in the upload request.

Reproduction Rate

6/6 successful automated runs:

  • October 26: 4f096705, 53e2a408
  • October 27: 9916fa6b, fb719fdb
  • November 3: fd861f91, 31fa65fa

Both November 3 runs reproduced the issue with libtacky v1.2.0-beta2 and the build 93 source revision.

Root Cause

The iOS attachment pipeline preserves the original filename and passes it unchanged to the HTTP Upload slot request:

  1. ConversationView.swift:379 creates OutgoingFile with name: url.lastPathComponent.
  2. ConversationClient+Live.swift:115,124 forwards the filename through PickedAttachment.file.
  3. Attachments.swift:143-147 sanitizes control characters and path separators but does not anonymize the filename.
  4. AttachmentService.swift:507 passes picked.name to api.messageUploadSlot.
  5. TackyAPI.swift:556 forwards the request to libtacky.
  6. libtacky message.tcl:5185-5205 and file.tcl:2354-2358 insert the supplied filename into the XEP-0363 <request/> stanza.

The temporary local copy uses a UUID directory, but the original filename is retained.

Other attachment sources (source-code assessment):

  • Gallery photos/videos: photo-<timestamp>.<ext> / video-<timestamp>.<ext>.
  • Voice recordings: voice-<UUID>.m4a.
  • Camera photos: photo-<UUID>.jpg.
  • Camera videos: Use a filename obtained from the system-provided media URL; its actual value was not verified.

Suggested Fix

Generate a random upload filename centrally in AttachmentService.performUpload before calling messageUploadSlot.

For example:

  • Upload slot filename: <UUID>.<extension> or <UUID>.
  • Original display filename: Retained only in the message metadata, protected by OMEMO when enabled.

Applying this at the upload-service level would cover Files, drag-and-drop, and other attachment sources consistently.

Separately evaluate whether the upload request should use application/octet-stream rather than the original MIME type. The current MIME type is intentionally preserved to support attachment previews in clients such as Gajim.

Verification After Fix

  1. Select a document named Contract_Test.pdf from Files.
  2. Send it in an OMEMO chat.
  3. Verify that the XMPP upload slot request contains a randomized filename.
  4. Verify that the returned storage URLs do not contain Contract_Test.pdf.
  5. Verify that the recipient can still see the intended display filename.
  6. Repeat for drag-and-drop, gallery media, voice recordings, and camera attachments.
  7. Confirm that attachment upload, download, and decryption continue to work.

Evidence and Status

Evidence directory:

2026-10-26_UP-02_upload_filename/evidence/

November 3 evidence:

  • stanzas_upload_fd861f91.txt
  • stanzas_upload_31fa65fa.txt
  • stanzas_full_fd861f91.log
  • stanzas_full_31fa65fa.log
  • tacky_more_fd861f91.json
  • tacky_more_31fa65fa.json

Test report:

autotests/metadata-audit/results/2026-11-03.md

Complete evidence archive:

_claude_tmp/evidence_ma_2026-11-03.tgz

Verified:

  • Original filename disclosure in the HTTP Upload slot request.
  • Original filename included in the returned storage URLs.
  • Reproduction in six automated runs, including two with the current libtacky revision.
  • Build 93 source-code path preserving filenames from Files.

Not verified:

  • End-to-end behavior on a physical iPhone running build 93.
  • Actual filename generated for camera videos.
  • Whether production XMPP and S3 services persist filenames in logs.
  • Actual S3 object upload in the simulated test environment.
# [iOS / HTTP Upload] Original filenames from Files are exposed to the XMPP server and S3 storage (XEP-0363) **Severity:** Major **Issue ID:** UP-02 · **QA case:** MD-FL-01 **Date:** October 26, 2026 · Updated November 4, 2026 ## Summary When a user sends an attachment selected from the iOS **Files** app, AnotherIM includes the original filename in the XEP-0363 HTTP Upload slot request. The filename is exposed to the XMPP server and embedded in the S3 object URL, even when the file content is protected by OMEMO encryption. For example, uploading `IMG_4821.HEIC` produces: - An XMPP upload request containing `filename='IMG_4821.HEIC'`. - S3 GET/PUT URLs containing `/IMG_4821.HEIC`. - An attachment URL subsequently included in the chat message. This can disclose sensitive metadata, such as document names, to infrastructure operators. In chats without OMEMO, the attachment URL is also visible in plaintext to the XMPP server and potentially stored in MAM. **Affected components:** - `Packages/FeatureConversation/Sources/ConversationUI/ConversationView.swift` - `AnotherIM/XMPP/Attachments/AttachmentService.swift` - `AnotherIM/XMPP/Types/Attachments.swift` - libtacky: `tacky/xmpp/modules/message.tcl`, `file.tcl` The issue affects files selected from Files and drag-and-drop attachments based on the build 93 source code. Gallery photos/videos and voice recordings use generated filenames. Camera video filename behavior has not been verified. ## Environment | Item | Details | |---|---| | Application | anotherim-swift `c5fdeae`, branch `feat/ui-redesign`, build 93 | | libtacky | `2f42a2f2` / v1.2.0-beta2 | | Test server | ejabberd 26.09, `mod_s3_upload` from anotherim-server `b9e675d` | | Test environment | Level 1 cloud-based test, `autotests/metadata-audit` | | Client | libtacky application profile | | Verification | XMPP upload slot requests and server stanza logs | The test server generates signed upload URLs locally; no actual S3 upload was performed. The iOS application path was verified through source code, not by executing build 93 on a physical device. ## Preconditions 1. Start the test environment: `PUSH_WINDOW=0 GRACE_PERIOD=0 CLEAN=1 bash setup/up.sh` 2. Build libtacky from the application submodule: `TACKY_SRC=<anotherim-swift/tacky> bash setup/build_tacky.sh` 3. Configure `main@example.local` with the upload service `upload.example.local`. ## Steps to Reproduce **Automated test** 1. Run: `cd checks && python3 tacky_more.py` 2. The test requests an HTTP Upload slot for `IMG_4821.HEIC` (2,483,117 bytes, `image/heic`) using `message upload_slot`. 3. Inspect the XMPP stanza log for `urn:xmpp:http:upload:0`. 4. Check the `filename` attribute and returned GET/PUT URLs. **Manual verification on iOS (not yet performed)** 1. On build 93, enable **Access from Files**. 2. Open an OMEMO chat and select **+ → File**. 3. Choose a document with a recognizable filename, such as `Contract_Test.pdf`, and send it. 4. Inspect DEBUG application logs for `upload_slot`, or capture the server-side XMPP upload request. 5. Repeat with a gallery photo to compare filename handling. The application log is available only in DEBUG builds. Ordinary Charles interception is insufficient for inspecting the upload PUT because the libtacky iOS HTTP client uses bundled trusted root certificates. ## Expected Behavior The HTTP Upload slot request should use a generated filename, such as `<UUID>.pdf`, instead of the original local filename. The original filename should remain available only inside the encrypted message payload when OMEMO is enabled. This follows QA case MD-FL-01, which expects a randomized filename and `application/octet-stream` content type in the slot request. **Note:** This is a security/privacy recommendation, not an explicitly documented product requirement. Whether to conceal the MIME type requires a separate decision. ## Actual Behavior Run `fd861f91` (`evidence/stanzas_upload_fd861f91.txt`) captured the following request: ```xml <request xmlns='urn:xmpp:http:upload:0' content-type='image/heic' filename='IMG_4821.HEIC' size='2483133'/> ``` The server returned URLs containing the same filename: ```text https://s3.invalid/rig/e26656dd6ee5982c46bb8d4cad88225b/IMG_4821.HEIC ``` The slot request size includes the additional 16-byte GCM authentication tag used for OMEMO attachment encryption. This confirms that the original filename reaches the XMPP upload service and is incorporated into the storage URL. The filename may be visible to: - **XMPP server:** Upload slot requests and potentially server logs. - **S3 storage:** Object keys and potentially access logs. - **Non-OMEMO chats:** Plaintext attachment URLs, including potentially archived messages. The attachment content remains encrypted when OMEMO is used, but the filename is not protected in the upload request. ## Reproduction Rate **6/6 successful automated runs:** - October 26: `4f096705`, `53e2a408` - October 27: `9916fa6b`, `fb719fdb` - November 3: `fd861f91`, `31fa65fa` Both November 3 runs reproduced the issue with libtacky v1.2.0-beta2 and the build 93 source revision. ## Root Cause The iOS attachment pipeline preserves the original filename and passes it unchanged to the HTTP Upload slot request: 1. `ConversationView.swift:379` creates `OutgoingFile` with `name: url.lastPathComponent`. 2. `ConversationClient+Live.swift:115,124` forwards the filename through `PickedAttachment.file`. 3. `Attachments.swift:143-147` sanitizes control characters and path separators but does not anonymize the filename. 4. `AttachmentService.swift:507` passes `picked.name` to `api.messageUploadSlot`. 5. `TackyAPI.swift:556` forwards the request to libtacky. 6. libtacky `message.tcl:5185-5205` and `file.tcl:2354-2358` insert the supplied filename into the XEP-0363 `<request/>` stanza. The temporary local copy uses a UUID directory, but the original filename is retained. **Other attachment sources (source-code assessment):** - Gallery photos/videos: `photo-<timestamp>.<ext>` / `video-<timestamp>.<ext>`. - Voice recordings: `voice-<UUID>.m4a`. - Camera photos: `photo-<UUID>.jpg`. - Camera videos: Use a filename obtained from the system-provided media URL; its actual value was not verified. ## Suggested Fix Generate a random upload filename centrally in `AttachmentService.performUpload` before calling `messageUploadSlot`. For example: - Upload slot filename: `<UUID>.<extension>` or `<UUID>`. - Original display filename: Retained only in the message metadata, protected by OMEMO when enabled. Applying this at the upload-service level would cover Files, drag-and-drop, and other attachment sources consistently. Separately evaluate whether the upload request should use `application/octet-stream` rather than the original MIME type. The current MIME type is intentionally preserved to support attachment previews in clients such as Gajim. ## Verification After Fix 1. Select a document named `Contract_Test.pdf` from Files. 2. Send it in an OMEMO chat. 3. Verify that the XMPP upload slot request contains a randomized filename. 4. Verify that the returned storage URLs do not contain `Contract_Test.pdf`. 5. Verify that the recipient can still see the intended display filename. 6. Repeat for drag-and-drop, gallery media, voice recordings, and camera attachments. 7. Confirm that attachment upload, download, and decryption continue to work. ## Evidence and Status Evidence directory: `2026-10-26_UP-02_upload_filename/evidence/` November 3 evidence: - `stanzas_upload_fd861f91.txt` - `stanzas_upload_31fa65fa.txt` - `stanzas_full_fd861f91.log` - `stanzas_full_31fa65fa.log` - `tacky_more_fd861f91.json` - `tacky_more_31fa65fa.json` Test report: `autotests/metadata-audit/results/2026-11-03.md` Complete evidence archive: `_claude_tmp/evidence_ma_2026-11-03.tgz` **Verified:** - Original filename disclosure in the HTTP Upload slot request. - Original filename included in the returned storage URLs. - Reproduction in six automated runs, including two with the current libtacky revision. - Build 93 source-code path preserving filenames from Files. **Not verified:** - End-to-end behavior on a physical iPhone running build 93. - Actual filename generated for camera videos. - Whether production XMPP and S3 services persist filenames in logs. - Actual S3 object upload in the simulated test environment.
Contributor
  • #16: Fixed in 4832765: the upload slot is requested under .; the real name goes only in the message. The MIME type is unchanged.
- #16: Fixed in 4832765: the upload slot is requested under <UUID>.<ext>; the real name goes only in the message. The MIME type is unchanged.
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#16
No description provided.