docling-project/docling · critical · SecurityError
ZIP slip attempt: {info.filename}
Error message
ZIP slip attempt: {info.filename} What it means
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.
Source
Thrown at docling/backend/msword_backend.py:220
return False
return not any(part == ".." for part in normalized.split("/"))
def _normalize_strict_ooxml(archive: zipfile.ZipFile) -> BytesIO:
"""Rewrite an open Strict OOXML package to Transitional namespaces in memory.
Only XML/relationship parts that actually carry a Strict namespace are
decoded and rewritten; every other member (images, fonts, ...) is copied
through with its original compression, avoiding a needless decode pass. Each
member is decompressed exactly once. The archive is validated against
zip-slip and zip-bomb attacks while it is read.
"""
normalized = BytesIO()
total_uncompressed = 0
with zipfile.ZipFile(normalized, "w", zipfile.ZIP_DEFLATED) as target:
for info in archive.infolist():
if not _is_safe_zip_member(info.filename):
raise SecurityError(f"ZIP slip attempt: {info.filename}")
if info.file_size > _MAX_MEMBER_UNCOMPRESSED_SIZE:
raise SecurityError(
f"Refusing to expand oversized OOXML part: {info.filename}"
)
total_uncompressed += info.file_size
if total_uncompressed > _MAX_TOTAL_UNCOMPRESSED_SIZE:
raise SecurityError(
"Refusing to expand OOXML package exceeding the uncompressed size limit"
)
content = archive.read(info.filename)
if (
info.filename.endswith((".xml", ".rels"))
and _STRICT_OOXML_MARKER in content
):
content = _STRICT_OOXML_NS_RE.sub(
lambda match: _strict_ns_to_transitional(match.group(0)),
content.decode("utf-8"),
).encode("utf-8")View on GitHub (pinned to 61d76f1ff3)
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.
Example fix
# before
result = converter.convert(user_supplied_docx) # SecurityError: ZIP slip attempt
# after (pre-scan and reject hostile archives)
import zipfile
with zipfile.ZipFile(user_supplied_docx) as z:
for n in z.namelist():
if n.startswith(('/', '\\')) or '..' in n.replace('\\', '/').split('/'):
raise ValueError(f'rejecting malicious archive: {n}')
result = converter.convert(user_supplied_docx) Defensive patterns
Strategy: validation
Validate before calling
import zipfile
def zip_members_safe(path: str) -> bool:
with zipfile.ZipFile(path) as z:
for n in z.namelist():
parts = n.replace('\\', '/').split('/')
if n.startswith(('/', '\\')) or '..' in parts or ':' in parts[0]:
return False
return True Try / catch
from docling.exceptions import SecurityError
try:
result = converter.convert(docx_path)
except SecurityError as e:
if 'ZIP slip' in str(e):
log.critical('malicious OOXML rejected: %s', docx_path)
quarantine_and_alert(docx_path) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ZIP slip attempt: {member.filename}
- Refusing to expand oversized OOXML part: {info.filename}
- Path traversal blocked: '{loc}' resolves outside base direct
- Resource bundle contains an unsafe path: {member!r}
- Resource bundle references an image outside the bundle: {uri
AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14).
Data as JSON: /api/errors/038d696e35535674.
Report an issue: GitHub.