Security Reference
MCP server XML injection and XXE security
MCP tool handlers that accept, parse, or transform XML input are vulnerable to XML External Entity (XXE) injection — an attack class that can exfiltrate arbitrary local files, make SSRF calls to internal services, and in some environments escalate to remote code execution via XSLT processing. Here is how each variant works and how to configure parsers to prevent all three.
Why XML in MCP servers is high risk
MCP servers that integrate with SOAP APIs, document management systems, or data pipelines often accept XML payloads in tool arguments. The risk is high because XML parsers expand external entity references by default — a behavior that is a feature for document processing but a critical vulnerability in security-sensitive tool handlers.
Attack surface: Any MCP tool that calls DOMParser.parseFromString(), Python's xml.etree.ElementTree, Java's DocumentBuilder, or PHP's SimpleXML on caller-supplied input is vulnerable unless external entity expansion is explicitly disabled. The default configuration of all four parsers allows XXE.
Attack 1: File exfiltration via external entity reference
An attacker supplies a crafted XML payload with a DOCTYPE declaration that defines an external entity pointing to a local file. When the parser expands the entity, it reads the file and injects its contents into the document — which the tool then returns to the caller.
<!-- Attacker-supplied XML in a tool argument --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <data><value>&xxe;</value></data> <!-- Parser expands &xxe; to the full contents of /etc/passwd --> <!-- Tool returns the expanded document to the LLM agent caller -->
The tool handler never explicitly reads /etc/passwd — the parser does it automatically during entity expansion. Static analysis tools that scan for fs.readFile('/etc/passwd') will not catch this pattern. Only parser configuration checks find it.
Attack 2: SSRF via external entity HTTP request
The external entity's SYSTEM identifier does not have to be a file URI — it can be an HTTP URL. This converts the XML parser into an SSRF proxy:
<!DOCTYPE data [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role"> ]> <data><value>&xxe;</value></data> <!-- Parser makes an HTTP GET to the EC2 metadata service --> <!-- Returns AWS IAM role credentials to the tool caller -->
The AWS instance metadata endpoint at 169.254.169.254 is a common target. In Kubernetes environments, the equivalent is the API server at its cluster-internal address. Cloud vendor metadata endpoints expose IAM credentials, access tokens, and SSH keys — all of which can be extracted through a single XXE payload if the MCP server runs on a cloud VM.
Attack 3: Billion laughs — denial of service via entity expansion
Even without external entities, XML is vulnerable to billion-laughs DoS: a nested entity definition that causes exponential expansion during parsing:
<!DOCTYPE bomb [ <!ENTITY a "AAAAAAAAAA"> <!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;"> <!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> <!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> ]> <root>&d;</root> <!-- 10^4 = 10,000 bytes expands to 10,000^4 = 10^16 bytes before the parser runs OOM -->
Hardened parser configuration
The fix is to disable external entity resolution and DOCTYPE processing entirely. Every XML parser has a mechanism for this; the specifics vary by language:
// Node.js — use fast-xml-parser with security options
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({
allowBooleanAttributes: true,
processEntities: false, // disables entity expansion (no XXE)
ignoreDeclaration: false,
});
// DO NOT use DOMParser or node-xml2js with default settings on untrusted input
# Python — defuse the stdlib parsers from defusedxml import ElementTree # defusedxml wraps stdlib parsers and disables: # - external entity resolution (file:// and http://) # - DTD processing # - entity expansion above a safe limit tree = ElementTree.parse(xml_input) # safe by default
// Java — disable XXE on DocumentBuilderFactory
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Prefer JSON over XML in tool argument schemas. If your MCP tool accepts a document format as input, prefer JSON schemas (Zod validation, no entity expansion possible). Use XML only when integrating with a system that requires it (SOAP, legacy ERP, some government APIs), and always parse with the hardened configuration above.
XSLT injection — escalation to RCE
If your MCP server applies XSLT transformations to XML input, the risk escalates further. Some XSLT processors (notably Saxon in XSLT 3.0 mode and older versions of libxslt) allow arbitrary function calls or extension elements that can execute shell commands:
<!-- Attacker-supplied XSLT -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl" version="1.0">
<xsl:template match="/">
<xsl:value-of select="php:function('system', 'cat /etc/shadow')"/>
</xsl:template>
</xsl:stylesheet>
Never apply caller-supplied XSLT to caller-supplied XML. If your tool must transform XML, use a static, version-controlled XSLT template and only allow the caller to supply the data document, not the stylesheet.
SkillAudit findings for XML/XXE vulnerabilities
Check your MCP server for XML parsing vulnerabilities
SkillAudit scans tool handlers for XML parser calls without external-entity-disable flags. Paste your GitHub URL for a free audit.
Run free audit →Related: MCP server SSRF security — the broader SSRF threat model. MCP server security risks — full threat landscape.