Summary
The Office validators (skills/{docx,pptx,xlsx}/scripts/office/validators/base.py, and pptx.py) parse the XML parts of the document being validated with lxml's default parser:
lxml.etree.parse(str(xml_file))On lxml < 5.0 the default parser resolves external entities, so a crafted .docx / .pptx / .xlsx makes the validator read local files into the parsed tree. lxml 5.0 changed the default, but the skills don't pin lxml, and the system packages on two current LTS releases are still 4.x: Debian 12 (python3-lxml 4.9.2) and Ubuntu 22.04 (4.8.0).
This is separate from the open XXE work (#58, #657, #1780, #1784, #1837), which covers xml.etree in other files (mcp-builder/scripts/evaluation.py, validators/redlining.py, slack-gif-creator). None of those touch the lxml calls in validators/base.py, and main still has them.
Reproduction
A part containing an external entity, and the same call the validators make:
<?xml version="1.0"?>
<!DOCTYPE r [<!ENTITY x SYSTEM "file:///tmp/canary.txt">]>
<r>&x;</r>import lxml.etree as e
print(e.tostring(e.parse("part.xml"), encoding="unicode"))| lxml | external entity | parameter entity (external DTD) | entity bomb |
|---|---|---|---|
| 4.9.4 | file contents read | file contents read | blocked by libxml2 |
| 5.4.0 | not resolved | not resolved | blocked by libxml2 |
Suggested fix
Use one hardened parser for every untrusted part:
_SAFE_PARSER = lxml.etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False)
lxml.etree.parse(str(xml_file), parser=_SAFE_PARSER)With this parser both entity cases are neutralised on 4.9.4 and 5.4.0, and ordinary parts still parse. OOXML parts don't use DTDs, so nothing legitimate should be lost. The trusted XSD loading can keep its own parser. A lighter alternative is to declare lxml>=5 as a dependency, but the explicit parser doesn't depend on the installed version.