{"record":{"id":"532f72937cd5fb05","repo":"docling-project/docling","slug":"resource-bundle-contains-an-unsafe-path-member-r","errorCode":null,"errorMessage":"Resource bundle contains an unsafe path: {member!r}","messagePattern":"Resource bundle contains an unsafe path: (.+?)","errorType":"exception","errorClass":"ArtifactDownloadError","httpStatus":null,"severity":"error","filePath":"docling/service_client/client.py","lineNumber":1796,"sourceCode":"            except zipfile.BadZipFile as exc:\n                raise ArtifactDownloadError(\n                    f\"Downloaded resource bundle is not a valid ZIP: {exc}\"\n                ) from exc\n            json_path = self._find_bundle_json(base_dir)\n            document = DoclingDocument.load_from_json(json_path)\n            # Embed while the extracted artifacts are still on disk, then let the\n            # temp dir be removed: the returned document is fully in-memory.\n            self._embed_referenced_images(document, base_dir)\n            return document\n\n    @staticmethod\n    def _safe_extract_zip(bundle_zip: zipfile.ZipFile, base_dir: Path) -> None:\n        # Guard against zip-slip even though bundles originate from our service.\n        base_resolved = base_dir.resolve()\n        for member in bundle_zip.namelist():\n            target = (base_dir / member).resolve()\n            if target != base_resolved and base_resolved not in target.parents:\n                raise ArtifactDownloadError(\n                    f\"Resource bundle contains an unsafe path: {member!r}\"\n                )\n        bundle_zip.extractall(base_dir)\n\n    @staticmethod\n    def _find_bundle_json(base_dir: Path) -> Path:\n        # The server writes the document files at the bundle root and the\n        # referenced images under artifacts/, so the document JSON is the\n        # top-level *.json file.\n        candidates = sorted(base_dir.glob(\"*.json\"))\n        if not candidates:\n            raise ArtifactDownloadError(\n                \"Resource bundle does not contain a top-level JSON document.\"\n            )\n        return candidates[0]\n\n    def _embed_referenced_images(\n        self, document: DoclingDocument, base_dir: Path","sourceCodeStart":1778,"sourceCodeEnd":1814,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/service_client/client.py#L1778-L1814","documentation":"Raised as ArtifactDownloadError when extracting a downloaded resource bundle ZIP: one of its member paths (e.g. '../../etc/passwd') would resolve outside the extraction directory. This is a zip-slip containment guard applied even though bundles nominally come from the trusted docling-serve service. Per the exception docstring it is normally caught internally and surfaced as a FAILURE ConversionResult rather than propagated.","triggerScenarios":"Calling convert()/convert_all() with images='referenced' (bundle mode) against a service that returns a ZIP whose member names contain absolute paths or '../' traversal segments that resolve beyond the temp extraction dir.","commonSituations":"A compromised or buggy docling-serve version writes member names with leading slashes or traversal; a proxy/CDN rewrites the artifact; a hand-crafted bundle is served from a misconfigured object store presigned URL.","solutions":["Check the resulting ConversionResult.status/errors — the failure is attached to that document's result, and other documents in the batch still succeed","Verify the docling-serve deployment version and that the artifact store is not tampering with bundle contents","If images are not needed, request images='embedded' or omit image export so no bundle ZIP is downloaded"],"exampleFix":"// before\nresult = client.convert(Path('doc.pdf'), options=options_with_referenced_images)\n\n// after (inspect per-document result instead of expecting an exception)\nresults = list(client.convert_all([Path('doc.pdf')], options=options))\nfor res in results:\n    if res.status == ConversionStatus.FAILURE:\n        print(res.errors)  # ArtifactDownloadError details appear here","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_artifact_download_error(exc: BaseException) -> bool:\n    return isinstance(exc, ArtifactDownloadError)","tryCatchPattern":"try:\n    results = list(client.convert_all(sources))\nexcept ArtifactDownloadError:\n    ...  # rare: only when the error escapes internal handling\n# preferred: inspect per-document results\nfor res in results:\n    if res.status == ConversionStatus.FAILURE:\n        handle(res.errors)","preventionTips":["Treat ArtifactDownloadError on bundles as a per-document FAILURE result, not a global exception","Only enable referenced-image bundles against a trusted, version-matched docling-serve"],"tags":["security","zip-slip","artifact-download","service-client"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}