diff --git a/docling_core/types/doc/document.py b/docling_core/types/doc/document.py index c0e9fd54..3ace72dd 100644 --- a/docling_core/types/doc/document.py +++ b/docling_core/types/doc/document.py @@ -1099,8 +1099,10 @@ def pil_image(self) -> Optional[PILImage.Image]: raise ValueError(f"Decoded image exceeds size limit of {settings.max_image_decoded_size} bytes.") self._pil = PILImage.open(BytesIO(decoded_img)) - # else: Handle http request or other protocols... + # NOTE: any further protocol support added (https etc.) must adhere to respective sanitization practices elif isinstance(self.uri, Path): + if not settings.allow_image_local_path: + raise ValueError("Loading images from local paths is not enabled.") self._pil = PILImage.open(self.uri) return self._pil diff --git a/docling_core/utils/settings.py b/docling_core/utils/settings.py index c54fd356..402c2da2 100644 --- a/docling_core/utils/settings.py +++ b/docling_core/utils/settings.py @@ -5,6 +5,7 @@ class CoreSettings(BaseSettings): model_config = SettingsConfigDict(env_prefix="DOCLINGCORE_") allow_image_file_uri: bool = False + allow_image_local_path: bool = False max_image_decoded_size: int = 20 * 1024 * 1024 # 20MB diff --git a/test/test_docling_doc.py b/test/test_docling_doc.py index 927a0c14..e81bb504 100644 --- a/test/test_docling_doc.py +++ b/test/test_docling_doc.py @@ -1,5 +1,6 @@ import os import re +import tempfile import warnings from collections import deque from copy import deepcopy @@ -898,6 +899,66 @@ def test_file_uri_blocked_by_default(): _ = image_ref.pil_image +def test_local_path_blocked_by_default(): + """Test that local Path objects are blocked by default.""" + image_ref = ImageRef( + dpi=72, + mimetype="image/png", + size=Size(width=100, height=100), + uri=Path("/etc/something"), + ) + + with pytest.raises(ValueError, match="Loading images from local paths is not enabled"): + _ = image_ref.pil_image + + +def test_local_path_allowed_with_setting(): + """Test that local Path objects work when enabled via settings.""" + orig_allow_image_local_path = settings.allow_image_local_path + + with tempfile.NamedTemporaryFile(suffix=".png") as tmp_file: + test_img_path = Path(tmp_file.name) + + try: + img = PILImage.new("RGB", (50, 50), color="blue") + img.save(test_img_path) + + settings.allow_image_local_path = True + + image_ref = ImageRef( + dpi=72, + mimetype="image/png", + size=Size(width=50, height=50), + uri=test_img_path, + ) + + pil_img = image_ref.pil_image + assert pil_img is not None + assert pil_img.size == (50, 50) + assert pil_img.mode == "RGB" + finally: + settings.allow_image_local_path = orig_allow_image_local_path + + +def test_local_path_traversal_blocked(): + """Test that path traversal attempts are blocked by default.""" + paths_to_check = [ + Path("/etc/something"), + Path("../../../etc/something"), + ] + + for path in paths_to_check: + image_ref = ImageRef( + dpi=72, + mimetype="image/png", + size=Size(width=100, height=100), + uri=path, + ) + + with pytest.raises(ValueError, match="Loading images from local paths is not enabled"): + _ = image_ref.pil_image + + def test_max_decoded_size_custom(): """Test that oversized images are rejected based on custom limit.""" orig_max_image_decoded_size = settings.max_image_decoded_size