Memory & Data Streams

How to Trigger Secure File Downloads from Memory

April 24, 2026
6 Min Read

In a traditional web application, downloading a file is easy. The backend server sends an HTTP response with a Content-Disposition: attachment header, and the browser handles the rest. But if you are building an End-to-End Encrypted (E2EE) application, the backend only holds unreadable ciphertext.

When your client-side JavaScript finishes decrypting a payload via the Web Crypto API, that raw file data sits exclusively in the browser's RAM as an ArrayBuffer. You cannot send it back to the server to trigger a download. You must force the browser to download the file directly from its own memory.

The Solution: Blobs and Object URLs

To achieve this, we use two powerful native web APIs: Blob (Binary Large Object) and URL.createObjectURL().

A Blob acts as an immutable, raw-data container. By converting our decrypted ArrayBuffer into a Blob, we can ask the browser to generate a temporary, internal URL (e.g., blob:https://yoursite.com/8f9a2b) that points directly to that memory space.

The Implementation

Here is the complete Vanilla JavaScript function to securely trigger a file download from an in-memory buffer without exposing the plaintext to a network request.

/**
 * Triggers a secure download of an ArrayBuffer from memory.
 * * @param {ArrayBuffer} decryptedBuffer - The raw decrypted file data
 * @param {string} filename - The original name of the file (e.g., 'secret.pdf')
 * @param {string} mimeType - The file type (e.g., 'application/pdf')
 */
function downloadSecureFile(decryptedBuffer, filename, mimeType) {
    // 1. Convert the raw buffer into a Blob
    const blob = new Blob([decryptedBuffer], { type: mimeType });

    // 2. Create a temporary, internal URL pointing to the Blob
    const blobUrl = URL.createObjectURL(blob);

    // 3. Create an invisible anchor tag
    const anchor = document.createElement('a');
    anchor.href = blobUrl;
    anchor.download = filename; // This attribute forces a download

    // 4. Append to the DOM, click it programmatically, and remove it
    document.body.appendChild(anchor);
    anchor.click();
    document.body.removeChild(anchor);

    // 5. CRITICAL: Destroy the internal URL to free up memory
    setTimeout(() => {
        URL.revokeObjectURL(blobUrl);
    }, 100);
}

The Security Threat: Memory Leaks

Step 5 in the code above—URL.revokeObjectURL()—is arguably the most important line for a security-focused application.

When you call createObjectURL, the browser pins that Blob in memory. It assumes the URL might be used again (for example, as the source for an <img> tag). If you do not explicitly revoke the URL, the decrypted plaintext file will sit in the browser's RAM indefinitely until the user completely closes the tab.

If an attacker executes an XSS payload 10 minutes after the user downloaded the file, they could still access that lingering Blob URL and steal the contents. Always clean up your memory.

See In-Memory Downloads in Action

We engineered ZeroKey to handle encrypted file attachments up to 2MB. When a recipient opens a link, the file is downloaded as unreadable ciphertext, decrypted dynamically using the Web Crypto API, and immediately downloaded to their local filesystem using this exact Blob methodology.

As soon as the download triggers, the Blob is revoked and the DOM is wiped clean, leaving zero trace of the file in the browser's memory.

Conclusion

Client-side cryptography completely shifts the responsibility of file delivery from the backend to the frontend. By mastering Blobs and DOM manipulation, you can securely route decrypted data straight from the user's RAM to their hard drive, maintaining a pristine zero-knowledge architecture.