Files
Codeman/.changeset/remote-file-access.md
T
Randalix 63aafdf274 fix(files): serve remote-case attachments, the path a click takes outside the case
A clicked path that points OUTSIDE the case directory goes through the attachment
routes (the frontend's `_isExternalPreviewPath` sends every absolute path not under
`workingDir` to `POST /attachments`), and those had the same local-`fs` assumption
as file-raw: `realpathSync`/`fs.stat` on a path that only exists on the remote host,
so the file never opened — the case the #415 report was actually about.

- `registerExternalAttachment()` accepts `remote` and resolves through
  `remoteProbePaths` (canonical path, size/mtime, kind, plus the workspace root for
  the confinement check). Everything around it — blocklist, extension allowlist,
  workspace confinement, registry/dedupe — is now shared by both branches, so the
  remote path cannot drift from the local one.
- The by-id routes (`raw`, `preview`, `thumbnail`), the metadata poll and the
  attachment history list resolve over ssh too. `raw` streams with the same
  Range contract as file-raw; `preview` (office) and `thumbnail` answer 400 for a
  remote record; an unreachable host answers 502, a vanished file 404.
- Which host a record is read from follows the SESSION, never the path string: the
  same absolute path is a different file on each host, and a remote session never
  falls back to a local file with that name.
- Codex generated artifacts keep force-workspace confinement for a remote case: the
  well-known artifact directories are anchored at THIS host's home, so only a file
  inside the remote workspace is trusted.

Still local-only by design: writes, office conversion, thumbnails, the file
tree/picker and tail-file.
2026-09-14 17:06:42 +02:00

2.1 KiB

aicodeman
aicodeman
patch

File previews, downloads and text reads now work in a remote (SSH) case.

A remote case's working directory is an absolute path on the remote host, but the file routes resolved it with local fs — so a clicked path (or the File Viewer) always failed as "File not found" even though the file existed and the session was clearly working in that directory. GET /api/sessions/:id/file-raw, file-content, file-preview and file-thumbnail now resolve and read through the same buildSshConnectionArgs() connection the launch uses (src/remote-files.ts, one realpath+stat probe per request returning both the file and the workspace root).

Clicked paths that point OUTSIDE the case directory (a remote /tmp scratchpad capture, a screenshot elsewhere in the remote home) go through the attachment routes, which had the same local-fs assumption: registration, the by-id raw stream, the metadata poll and the attachment history list now resolve over ssh as well, so the click-path works whether the file sits inside or outside the case. Which host a record is read from follows the SESSION, never the path string — the same absolute path means a different file on each host, and a remote session never falls back to a local file.

The guards are unchanged in strength: the workspace boundary is still enforced (now resolved on the host that can actually resolve it), the sensitive-path blocklist and the size cap (CODEMAN_MAX_DOWNLOAD_BYTES) still apply before any bytes are read, and Range requests keep working, so remote <video>/<audio> seeking behaves like a local file. An unreachable host is reported as 502 with the remote reason instead of a misleading 404. Nothing is ever copied to the Codeman host.

Still not available for remote cases, and now said explicitly instead of 404-ing: editing a file (edit=1 / PUT answer 400, the viewer hides its Edit affordance), office-document previews and generated thumbnails (both need the bytes on the server's disk), the file tree / path picker, and tail-file. Docker cases are unaffected (their workspace is bind-mounted at the same absolute path).