T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/translate_pptx_text.py:198
- Finding
- Unhardened XML and ZIP Processing of Untrusted PPTX Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/translate_pptx_text.py:198-211`; equivalent parsing also occurs at `scripts/translate_pptx_text.py:232-241`, `scripts/translate_pptx_text.py:286-289`, and `scripts/scan_pptx_text.py:95-107` **Vulnerability Type**: Unsafe processing of untrusted XML and compressed archives **Risk Level**: High ### Vulnerable Code ```python def collect_unique_paragraphs( files: dict[str, bytes], prefixes: list[str], source_lang: str, skip_patterns: list[re.Pattern[str]], ) -> list[str]: source_pattern = pattern_for(source_lang) found: Counter[str] = Counter() for name in iter_text_files(list(files), prefixes): root = etree.fromstring(files[name]) for paragraph in root.findall(".//a:p", namespaces=NS): text, _ = paragraph_text_nodes(paragraph) if text and matches_source_lang(text, source_lang) and not should_skip(text, skip_patterns): found[text] += 1 ``` The entire PPTX archive is also loaded into memory without limits: ```python with zipfile.ZipFile(temp_path, "r") as src: files = {name: src.read(name) for name in src.namelist()} ``` The scan-only implementation uses the same pattern: ```python with zipfile.ZipFile(path) as archive: for name in archive.namelist(): if not name.endswith(".xml") or not any(name.startswith(prefix) for prefix in prefixes): continue root = etree.fromstring(archive.read(name)) ``` ### Technical Analysis PPTX files are ZIP archives containing attacker-controlled XML. The scripts pass this XML directly to `lxml.etree.fromstring()` without constructing a hardened parser that explicitly disables DTD loading, external entity resolution, and network access. They also do not reject `DOCTYPE` declarations. In addition, ZIP entries are read without limits on: - Total uncompressed package size - Individual entry size - Number of entries - Compression ratio - XML depth, nod ...[truncated 1732 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Construct and consistently reuse a hardened XML parser: ```python SAFE_XML_PARSER = etree.XMLParser( resolve_entities=False, load_dtd=False, no_network=True, huge_tree=False, recover=False, ) if b"<!DOCTYPE" in payload.upper(): raise ValueError("DTD declarations are not permitted") root = etree.fromstring(payload, parser=SAFE_XML_PARSER) ``` 2. Apply this parser at every `etree.fromstring()` call in both scripts. 3. Validate the ZIP central directory before reading content: - Set a maximum number of entries. - Set maximum compressed and uncompressed sizes. - Set a maximum cumulative uncompressed package size. - Reject suspicious compression ratios. - Reject duplicate or malformed entry names. 4. Stream or selectively read only required PPTX entries instead of loading the entire archive into a dictionary. 5. Set limits on XML depth, paragraph count, text length, and translation request size. 6. Catch `zipfile.BadZipFile`, `lxml.etree.XMLSyntaxError`, memory errors, and limit violations, then fail safely without replacing the output. 7. Add regression tests using oversized archives, deeply nested XML, DTD declarations, and entity-expansion payloads. ]]>
