{"record":{"id":"89b2be72b225adf5","repo":"deepset-ai/haystack","slug":"document-with-id-doc-id-has-a-file-path-file","errorCode":null,"errorMessage":"Document with ID '{doc.id}' has a file path '{file_path}' that escapes the configured root '{root_path}'. Resolved path: '{resolved_file_path}'.","messagePattern":"Document with ID '(.+?)' has a file path '(.+?)' that escapes the configured root '(.+?)'\\. Resolved path: '(.+?)'\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"haystack/components/converters/image/image_utils.py","lineNumber":260,"sourceCode":"    for doc in documents:\n        file_path = doc.meta.get(file_path_meta_field)\n        if file_path is None:\n            raise ValueError(\n                f\"Document with ID '{doc.id}' is missing the '{file_path_meta_field}' key in its metadata.\"\n                f\" Please ensure that the documents you are trying to convert have this key set.\"\n            )\n\n        resolved_file_path = Path(root_path, file_path)\n\n        # When root_path is set, ensure the resolved path stays within it to block path-traversal\n        # payloads (e.g. \"../../etc/passwd\") coming from document metadata. When root_path is unset,\n        # file paths are treated as absolute by design and no containment check is applied; callers that\n        # process untrusted metadata should configure root_path (see component docstrings).\n        if root_path:\n            resolved_file_path = resolved_file_path.resolve()\n            resolved_root = Path(root_path).resolve()\n            if not resolved_file_path.is_relative_to(resolved_root):\n                raise ValueError(\n                    f\"Document with ID '{doc.id}' has a file path '{file_path}' that escapes the \"\n                    f\"configured root '{root_path}'. Resolved path: '{resolved_file_path}'.\"\n                )\n\n        if not resolved_file_path.is_file():\n            raise ValueError(\n                f\"Document with ID '{doc.id}' has an invalid file path '{resolved_file_path}'. \"\n                f\"Please ensure that the documents you are trying to convert have valid file paths.\"\n            )\n\n        mime_type = doc.meta.get(\"mime_type\") or mimetypes.guess_type(resolved_file_path)[0]\n        if mime_type not in IMAGE_MIME_TYPES:\n            raise ValueError(\n                f\"Document with file path '{resolved_file_path}' has an unsupported MIME type '{mime_type}'. \"\n                f\"Please ensure that the documents you are trying to convert are of the supported \"\n                f\"types: {', '.join(IMAGE_MIME_TYPES)}.\"\n            )\n","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/converters/image/image_utils.py#L242-L278","documentation":"As a path-traversal guard, when root_path is configured every resolved file path must remain inside that root. If metadata-controlled paths like '../../etc/passwd' or absolute paths outside the root resolve outside it, the component raises ValueError to prevent reading arbitrary files.","triggerScenarios":"Document meta file_path containing '..' segments or absolute paths that, after Path(root_path, file_path).resolve(), are not relative to the configured root_path — e.g. meta file_path='/etc/passwd' with root_path='/data'.","commonSituations":"Documents ingested from untrusted sources carrying absolute paths; mixing storage roots between pipeline stages; symlinks pointing outside the root; moving a pipeline between machines with different data roots.","solutions":["Make file_path relative to root_path (e.g. 'img/doc1.png' with root_path='/data').","Point root_path at a directory that actually contains all referenced files.","Remove '..' segments from stored metadata; re-index documents with corrected paths.","If absolute paths are intended, set root_path to None (paths are then trusted as absolute by design)."],"exampleFix":"// before\nconverter.run(documents=docs, root_path=\"/data\")  # meta file_path=\"../../etc/image.png\"\n// after\ndoc.meta[\"file_path\"] = \"images/image.png\"  # relative to /data\nconverter.run(documents=docs, root_path=\"/data\")","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef ensure_inside_root(root_path, file_path):\n    resolved = Path(root_path, file_path).resolve()\n    if not resolved.is_relative_to(Path(root_path).resolve()):\n        raise ValueError(f\"{file_path!r} escapes root {root_path}\")","typeGuard":"def is_safe_relative(file_path: str) -> bool:\n    p = Path(file_path)\n    return not p.is_absolute() and \"..\" not in p.parts","tryCatchPattern":"try:\n    result = converter.run(documents=docs, root_path=root)\nexcept ValueError as e:\n    if \"escapes the configured root\" in str(e):\n        logger.error(\"Rejected untrusted path: %s\", e)  # treat as security event, do not retry\n        raise\n    raise","preventionTips":["Store paths relative to a single data root; never accept absolute paths from untrusted metadata.","Strip '..' and leading '/' from incoming paths at ingestion time.","Keep all data files under one root directory matching root_path.","Be careful with symlinks inside the root that point outside."],"tags":["python","security","path-traversal","image"],"backgroundTag":"path-traversal","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}