hermes-agent/tests/integration
emozilla 316e77517e fix(vision): unified image-source resolver + terminal-backend confinement
Salvage of #35362, evolved to also close the vision sandbox-escape
(GHSA-gpxw-6wxv-w3qq). The two were the same root cause — vision read image
bytes host-side while every other tool reads through the terminal backend —
so one resolver fixes both the delivery gaps and the escape.

Delivery (from #35362, re-authored against current main since the branch was
4140 commits stale and vision_tools.py had been rewritten on both sides):
- tools/image_source.py: one resolver for data:/http(s)/file/local/container
  image sources, returning raw bytes through a single magic-byte-sniff +
  50MB-ingest chokepoint. Fixes 'no image attached' / 'Invalid image source'
  for every source type (#7571, #25118, #29643, #22328, #32709, #9077).
- tools/credential_files.py: from_agent_visible_cache_path, the container->host
  cache reverse-map (inverse of the existing forward twin).
- tools/vision_tools.py: both vision sites route through the resolver with
  task_id threaded from the handler; resolved bytes are materialized to a temp
  file so main's evolved encode/resize/embed-cap pipeline is reused verbatim
  (kept over the PR's older bytes-core resize to avoid touching browser_tool /
  conversation_compression callers).

Security (fills #35362's deliberately-stubbed _within_allowed_roots seam):
- Under a non-local terminal backend the file tools are confined to the sandbox
  (SECURITY.md 2.2), but vision read host-side — a prompt-injected
  vision_analyze('/etc/passwd') exfiltrated host secrets, and read_file even
  redirects the model to vision_analyze for image paths. The resolver now
  enforces the same boundary: local backend reads any host path (chosen
  posture); non-local backend host-reads ONLY the media caches under
  HERMES_HOME (where the gateway/download media lives) and routes every other
  path to an in-sandbox base64 exec-read — which reads the CONTAINER's file,
  the same one 'cat' would, never the host's. Paths are resolve()-d so a
  symlink can't escape a cache; fail-closed when no sandbox env exists.
  This closes the escape AND delivers container-only images (#32709) with the
  same mechanism.

Tests: unified resolver + confinement model (tests/tools/test_image_source.py,
incl. proof a non-cache host path under Docker yields container bytes not the
host secret); existing vision tests updated to the resolver boundary; Docker
integration test verified green against a real daemon (exec-read of a tmpfs
/workspace file, a root-owned mode-600 file, and the host-secret invariant).

Fixes GHSA-gpxw-6wxv-w3qq.
Co-authored-by: banditburai <promptsiren@gmail.com>
2026-07-04 15:30:50 -07:00
..
__init__.py test: reorganize test structure and add missing unit tests 2026-02-26 03:20:08 +03:00
test_batch_runner.py test: reorganize test structure and add missing unit tests 2026-02-26 03:20:08 +03:00
test_checkpoint_resumption.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_daytona_terminal.py test(daytona): add unit and integration tests for Daytona backend 2026-03-05 10:26:22 -08:00
test_ha_integration.py refactor(gateway): migrate Home Assistant adapter to bundled plugin 2026-06-06 11:46:24 -07:00
test_modal_terminal.py refactor: remove dead code — 1,784 lines across 77 files (#9180) 2026-04-13 16:32:04 -07:00
test_vision_docker_resolve.py fix(vision): unified image-source resolver + terminal-backend confinement 2026-07-04 15:30:50 -07:00
test_voice_channel_flow.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_web_tools.py feat(web_extract): truncate-and-store instead of LLM summarization (#54843) 2026-06-29 10:00:49 -07:00