Commit Graph
11 Commits
Author SHA1 Message Date
can1357 eff714c750 Merge remote-tracking branch 'origin/farm/16f7ef23/ssh-helper-timeouts' 2026-07-02 23:43:11 +02:00
roboomp ec6c31a737 fix(tool): detected macos sshfs mounts without mountpoint
Added a macOS stat-device fallback for SSHFS mount detection so already-mounted remotes do not trigger a second sshfs invocation when mountpoint is unavailable.

Added a focused regression test and changelog entry.

Fixes #4319
2026-07-02 14:28:21 +00:00
roboomp 08899a4392 fix(ssh): bound pre-command SSH helpers with ptree.exec timeout
runSshSync and runSshCaptureSync in connection-manager.ts invoked ssh
through the Bun shell with .quiet().nothrow() but no timeout and no
abort signal. They sit on the ensureHostInfo -> probeHostInfo /
ensureConnection path that runs *before* SshTool.execute applies the
user-supplied command timeout, so an unreachable host or wedged
control-master hung the tool forever.

Switch both helpers to ptree.exec with a 30s timeout,
allowNonZero: true, allowAbort: true, and stderr: "full". The
timeout is a parameter (default 30_000) so tests can override with a
short bound; both helpers still return their existing failure-result
shape (exitCode / stdout / stderr), so ensureConnection can surface a
'Failed to start SSH master ...' error instead of blocking.

Add a regression test that stages a fake ssh binary trapping SIGTERM
and sleeping 300s, then confirms both helpers return within a bounded
window with a non-zero exit code.

Fixes #4232
2026-07-02 08:32:52 +00:00
roboomp f16d618ca2 fix(ssh): recover transferShell when host marker probe fails
When csh/tcsh aborts the initial host-info marker probe before emitting
PI_HOST_PROBE=, the previous fallback returned os/shell unknown without
ever checking whether sh -lc still worked. That kept ssh:// rejecting a
POSIX-capable host (P2 from PR #3722 review).

The marker-missing fallback now runs probeTransferShell before returning.
If sh/bash/zsh round-trips the transfer marker, the fallback carries
transferShell and derives os from that probe's uname -s output. Windows
compat unames (MINGW/MSYS/Cygwin/Windows) still classify as windows so
ssh:// keeps refusing Windows hosts.

Added osFromUname coverage for Linux, GNU/Linux, Darwin, Windows compat
unames, and unknown payloads.
2026-06-28 12:01:49 +00:00
roboomp 7b937104bd fix(ssh): scan stderr for the transfer-shell marker too
The host-info probe already recovers its marker from stderr (some
remotes have dotfiles that swap fd 1/2), but `probeTransferShell` only
scanned `probe.stdout`. That left `transferShell` unset on those
hosts and made `ssh://` refuse a POSIX-capable remote (P2 from PR
#3722 review).

Extracted the both-streams scan into `findProbeMarker(stdout, stderr,
marker)` and routed the transfer probe through it: stdout first,
stderr as the rescue. Same recovery contract as the host probe.

`TRANSFER_PROBE_MARKER` exported alongside `findProbeMarker` so the
recovery branch is unit-testable without touching disk.

Tests: covers stdout-only, stderr-only, both-streams-prefer-stdout,
and neither-stream.
2026-06-28 11:57:49 +00:00
roboomp 207734e915 fix(ssh): dispatch transfer snippets through verified transferShell
The previous fix gated on a verified `transferShell` but still let
OpenSSH hand the snippet to whatever `$SHELL` happens to be on the
remote. On a fish/csh/tcsh host the new gate would accept the host,
then fail anyway because the login shell can't parse `if [ ... ]; then
...` (P1 from PR #3722 review).

Each transfer command (read, write, stat, list) is now wrapped in
`<transferShell> -c '...'` via a shared `wrapInPosixShell` helper, so
the snippet is parsed by the same shell OMP's capability probe verified
can run it. `ensurePosixRemote` returns the verified shell so each
call site can do the wrap. `ssh-executor.ts`'s identical Windows-compat
helper is consolidated onto the same primitive (`buildCompatCommand` /
`quoteForCompatShell` removed).

Stays POSIX-clean across all four call sites; `-c` (not `-lc`) since
the snippets only call absolute builtins and don't need login-profile
setup. Capability *probing* still uses `-lc` to mirror the user env.

Test additions: one case asserts every dispatch starts with
`bash -c '...'` and embeds the original POSIX snippet (read/write/stat/list)
when transferShell is bash and login shell is unknown; another covers
the `sh -c` happy path.
2026-06-28 11:52:44 +00:00
roboomp c4ed1614eb fix(ssh): gate ssh:// transfers on verified POSIX shell capability
Replace the login-shell-name allowlist in `ensurePosixRemote` with a
capability check against a newly probed `transferShell`. The host probe
runs `sh -lc` / `bash -lc` / `zsh -lc` against the remote and records
the first candidate whose printf marker round-trips; `uname -s` from the
same probe also refines the OS classification when the first probe could
not resolve it.

Three compounding problems fixed:

- The host probe parsed only the first stdout line, so login-shell
  banners or any startup noise would land ahead of the payload and
  classify the host as `shell: "unknown"`. The probe now frames its
  payload with a `PI_HOST_PROBE=` marker (see `extractProbePayload`)
  and scans both streams for the marker line.
- `shouldRefreshHostInfo` did not treat `{os: "linux", shell: "unknown"}`
  as stale, so a single bad classification stuck and kept failing
  later `ssh://` operations. It now refreshes any non-Windows cache
  entry without a verified `transferShell`.
- The transfer guard refused the host on the self-reported login-shell
  name. It now gates on `info.transferShell`, which is the shell OMP
  actually verified can run `head`/`cat`/`mv`/`test`/`ls`. The
  refusal message names the capability we couldn't confirm.

`HOST_INFO_VERSION` bumped 3 → 4 so existing caches re-probe and pick
up `transferShell`. `parseHostInfo` exported so the cache round-trip
of `transferShell` is testable without touching disk.

Fixes #3719
2026-06-28 11:45:25 +00:00
Tommaso Fontana dfd0fe3cfa fix(omp): reject ssh:// query/fragment and non-POSIX login shells (#3553 review)
remotePathFromUrl now rejects a URL query string or fragment, so a mistyped ssh://host/tmp/a?draft no longer silently operates on /tmp/a; a literal ?/# in a remote filename must be percent-encoded (%3F/%23).

ssh:// transfers now require a sh/bash/zsh login shell. fish can't parse the POSIX transfer snippets and csh/tcsh apply ! history expansion to the command line, so they're refused (use the ssh tool). Host-shell detection no longer misclassifies fish/csh/tcsh as sh (basename allowlist over endsWith), and HOST_INFO_VERSION is bumped to re-probe caches that stored the old classification.
2026-06-26 23:01:49 +02:00
Tommaso Fontana e4ceebb258 feat(omp): list ssh:// directories via read
- `read ssh://host/dir` lists a remote directory one level deep; `ssh://host/` lists the remote root
- add statRemotePath + listRemoteDir; resolve reads first and classifies on error (directory -> one-level listing, dirs-first, dotfiles included)
- directory resources carry isDirectory + immutable and expose no sourcePath
- search refuses a virtual (no-sourcePath) directory resource instead of grepping the listing text
- writeRemoteFile refuses a directory destination and cleans up its temp on that path
2026-06-26 20:41:35 +02:00
Tommaso Fontana f9ece90853 fix(omp): harden ssh:// URL handler per PR review
- buildSshTarget rejects destinations beginning with "-" (SSH argument-injection / local RCE guard)
- gate ssh:// read/search/write at the exec approval tier; substring scan covers search's pre-expansion delimited paths and write's hashline-wrapped paths
- validate the entire materialized buffer as UTF-8 instead of only the first 8 KiB prefix
- write peels read selectors (raw/conflicts) so it targets the same file read does, and rejects line-range/malformed selectors instead of silently stripping them
- write to a uniquely named remote temp; document symlink-replacement on write as a v1 limit
2026-06-26 20:41:35 +02:00
Tommaso Fontana c63171b909 feat(omp): add ssh:// URL support to read, search, and write 2026-06-26 20:41:35 +02:00