DEV Community

mno tao
mno tao

Posted on AI-assisted

Safely parsing email files in the browser

An email file is not just text plus a few attachments. It can contain HTML, nested MIME parts, misleading filenames, inline resources, remote tracking pixels, malformed encodings, and enough data to exhaust a browser tab.

Moving parsing into the browser removes an upload from the architecture, but it does not automatically make the viewer safe. It changes the security job: untrusted content is now being interpreted next to the user’s active web session.

This is the checklist I use for a local EML and winmail.dat/TNEF reader.

Treat every parsed field as untrusted

The sender, subject, recipient, filename, MIME type, and message body all came from a file. Render headers and filenames as text, never by concatenating HTML.

The same applies to errors. A parser exception can include a filename or fragment of malformed input. Showing that message verbatim may leak data into logs or turn it into markup. Map parser failures to stable error categories, then display a controlled explanation.

Normalize into one internal model

EML and TNEF have different container structures, but the UI should not contain two independent security implementations.

Both parsers can produce a common message model:

subject, sender, to, cc, date,
plain body, sanitized HTML candidate,
attachments[] {
  safe filename, MIME type, bytes,
  inline flag, content ID, content location
}
Enter fullscreen mode Exit fullscreen mode

The normalization layer is the right place to enforce per-source limits and reject unsupported structures. The viewer and download code then work against the same constrained data regardless of input format.

Sanitize HTML as hostile input

Email HTML was designed for mail clients, not for direct insertion into an application DOM.

A conservative policy removes:

  • scripts and event handlers;
  • forms and interactive controls;
  • iframe, object, and embed elements;
  • styles and CSS URLs;
  • unsafe protocols;
  • executable or unexpected embedded content.

Use a maintained sanitizer with a pinned version, but do not stop at its default configuration. Email has resource-loading behavior that a generic “safe HTML” preset may still allow.

Plain text should remain the fallback. If HTML cannot be sanitized into the allowed subset, showing text is better than trying to preserve every visual detail.

Rewrite local inline images

Legitimate messages often reference an attachment using cid: or Content-Location. Those images can be displayed without a network request:

  1. match the reference to a parsed attachment;
  2. create a Blob from the attachment bytes;
  3. create an object URL;
  4. replace the resource reference with that local URL;
  5. revoke the URL when the message is switched, removed, cleared, or the page exits.

Matching needs normalized identifiers and explicit MIME checks. Do not let an attachment’s declared filename or content location become an arbitrary URL.

Keep remote images out of the DOM by default

Removing a visible remote image after rendering is too late. The request may already have exposed the user’s IP address, time, user agent, and a unique tracking token.

The safer sequence is:

  1. parse and sanitize the body;
  2. replace remote image sources with inert placeholders before insertion;
  3. tell the user that external images are blocked;
  4. only after an explicit action for the current message, restore allowed HTTPS sources;
  5. use a no-referrer policy.

“For the current message” is important. Permission should not silently carry to another email in the batch, and it does not belong in local storage.

A browser test should observe the network and assert zero remote-image requests before the click. After the click, it should allow only the expected image request and no navigation or script execution.

Make downloads path-safe

Attachment names can contain path separators, control characters, reserved device names, or repeated values. Normalize every name before using it in a download or ZIP archive:

  • strip directory components;
  • remove control and unsafe characters;
  • provide a fallback when the result is empty;
  • cap length;
  • deduplicate names deterministically.

ZIP creation can happen entirely in memory. Object URLs used for individual files, PDF previews, and archives should have a clear owner and lifecycle. Revoke too early and downloads fail; never revoke and repeated batches leak memory.

PDF attachments deserve another small boundary. Create a preview only after the user asks, and use a Blob URL. Do not embed an untrusted remote URL just because an attachment declared itself as a PDF.

Put limits on expansion, not only input bytes

A 25 MB source can contain many MIME parts or highly compressible attachments. Useful limits include:

  • source files per batch;
  • bytes per source and per batch;
  • MIME parts or attachments per source;
  • total successfully extracted attachments;
  • nesting depth and parser work, when the library exposes them.

Check limits during parsing instead of building an unbounded structure and rejecting it afterward.

The UI should distinguish “unsupported format,” “malformed message,” and “safe limit exceeded.” That helps users without exposing parser internals.

Local processing includes telemetry

A page can parse locally and still send sensitive metadata to analytics or error reporting. For a mail viewer, do not collect:

  • filenames or file sizes;
  • subject, sender, recipient, or body;
  • attachment names, types, or counts derived from one message;
  • parser errors containing input values;
  • full referrers or query strings that could carry user data.

If aggregate task events exist, make their vocabulary fixed and inspect the exact payload. A successful open can be counted without serializing anything about the email that was opened.

Also avoid storing parsed messages in localStorage, sessionStorage, IndexedDB, cookies, or URLs. “The files never leave your device” is weaker than “the message stays in memory for this session.” State which one you mean.

Test with synthetic fixtures

Real user mail is a tempting source of edge cases and a bad test asset. Build repository-owned fixtures containing:

  • plain text and sanitized HTML alternatives;
  • CID and Content-Location images;
  • one deliberately blocked HTTPS image;
  • duplicate and path-like filenames;
  • zero, one, and many attachments;
  • malformed input and exact size/count boundaries;
  • Unicode headers and filenames.

Use unique harmless markers so the test can detect accidental requests or persistence. Run the same fixture through every translated interface; security warnings and errors are part of the product, not incidental copy.

The reference implementation for this checklist is Mail File Viewer. Browser-only parsing reduces one major exposure, but the remaining work—sanitization, resource policy, bounds, and cleanup—is what makes the result defensible.

Top comments (0)