T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/prose_lint.py:404
- Finding
- Unbounded Input and DOCX Decompression Can Cause Resource Exhaustion## Vulnerability Details **File Location**: `scripts/prose_lint.py:404-421` and `scripts/prose_lint.py:458-462` **Vulnerability Type**: Unbounded file loading, archive decompression, and XML parsing **Risk Level**: Medium ### Vulnerable Code ```python with zipfile.ZipFile(path) as zf: part_names = set(zf.namelist()) if "word/document.xml" not in part_names: raise InputReadError(f"DOCX missing main document content: {path}") xml_names = ["word/document.xml"] if scope == "all": for kind in ("header", "footer"): xml_names.extend(sorted( name for name in part_names if re.fullmatch(rf"word/{kind}[^/]*\.xml", name) )) xml_names.extend(( "word/footnotes.xml", "word/endnotes.xml", "word/comments.xml", )) for name in xml_names: if name not in part_names: continue root = ElementTree.fromstring(zf.read(name)) ``` The same input path also permits unbounded loading of plain-text and Markdown files: ```python path = Path(path_arg) try: if path.suffix.lower() == ".docx": return str(path), read_docx(path, scope=docx_scope, format_findings=docx_format_findings) raw = path.read_bytes() ``` ### Technical Analysis The Skill explicitly supports user-provided DOCX, TXT, and Markdown documents. The linter loads plain-text files entirely into memory through `Path.read_bytes()`. For DOCX files, which are ZIP archives, selected XML members are read and decompressed entirely through `zf.read(name)` before being parsed with `ElementTree.fromstring()`. No controls are applied to: - The original input-file size. - The number of ZIP members. - Individual or aggregate uncompressed member sizes. - Compression ratios. - XML document size, depth, or element count. - Memory consumption or processing time. An attacker can therefore provide an oversized text file, an XML-heavy DOCX, or a highly compressed ZIP-bomb-style DOCX. Pr ...[truncated 1604 chars]
- Remediation
- ## Remediation Suggestions 1. **Enforce input-size limits before reading** - Check `Path.stat().st_size` against a documented maximum. - Reject oversized TXT, Markdown, and DOCX inputs before calling `read_bytes()` or opening the archive. - Use bounded, incremental reads instead of loading complete plain-text files into memory. 2. **Validate DOCX archive metadata** - Inspect every relevant `ZipInfo` entry before decompression. - Enforce limits on individual and aggregate `file_size` values. - Reject suspicious compression ratios, such as very small `compress_size` values paired with very large `file_size` values. - Limit the total number of archive entries. - Reject encrypted, malformed, duplicated, or unexpected members where they are not required for supported DOCX processing. 3. **Bound decompression** - Read archive members through a streaming interface with a strict byte counter. - Abort processing immediately when an individual or aggregate decompression limit is exceeded. - Do not rely exclusively on ZIP metadata because malicious or malformed archives may contain misleading metadata. 4. **Harden XML parsing** - Enforce maximum XML sizes before parsing. - Apply depth, node-count, and text-size limits. - Use a hardened XML parser or a parser configuration designed for untrusted documents. - Stop parsing when configured complexity thresholds are exceeded. 5. **Isolate execution** - Run document-processing utilities with memory, CPU, and wall-clock time limits. - Execute them in a restricted worker process or sandbox so resource exhaustion cannot destabilize the main Agent process. - Return a controlled input-validation error when a limit is reached. 6. **Add security tests** - Test oversized plain-text inputs. - Test DOCX archives with extreme compression ratios and large aggregate expanded sizes. - Test deeply nested and high-node-count XML documents. - Verify that each case is rejected be ...[truncated 43 chars]
