{"record":{"id":"038d696e35535674","repo":"docling-project/docling","slug":"zip-slip-attempt-info-filename","errorCode":null,"errorMessage":"ZIP slip attempt: {info.filename}","messagePattern":"ZIP slip attempt: (.+?)","errorType":"exception","errorClass":"SecurityError","httpStatus":null,"severity":"critical","filePath":"docling/backend/msword_backend.py","lineNumber":220,"sourceCode":"        return False\n    return not any(part == \"..\" for part in normalized.split(\"/\"))\n\n\ndef _normalize_strict_ooxml(archive: zipfile.ZipFile) -> BytesIO:\n    \"\"\"Rewrite an open Strict OOXML package to Transitional namespaces in memory.\n\n    Only XML/relationship parts that actually carry a Strict namespace are\n    decoded and rewritten; every other member (images, fonts, ...) is copied\n    through with its original compression, avoiding a needless decode pass. Each\n    member is decompressed exactly once. The archive is validated against\n    zip-slip and zip-bomb attacks while it is read.\n    \"\"\"\n    normalized = BytesIO()\n    total_uncompressed = 0\n    with zipfile.ZipFile(normalized, \"w\", zipfile.ZIP_DEFLATED) as target:\n        for info in archive.infolist():\n            if not _is_safe_zip_member(info.filename):\n                raise SecurityError(f\"ZIP slip attempt: {info.filename}\")\n            if info.file_size > _MAX_MEMBER_UNCOMPRESSED_SIZE:\n                raise SecurityError(\n                    f\"Refusing to expand oversized OOXML part: {info.filename}\"\n                )\n            total_uncompressed += info.file_size\n            if total_uncompressed > _MAX_TOTAL_UNCOMPRESSED_SIZE:\n                raise SecurityError(\n                    \"Refusing to expand OOXML package exceeding the uncompressed size limit\"\n                )\n            content = archive.read(info.filename)\n            if (\n                info.filename.endswith((\".xml\", \".rels\"))\n                and _STRICT_OOXML_MARKER in content\n            ):\n                content = _STRICT_OOXML_NS_RE.sub(\n                    lambda match: _strict_ns_to_transitional(match.group(0)),\n                    content.decode(\"utf-8\"),\n                ).encode(\"utf-8\")","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/backend/msword_backend.py#L202-L238","documentation":"SecurityError raised while normalizing an OOXML package that uses Strict OOXML namespaces: each zip member's filename is passed through _is_safe_zip_member(), and any member whose path could escape the extraction root (absolute paths, '..' traversal, drive letters, weird segments) trips this explicit zip-slip rejection before the member is copied into the normalized archive.","triggerScenarios":"Converting a Strict-OOXML .docx/.xlsx/.pptx (or a file routed through msword's strict-namespace normalization) whose zip contains a member like '../../evil.dll' or '/etc/passwd'. The guard fires during init/normalization, before any parsing.","commonSituations":"Crafted or fuzzed Office files, files produced by obfuscation tooling, archives repacked with hostile member names, or security test suites probing docling with zip-slip payloads. Genuine Word output never contains such members, so a hit usually means malicious or badly broken input.","solutions":["Quarantine and reject the input file — a zip-slip member name is malicious or irreparably malformed.","Scan the producing pipeline/source: whoever generated the file uploaded a crafted archive.","If you must inspect: list members with `python -m zipfile -l file.docx` and find the offending path before deciding.","Keep the guard enabled; do not patch it out, as it protects extraction paths from traversal."],"exampleFix":"# before\nresult = converter.convert(user_supplied_docx)  # SecurityError: ZIP slip attempt\n\n# after (pre-scan and reject hostile archives)\nimport zipfile\nwith zipfile.ZipFile(user_supplied_docx) as z:\n    for n in z.namelist():\n        if n.startswith(('/', '\\\\')) or '..' in n.replace('\\\\', '/').split('/'):\n            raise ValueError(f'rejecting malicious archive: {n}')\nresult = converter.convert(user_supplied_docx)","handlingStrategy":"validation","validationCode":"import zipfile\n\ndef zip_members_safe(path: str) -> bool:\n    with zipfile.ZipFile(path) as z:\n        for n in z.namelist():\n            parts = n.replace('\\\\', '/').split('/')\n            if n.startswith(('/', '\\\\')) or '..' in parts or ':' in parts[0]:\n                return False\n    return True","typeGuard":null,"tryCatchPattern":"from docling.exceptions import SecurityError\ntry:\n    result = converter.convert(docx_path)\nexcept SecurityError as e:\n    if 'ZIP slip' in str(e):\n        log.critical('malicious OOXML rejected: %s', docx_path)\n        quarantine_and_alert(docx_path)","preventionTips":["Pre-scan all user-supplied OOXML files for traversal member names before conversion.","Quarantine files that trip SecurityError; never whitelist and retry them.","Treat any zip-slip hit as evidence of a targeted or corrupted upload, not a bug."],"tags":["security","zip-slip","ooxml","msword","path-traversal"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}