{"record":{"id":"ecbbde57e94c4883","repo":"docling-project/docling","slug":"archive-member-image-info-path-is-not-a-regula","errorCode":null,"errorMessage":"Archive member '{image_info.path}' is not a regular file (directory or symlink in tar).","messagePattern":"Archive member '(.+?)' is not a regular file \\(directory or symlink in tar\\)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"docling/backend/mets_gbs_backend.py","lineNumber":388,"sourceCode":"        # A page's fileGrp entries in the METS XML are independently optional (see\n        # _PageFiles), so a page can legitimately have no `image` or `coordOCR` fptr\n        # (e.g. a blank/cover page with no OCR layer). Report it as unparseable rather\n        # than asserting, so the caller can mark it invalid and skip it, consistent\n        # with how sibling PDF backends (e.g. DoclingParsePageBackend) handle a page\n        # they can't build.\n        image_info = self.page_map[page_no].image\n        ocr_info = self.page_map[page_no].coordOCR\n        if image_info is None or ocr_info is None:\n            _log.warning(\n                f\"Page {page_no} is missing an 'image' or 'coordOCR' fileGrp entry; \"\n                \"skipping.\"\n            )\n            return None, None\n\n        # Security: limit extraction size to prevent decompression bombs\n        image_file = self._tar.extractfile(image_info.path)\n        if image_file is None:\n            raise RuntimeError(\n                f\"Archive member '{image_info.path}' is not a regular file \"\n                \"(directory or symlink in tar).\"\n            )\n        image_file = cast(tarfile.ExFileObject, image_file)\n        image_data = image_file.read(self.options.max_file_bytes + 1)\n        if len(image_data) > self.options.max_file_bytes:\n            raise ValueError(\n                f\"Image file {image_info.path} exceeds individual file size limit of {self.options.max_file_bytes} bytes\"\n            )\n\n        # Security: Track total bytes extracted\n        self._total_bytes_extracted += len(image_data)\n        if self._total_bytes_extracted > self.options.max_total_bytes:\n            raise ValueError(\n                f\"Total extracted data exceeds maximum limit of {self.options.max_total_bytes} bytes\"\n            )\n\n        buf = BytesIO(image_data)","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/backend/mets_gbs_backend.py#L370-L406","documentation":"RuntimeError raised while extracting a page image from a METS/GBS archive: tarfile.extractfile() returned None for the member referenced by the page's 'image' fileGrp entry. extractfile() returns None for non-regular members (directories, symlinks, devices), so the METS metadata points at something that is not a readable file — an integrity/security check against path traversal via tar links.","triggerScenarios":"Converting a METS archive where the fileGrp image entry resolves to a directory, a symlink, or a hard link instead of a regular file member; also when the referenced member name is absent from the archive in a form extractfile can open.","commonSituations":"Hand-assembled or repacked archives that converted regular files to symlinks (e.g. `tar -cf` with symlinked trees), METS files referencing entries by slightly different paths, or tampered archives attempting link-based tricks.","solutions":["Repack the archive materializing symlinks into real files: `tar -czhf` behaviors aside, use `tar -xzf` then `tar -czf` from the extracted tree.","Verify each page's image entry with tarfile.getmember(path).isreg() before conversion.","Regenerate the METS package from the source system so fileGrp references match actual members.","Reject untrusted archives that fail this check — symlink members in a METS package are not legitimate."],"exampleFix":"# before\nresult = converter.convert(mets_path)  # RuntimeError: not a regular file\n\n# after (pre-check members are regular files)\nimport tarfile\nwith tarfile.open(mets_path) as t:\n    bad = [m.name for m in t.getmembers() if not m.isfile() and not m.isdir()]\n    assert not bad, f'archive contains non-regular members: {bad}'\nresult = converter.convert(mets_path)","handlingStrategy":"validation","validationCode":"import tarfile\n\ndef image_members_are_regular(tar_path: str) -> bool:\n    with tarfile.open(tar_path) as t:\n        return all(m.isfile() for m in t.getmembers() if m.name.endswith(('.png', '.jpg', '.jpeg')))","typeGuard":null,"tryCatchPattern":"try:\n    result = converter.convert(mets_path)\nexcept RuntimeError as e:\n    if 'not a regular file' in str(e) and 'image' in str(e):\n        log.error('METS image entry is a link/dir in %s — repack required', mets_path)","preventionTips":["Repack archives from extracted trees to eliminate symlinked image members.","Validate isreg() on image members during ingest.","Treat link members in METS packages as tampering on untrusted input."],"tags":["mets","archive","security","tar","symlink"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}