Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions agent/image_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,17 @@ def _file_to_data_url(path: Path) -> Optional[str]:
caller reports those paths in ``skipped`` and the rest of the turn
proceeds.
"""
try:
from agent.file_safety import raise_if_read_blocked

raise_if_read_blocked(str(path))
except ValueError as exc:
logger.warning("image_routing: blocked local image attachment %s -- %s", path, exc)
return None
except Exception:
# Keep attachment routing best-effort if the guard itself is unavailable.
pass

try:
raw = path.read_bytes()
except Exception as exc:
Expand Down
38 changes: 38 additions & 0 deletions tests/agent/test_image_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,44 @@ def test_png_passes_through_no_transcode(self, tmp_path: Path):
b64 = url.split(",", 1)[1]
assert base64.b64decode(b64) == _png_bytes()

def test_file_to_data_url_blocks_read_denied_image_path(self, tmp_path: Path):
"""Native image routing must honor the shared credential read guard."""
from agent.image_routing import _file_to_data_url

img_path = tmp_path / ".env"
img_path.write_bytes(_png_bytes())

assert _file_to_data_url(img_path) is None

def test_native_content_parts_skip_read_denied_local_image(self, tmp_path: Path):
from agent.image_routing import build_native_content_parts

img_path = tmp_path / ".env.local"
img_path.write_bytes(_png_bytes())

parts, skipped = build_native_content_parts("inspect this", [str(img_path)])

assert skipped == [str(img_path)]
assert all(part.get("type") != "image_url" for part in parts)

def test_native_content_parts_blocks_image_symlink_to_read_denied_file(self, tmp_path: Path):
from agent.image_routing import build_native_content_parts
import os
import pytest

secret = tmp_path / ".env"
secret.write_bytes(_png_bytes())
img_link = tmp_path / "secret.png"
try:
os.symlink(secret, img_link)
except (OSError, NotImplementedError) as exc:
pytest.skip(f"symlinks unavailable: {exc}")

parts, skipped = build_native_content_parts("inspect this", [str(img_link)])

assert skipped == [str(img_link)]
assert all(part.get("type") != "image_url" for part in parts)

def test_jpeg_passes_through_no_transcode(self, tmp_path: Path):
from agent.image_routing import _file_to_data_url

Expand Down
Loading