T09 · Insecure Skill Coding Practices
- Location
scripts/run_audit.py:194- Finding
Unbounded DOCX Decompression and XML Parsing Can Cause Denial of Service
- Content
View full analysis
Vulnerability Details
File Location:
scripts/run_audit.py:194-200
Additional Location:scripts/validate_outputs.py:24-28
Vulnerability Type: Uncontrolled resource consumption during archive decompression and XML parsing
Risk Level: MediumVulnerable Code
From
scripts/run_audit.py:python def read_docx_text(path: Path) -> str: """Read DOCX text using only Python standard library.""" ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} try: with zipfile.ZipFile(path) as zf: xml_data = zf.read("word/document.xml") except Exception as exc: raise RuntimeError(f"Unable to read the Word document; confirm that it is a valid .docx file: {exc}") from exc root = ET.fromstring(xml_data)The same unsafe processing pattern occurs in
scripts/validate_outputs.py:python def read_docx_text(path: Path) -> str: """Extract paragraph text from a valid DOCX package.""" namespace = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} with zipfile.ZipFile(path) as archive: xml_data = archive.read("word/document.xml") root = ET.fromstring(xml_data)Technical Analysis
A DOCX file is a ZIP archive. Both processing paths call
ZipFile.read()onword/document.xml, causing the complete compressed entry to be decompressed into memory. The code does not enforce limits on:- Overall input size
- Declared or actual uncompressed entry size
- Compression ratio
- XML depth or element count
- Parsing memory or CPU consumption
- Processing duration
An attacker able to supply a DOCX file can construct an archive whose
word/document.xmlis very small when compressed but expands to a very large payload. The expanded data is then retained in memory and passed toxml.etree.ElementTree.fromstring(), which can further consume substantial memory and CPU while building the complete XML tree.The issue affects both initial DOCX input ...[truncated 1795 chars]
- Remediation
View remediation
Remediation Suggestions
-
Enforce an input-file size limit before opening the archive.
python MAX_DOCX_SIZE = 20 * 1024 * 1024 if path.stat().st_size > MAX_DOCX_SIZE: raise RuntimeError("DOCX exceeds the permitted input size.") -
Inspect ZIP metadata before decompressing any entry. Retrieve
ZipInfoforword/document.xmland reject entries whose declared uncompressed size exceeds a conservative limit.python MAX_XML_SIZE = 50 * 1024 * 1024 MAX_COMPRESSION_RATIO = 100 info = zf.getinfo("word/document.xml") if info.file_size > MAX_XML_SIZE: raise RuntimeError("DOCX document XML exceeds the permitted size.") compressed_size = max(info.compress_size, 1) if info.file_size / compressed_size > MAX_COMPRESSION_RATIO: raise RuntimeError("DOCX contains a suspicious compression ratio.") -
Read decompressed content through a bounded stream. Do not rely exclusively on attacker-controlled ZIP metadata. Read at most
MAX_XML_SIZE + 1bytes and reject the document if the bound is exceeded.python with zf.open(info) as source: xml_data = source.read(MAX_XML_SIZE + 1) if len(xml_data) > MAX_XML_SIZE: raise RuntimeError("DOCX document XML exceeds the permitted size.") -
Apply archive-wide controls. Limit the number of entries, cumulative uncompressed size, individual entry size, and compression ratio. Reject encrypted entries, malformed paths, and unsupported compression methods where appropriate.
-
Constrain XML complexity. Prefer bounded or streaming parsing and impose reasonable limits on nesting depth, element count, text length, and total parsed content.
-
Use runtime isolation. Process untrusted documents in a worker with explicit memory, CPU, and wall-clock limits. Terminate the worker when those limits are exceeded.
-
Use one hardened helper in both scripts. Replace the duplicated DOCX-reading implementat ...[truncated 314 chars]
-
