Deleting a file in Wire doesn’t remove it from servers — and other findings

Published on Wed 25 June 2025 by myst404 (@myst404_)

This article takes a closer look at Wire's approach to asset deletion, access control, and key management
TL;DR: Our tests show that while Wire’s file-sharing system is built on strong security principles, several behaviors diverge from potential user expectations or best practices. We identified five such cases: incomplete deletion of assets, local persistence of decryption keys after expiration, missing access control for some downloads, ability to infer the client used, and excessive asset retention from Android clients.

Readers wishing to omit technical details may refer directly to the conclusion.

Tests were performed on the Wire web application version 2025.05.26.14.15.10 with a personal account.

Table of contents

  1. Introduction
  2. Sending an asset
  3. Deleting an asset
  4. Accessing assets without authorization
  5. Risk scenarios
  6. Wire response
  7. A look at the server source code
  8. Mobile clients
  9. Conclusion

1. Introduction

According to Wikipedia, Wire is a secure communication and collaboration platform developed by Wire Swiss.
Both the server and clients source code are open-source and publicly available on GitHub.
Regular security audits, cryptographic reviews, and penetration tests are conducted on various components of the system, details can be found in Wire's security overview.

While message encryption often gets the spotlight — and rightly so — asset-sharing capabilities often receive less scrutiny, despite being an equally sensitive part of modern collaboration tools.

In this article, the term asset will be used as Wire does, referring to any file shared in a conversation.

2. Sending an asset

Sending and receiving an asset involves five basic steps.

A) Asset encryption

Asset encryption is handled client-side via JavaScript encryptAsset and encrypt functions:

export const encryptAsset = async ({plainText, algorithm = 'AES-256-CBC'}: EncryptOptions): Promise<EncryptedAsset> => {
  const initializationVector = crypto.getRandomValues(16);
  const rawKeyBytes = crypto.getRandomValues(32);

  const {key, cipher} = await crypto.encrypt(plainText, rawKeyBytes, initializationVector, algorithm);

  const ivCipherText = new Uint8Array(cipher.byteLength + initializationVector.byteLength);
  ivCipherText.set(initializationVector, 0);
  ivCipherText.set(new Uint8Array(cipher), initializationVector.byteLength);

  const sha256 = await crypto.digest(ivCipherText);

  return {
    cipherText: ivCipherText,
    keyBytes: key,
    sha256,
  };

Things are properly performed here:

  • AES-256-CBC is used. Although CBC mode can be vulnerable in some contexts, there is no obvious exploitation scenario here.
  • The native and secure getRandomValues function is used.
  • SHA256 is used to create a hash of the file. Later, after downloading the file and before decryption, the hash is verified. While this combination works correctly in this context, a modern authenticated encryption scheme like AES-256-GCM could provide both confidentiality and integrity in a single step.
  • Encryption is performed locally in the web browser.

B) Asset upload

After that, the encrypted asset prepended by the Initialization Vector is uploaded to Wire's infrastructure (and stored on a S3 bucket) via a POST request like:

POST /v8/assets HTTP/2
Host: prod-nginz-https.wire.com
Content-Length: 393
Authorization: Bearer [...]
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Content-Type: multipart/mixed; boundary=FrontierDT1konbgbqZccxEJNISHhNdI4eclAVSv
Origin: https://app.wire.com

--FrontierDT1konbgbqZccxEJNISHhNdI4eclAVSv
Content-Type: application/json;charset=utf-8
Content-length: 38

{"public":true,"retention":"expiring"}
--FrontierDT1konbgbqZccxEJNISHhNdI4eclAVSv
Content-Type: application/octet-stream
Content-length: 48
Content-MD5: tACOTEl6BW2IjvX/sdQsSA==

[Encrypted asset]
--FrontierDT1konbgbqZccxEJNISHhNdI4eclAVSv--

The server responds with a JSON message including an asset key (a unique identifier, not the encryption key) and an expiration date:

{"domain":"wire.com","expires":"2026-06-05T09:13:07.365Z","key":"3-5-98d29e9f-5acc-4bde-9c0c-f4d18209437d"}

C) Sending the asset to contacts

To download and decrypt the uploaded asset, the recipients need:

  • key: the identifier used to download the asset. Notice that the key is composed of an UUIDv4 prepended here by 3-5-.
  • otr_key: the AES encryption key.
  • sha256: to check that the asset has not been modified. This check is a safeguard only: the file can still be decrypted without the sha256 hash.

The Initialization Vector is prepended to the encrypted asset so there is no need to transmit it.

The data above is sent in an MLS (previously Proteus) message and stored locally and in the IndexedDB named wire@production along with messages received.

IndexedDB

D) Asset downloading

When a user clicks on an asset to download it, the download is not direct:

  1. An authenticated GET is performed to https://prod-nginz-https.wire.com/v8/assets/wire.com/{key} (key being the asset identifier).
  2. The server issues a 302 redirection to a URL like https://prod-assets.wire.com/v3/expiring/{UUIDv4 part of the key}?Expires=1749116123&Signature={AWS S3 Signature}>. The generated download link is valid only for a duration of 5 minutes.
  3. The asset is downloaded from <https://prod-assets.wire.com/v3/expiring/{UUIDv4 part of the key}?Expires=1749116123&Signature={AWS S3 Signature}>, an EC2 instance acting here like a proxy to an S3 bucket. No authentication header is required here.

E) Asset decrypting

After the SHA-256 hash of the file is verified in the decryptAsset function, the asset is decrypted by the decrypt function:

  async decrypt(cipherText: Uint8Array, keyBytes: Uint8Array): Promise<Uint8Array> {
    const key = await cryptoLib.subtle.importKey('raw', keyBytes, 'AES-CBC', false, ['decrypt']);

    const initializationVector = cipherText.slice(0, 16);
    const assetCipherText = cipherText.slice(16);
    const decipher = await cryptoLib.subtle.decrypt({iv: initializationVector, name: 'AES-CBC'}, key, assetCipherText);

    return new Uint8Array(decipher);
  },

Remark: there is one type of unencrypted assets which are sent to Wire's servers: profile pictures. In this case, the retention policy used during the asset upload is eternal.

From a security point of view, there is nothing to complain about the process described above. This is what is expected from an end-to-end messaging platform and from what is described in the Wire Security Whitepaper (section 4.3 Assets).

3. Deleting an asset

When a user deletes an asset:

  • The message and keys are removed from the local IndexedDB wire@production.
  • A deletion event is broadcast to contacts so that they know when the asset has been deleted.

File deleted

However, the encrypted asset itself remains stored on Wire’s servers.

Only the key referencing the asset and the otr_key used for decryption are deleted, not the asset itself. Unless this metadata is preserved before deletion, the asset will remain stored on the server but will be practically inaccessible, since both the download key and the decryption key are missing.

Assets are not kept forever. As stated in the FAQ, shown in the expires JSON value in the section B) Asset upload and confirmed in real world conditions: assets are automatically erased from the servers after one year.
However in this case, the asset is deleted from the servers but locally the key referencing the asset and the otr_key are not deleted from the IndexedDB wire@production.

This contradicts Wire’s 2021 Security Whitepaper which states:

Assets are persistently stored on the server without a predefined timeout.

In practice, Wire's implementation is more secure, but the lack of a clear deletion mechanism is misleading.

4. Accessing assets without authorization

In the D) Asset Downloading section, no check is performed to verify whether the user has the right to access the asset. This presents an IDOR vulnerability on uploaded assets, where an attacker with knowledge of the asset reference can download the asset, regardless of whether the asset was shared with anyone, let alone the attacker. The risk is relatively low, as high-entropy UUIDv4 are used to reference the assets.

Indeed, by knowing the key referencing the asset: it is possible to download it using the same sequence of requests explained in D) Asset Downloading.
While the ciphertext remains encrypted and unreadable without the decryption key, its unauthorized availability still violates the principle of access control and confidentiality.

We created a proof asset that is downloadable: https://prod-nginz-https.wire.com/v8/assets/wire.com/3-5-8b404b38-6e36-40fe-a7c2-80d4b0584c57.

In Wire’s 2021 Security Whitepaper, it is claimed:

As with regular text messages, only clients in the same conversation can receive asset metadata messages from one another and are authorized to download the corresponding asset ciphertext.

This contradicts Wire's statement that only users in the same conversation are authorized to download the ciphertext.

However, it is worth noting that, privacy-wise, this means Wire does not link an asset to recipients it is shared with (the asset owner is still stored in the asset metadata).

5. Risk scenarios

While encryption significantly reduces risk, several potential concerns remain:

  • A variant of the "Harvest now, decrypt later" approach: adversaries could collect encrypted assets and, even after those assets are automatically deleted by the server after 1 year, later gain access to the decryption keys — via XSS, stolen backups, or legal requests — and decrypt the data they had archived.
  • False sense of deletion: users might assume deletion erases the file from all storage.
  • Persistence of shared asset access: failure to fully delete shared assets could have serious consequences in certain scenarios. For instance, assets tagged with their author's identity enforce non-repudiation, which could pose a significant risk in situations involving abuse, oppressive regimes, or similar threats. Another example is the persistence of access to an asset by individuals who are no longer part of the original group — whether due to deauthorization or erroneous inclusion (as seen in incidents like Signal Gate) — if they retain both the asset key and its otr_key in their browser.

6. Wire response

Our findings were reported to Wire. Here is their initial response:

Thank you for your detailed insights and for taking the time to explore our file encryption and decryption processes. We truly appreciate your thorough investigation and the feedback you've provided. Regarding the behavior you've described, it is operating as expected within our current specifications. However, we want to assure you that we are continuously working on enhancing the security of our platform. Your feedback is invaluable in helping us identify areas for improvement.

Wire later clarified further:

1) Files (assets) are not immediately deleted from our servers upon message deletion. For free Wire accounts, assets are automatically removed from our servers after 1 year unless the user specifically requests deletion before then. For paying customers, files will remain stored until the customer chooses to delete them.

2) Access to file downloads is based on the possession of the correct file identifier and encryption keys; this behavior aligns with our current product specifications. We appreciate you highlighting these points, and please know that we are always working to enhance the security and privacy of our platform.

The Wire year end review 2024 and outlook 2025 blog post suggests a new Wire Secure File Sharing feature may soon address these observations.

We’re introducing a revolutionary way to securely share and manage files. This feature empowers you to maintain full control over data ownership and accessibility while seamlessly integrating with team workflows. This is a game-changer as it combines security, control, and convenience in file sharing and management.

7. A look at the server source code

DELETE

In fact, a server-side method does exist to permanently delete an asset. Reference: changelog and source code.
A simple DELETE request can be performed on the endpoint https://prod-nginz-https.wire.com/v8/assets/wire.com/{key}:

DELETE request

Only the user who has uploaded the asset can delete it, the owner is stored as an S3 metadata x-amz-meta-user:

Metadata S3

Note: if the message containing the asset's key has been deleted, it is too late as it will not be possible to delete it server-side without this key.

However, if the server-side implementation exists, it looks like there is no client-side implementation, at least in the web application.

Asset-Token

As seen in the JSON data ({"public":true,"retention":"expiring"}) in the section B) Asset upload, there is a public parameter.

When an asset associated to the JSON data {"public":false,"retention":"expiring"} (notice the false value) is uploaded, the server returns the following JSON parameters:

{"domain":"wire.com","expires":"2026-06-23T12:30:04.518Z","key":"3-5-0f75ad9d-25cd-406d-9d3a-30755fa41a8b","token":"fvolJaOeVzw_itNXsXr6Xg=="}

Now, if the user is not the owner of the asset and tries to access https://prod-nginz-https.wire.com/v8/assets/wire.com/{key}, a 404 error is returned:

404

The header Asset-Token is required, this is the value from the token JSON parameter returned on the upload request.

Asset-Token

The value of the token JSON parameter can also be provided as an asset_token GET parameter, instead of being sent in the header.

A new metadata is associated with the asset on the S3 bucket:

Metadata Asset-Token

This upload method is no longer prone to IDOR.

It looks like there is no client-side implementation of this upload method in the Wire web app personal version.

Retention policy

As seen in the JSON data ({"public":true,"retention":"expiring"}) in the section B) Asset upload, there is a retention parameter which can have multiple values:

  • eternal (key prefix: 3-1): retained indefinitely, frequent access (like profile pictures).
  • persistent (key prefix: 3-2): according to the comments in the source code, retained indefinitely and deprecated. This is the fallback policy if no retention parameter is defined. Actually retained for 564 weeks according to AWS headers.
  • volatile (key prefix: 3-3): retained for 1 month (it looks like there is no client-side implementation).
  • eternal-infrequent_access (key prefix: 3-4): retained indefinitely, infrequent access (it looks like there is no client-side implementation).
  • expiring (key prefix: 3-5): retained for 1 year (default retention mode in the personal version).

8. Mobile clients

We later performed checks on the latest iOS (3.124.1) and Android (4.13.1-99014-prod) mobile applications; here are some observations:

  • For both clients, the assets are uploaded with the public parameter set to false (cf. B) Asset upload): the Asset-Token mode is used.
  • For iOS, assets use the expiring retention policy, like on the web application.
  • For Android, assets use the persistent retention policy: files are kept for 564 weeks (nearly 11 years).

Android

This contradicts Wire's FAQ that assets are stored for one year.

This means that assets uploaded from an Android client are retained for more than 10 years, significantly longer than iOS & web.

9. Conclusion

Overall, Wire’s asset-sharing system is well-designed and aligns with strong security principles. The issues identified are edge cases that do not compromise the core encryption model but could benefit from clearer user-facing behavior and documentation and minor access control adjustments.

We identified five such cases where behavior diverges from user expectations or best practices:

  1. Deletion is incomplete: Deleting a file in the Wire web app removes only local references and keys on clients, not the asset itself. A server-side API to delete the file exists but lacks client implementation.

  2. Key persistence: When assets expire (having not been deleted by a user) and are removed from the server, their decryption keys can persist locally in IndexedDB. Wire does not provide an easy mechanism to purge these expired keys.

  3. Missing access control on asset downloads: If the UUIDv4 of an asset sent via the web application is known, the encrypted version of the asset can be downloaded without authorization. A token-based access control exists but is not implemented in the personal web application.

  4. Ability to infer the client used: Based on the metadata of the uploaded asset (retention policy and the presence of an Asset-Token), it is possible to know with which client — web, iOS or Android — an asset was uploaded. This fingerprinting capability could be exploited to infer user habits or device usage.

  5. Android inconsistency: Assets uploaded via this client are stored for nearly 11 years, instead of the 1-year policy otherwise applied. This may indicate a legacy default configuration on Android, potentially misleading users that expect a 1-year expiration.