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

CRITICAL
XML parsing of tool argument with external entity expansion enabled — confirmed XXE vector. Grade impact: −30 on Security axis, blocks install gate. Fix: apply hardened parser configuration above.
HIGH
XSLT transformation applied to caller-supplied stylesheet — potential RCE. Grade impact: −20 on Security axis. Fix: use static XSLT templates only.
HIGH
XML parsing without entity expansion limit — billion-laughs DoS vector. Grade impact: −10 on Security axis. Fix: disable entity expansion or set expansion depth limit.
MEDIUM
XML parsed from tool arg but DOCTYPE processing appears disabled — partial mitigation found. Grade impact: −5 on Security axis. Fix: confirm all external entity types are disabled, not just DOCTYPE.

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.