fix(agent): admit and sync gateway-staged attachments on remote execution backends (#110174)

Desktop paste/file attachments land in Hermes-managed staging dirs on the
GATEWAY (composer-pastes/ for large text pastes, attachments/ for dropped
files), but on the Remote SSH topology the workspace root (TERMINAL_CWD) is a
path on the SSH HOST - the two filesystems are fully disjoint, as the issue
thread confirms. Two gaps combined to reject every staged attachment with
"path is outside the allowed workspace":

- _resolve_path admitted only allowed_root + composer-paste roots, so a
  gateway-staged attachments/ path was refused outright. Admit the
  _CACHE_DIRS staging roots (attachments/, images/, cache/*, composer-pastes/)
  via a helper that asks get_cache_directory_mounts - the gateway's OWN
  payload is never a workspace escape, and the path-traversal and
  credential-deny guards in _ensure_reference_path_allowed still run after.
  Anything else outside the workspace stays blocked.
- composer-pastes/ was missing from _CACHE_DIRS, so its bytes never reached
  the remote: ssh/daytona/vercel_sandbox sync via iter_sync_files ->
  iter_cache_files, and to_agent_visible_cache_path only translates mounted
  dirs - a paste attached on a fresh session dangled on the remote host.

Tests cover the disjoint-filesystem SSH topology end-to-end (text inlines,
binary renders the synced ~/.hermes path), the still-refused stranger path,
local-backend unchanged, and the composer-pastes mount+sync enumeration.

Consolidates PR #110387 by Finn763 (the _agent_staged_path guard widening and
the SSH-topology tests, adapted to the current _ensure_reference_path_allowed
ordering) with PR #103412 by ericmaddox (whose mapping insight is subsumed by
the _CACHE_DIRS entry, which fixes both the sync and the translation).

Co-authored-by: ericmaddox <ericmaddox@users.noreply.github.com>
This commit is contained in:
finn763
2026-09-24 19:25:38 -05:00
committed by brooklyn!
parent a723194c1e
commit a164569429
4 changed files with 153 additions and 0 deletions

View File

@@ -258,6 +258,13 @@ _CACHE_DIRS: list[tuple[str, str]] = [
# Mount it so the agent's file tools can read dropped binaries (zip/pdf/...) from inside sandbox
# containers instead of dangling host paths (#76577).
("attachments", "attachments"),
# Desktop stages a large plain-text paste as a `.txt` under this Hermes-managed dir
# (apps/desktop/electron/composer-paste.ts; `COMPOSER_PASTES_DIRNAME` in
# agent/context_references.py) and attaches it as `@file:`. Without a mount/sync
# entry, remote execution backends (ssh/daytona/vercel_sandbox) never received the
# bytes and `to_agent_visible_cache_path` left the gateway-host path dangling on
# the remote host (#110174). No legacy alias, so both tuple slots match.
("composer-pastes", "composer-pastes"),
]