docling-project/docling · critical · SecurityError

ZIP slip attempt: {member.filename}

Error message

ZIP slip attempt: {member.filename}

What it means

When downloading EasyOCR model archives, docling extracts each zip member after checking that the resolved destination stays inside the target directory. If a member filename like '../../etc/passwd' would escape local_dir, a SecurityError is raised naming the offending entry. This is a defense against the ZIP slip path-traversal attack, and it means the downloaded archive is malformed or was tampered with, not that your code is wrong.

Source

Thrown at docling/models/stages/ocr/easyocr_model.py:181

        recognition_models_by_name = {
            model_name: model_details
            for generation in rec_models_dict.values()
            for model_name, model_details in generation.items()
        }
        for model_name in recognition_models:
            if model_name in recognition_models_by_name:
                download_list.append(recognition_models_by_name[model_name])

        # Download models
        for model_details in download_list:
            buf = download_url_with_progress(model_details["url"], progress=progress)
            with zipfile.ZipFile(buf, "r") as zip_ref:
                for member in zip_ref.infolist():
                    member_path = os.path.realpath(
                        os.path.join(local_dir, member.filename)
                    )
                    if not member_path.startswith(os.path.realpath(local_dir) + os.sep):
                        raise SecurityError(f"ZIP slip attempt: {member.filename}")
                    zip_ref.extract(member, local_dir)

        return local_dir

    def __call__(
        self, conv_res: ConversionResult, page_batch: Iterable[Page]
    ) -> Iterable[Page]:
        if not self.enabled:
            yield from page_batch
            return

        for page in page_batch:
            assert page._backend is not None
            if not page._backend.is_valid():
                yield page
            else:
                with TimeRecorder(conv_res, "ocr"):
                    ocr_rects = self.get_ocr_rects(page)

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Delete the partially downloaded cache/artifacts and retry the model download from the official source on a trusted network.
  2. Verify the download URL and integrity of the model archive (hash) if your setup pins URLs.
  3. If it recurs, fetch models manually from the EasyOCR upstream repository and place them in artifacts_path, and report the incident — a zip-slip hit can indicate tampering.
Defensive patterns

Strategy: try-catch

Validate before calling

def zip_is_safe(zip_path: Path, dest: Path) -> bool:
    dest_real = os.path.realpath(dest)
    with zipfile.ZipFile(zip_path) as zf:
        return all(
            os.path.realpath(os.path.join(dest_real, m.filename)).startswith(dest_real + os.sep)
            for m in zf.infolist()
        )

Try / catch

try:
    model_dir = model.download_models(local_dir=artifacts)
except SecurityError as err:
    logger.critical("Model archive failed ZIP-slip check: %s — possible tampering", err)
    raise  # do NOT bypass; retry from a trusted network/source instead

Prevention

When it happens

Trigger: Prefetching/downloading EasyOCR models (download_models) where the fetched zip contains entries whose paths resolve outside the extraction directory — malicious or corrupted archive, or a redirected/spoofed download URL.

Common situations: Compromised or misconfigured model host serving crafted archives; corporate proxies returning HTML/error pages saved as zips with odd entry names; corrupted downloads from interrupted transfers.

Related errors


AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14). Data as JSON: /api/errors/a348768db90aa28f. Report an issue: GitHub.