Commit Graph
15 Commits
Author SHA1 Message Date
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 63bd006769 fix(omp): reject malformed percent-escapes in ssh:// authority (#3553 review)
decodeOr fails open, so a bad escape (ssh://prod%ZZ, ssh://user%ZZ@prod) passed the canonical authority check (both sides equally fail-open) and reached OpenSSH literally. resolveTarget now validates the encoded authority parts (url.username, url.hostname) with a throwing decodeURIComponent up front and rejects malformed escapes, matching the path decoder's fail-closed behavior. Valid percent-encoded aliases (alice%40prod) still pass.
2026-06-26 20:41:36 +02:00
Tommaso Fontana 9dd8ed12f4 fix(omp): reject empty/stray ssh:// authority markers (#3553 review)
An empty password marker (ssh://user:@prod, ssh://:@prod) leaves url.password === '' so the truthy password check skipped it. Added a canonical-authority backstop after the explicit guards: reject any authority whose decoded rawHost differs from the canonical [user@]host[:port] WHATWG parsed. This catches empty-password (and any future stray/empty marker) while every valid authority — including percent-encoded reserved-char aliases (alice%40prod, %5Bprod%3A2222%5D) — reconstructs to exactly rawHost.
2026-06-26 20:41:36 +02:00
Tommaso Fontana da2bccfdf9 fix(omp): reject ssh:// URL passwords and empty userinfo (#3553 review)
resolveTarget now rejects a non-empty password (ssh://user:pass@host, ssh://:pw@host) since ssh:// uses key/agent auth (BatchMode), and a literal empty username (ssh://@host) that previously fell through to the bare-host fallback and could match a configured host. Both use the same decoded-authority comparison as the empty-port guard, so a percent-encoded alias like %40prod (decodes to @prod) is still matched.
2026-06-26 20:41:36 +02:00
Tommaso Fontana 33a435af67 fix(omp): stat ssh:// targets before reading to reject special files (#3553 review)
resolve() ran readRemoteFile (head) before any classification, so a FIFO with no writer blocked until the 30s timeout and a device like /dev/zero streamed the whole probe. It now stats first: directory -> listing, special file (other) -> fast refusal, regular file -> read. missing/stat-failure still falls through to the read so the original remote stderr surfaces.
2026-06-26 20:41:35 +02:00
Tommaso Fontana a7d2fb3b21 fix(omp): reject ssh:// overrides on encoded configured aliases (#3553 review)
The override-rejection compared the still-encoded url.hostname, so a user/port override on a reserved-char alias (configured alice@prod addressed as ssh://bob@alice%40prod/) slipped through to the opaque target (bob@alice@prod). The guard now also compares the decoded bare host; the error names the decoded host while suggesting the correct encoded URL.
2026-06-26 20:41:35 +02:00
Tommaso Fontana ceefaf4f6b fix(omp): decode percent-encoded ssh:// username and host for the connection target (#3553 review)
The override path stored url.username/url.hostname verbatim, so ssh://user%40corp@host/ reached OpenSSH as user%40corp@host (wrong user). Decode both before building the SSHConnectionTarget. Decoding happens before buildSshTarget's leading-dash guard, so an encoded %2DoProxyCommand now decodes to -oProxyCommand and is rejected.
2026-06-26 20:41:35 +02:00
Tommaso Fontana 04bc70a8ad fix(omp): reject encoded empty ssh:// port authorities (#3553 review)
The empty-port guard compared decoded url.rawHost against the percent-encoded url.hostname/username, so ssh://prod%2Dblue:/ and ssh://u%2Dname@prod:/ slipped through. Decode both sides before comparing; a %3A alias (decoded host already ends in ':') reconstructs to 'prod::' and is still left alone.
2026-06-26 20:41:35 +02:00
Tommaso Fontana 12b966f90a fix(omp): reject empty ssh:// port (#3553 review)
An empty port (ssh://host:/path) parses with url.port === "" and slipped past the malformed-authority guard as "no port", silently using the default/configured target. resolveTarget now rejects it (raw authority retains the trailing colon; percent-encoded aliases like prod%3A keep %3A in hostname and are unaffected).
2026-06-26 20:41:35 +02:00
Tommaso Fontana f7ff11b15b fix(omp): address #3553 round-2 review feedback
- strip IPv6 URL brackets before invoking ssh (ssh://[::1]/ -> ::1)
- reject malformed/out-of-range ssh:// ports before connecting (prod:abc, host:65536)
- search requests directory metadata only (skipDirectoryListing) instead of draining a remote ls it always rejects
2026-06-26 20:41:35 +02:00
Tommaso Fontana 73c1b33fd9 fix(omp): address #3553 review feedback
- reject explicit ssh:// port 0 before connecting (Codex P2)
- keep a path-less ssh://host:port authority port out of selector peeling
- restore ResolveContext on ProtocolHandler.complete (symmetry with resolve/write)
- clarify search/read selector-parity docs + add read-side regression
2026-06-26 20:41:35 +02:00
Tommaso Fontana 3028bc4ef0 feat(omp): autocomplete ssh:// hosts and list them on bare read
- ssh handler complete() suggests the configured hosts; bare `read ssh://` resolves to an immutable host index (markdown links per host)
- thread a minimal cwd through the completion pipeline (provider basePath -> getInternalUrlSuggestions -> router.complete -> handler.complete) so project-scoped ssh.json hosts resolve like the cwd-aware bare-read path
- scoped to configured SSH-capability hosts; opaque ~/.ssh/config aliases stay usable at ssh://alias/path but are not enumerated
- local handler complete() signature updated to match (behavior preserved; the router never threaded context to it)
2026-06-26 20:41:35 +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