fix(pi-ssh): harden remote execution and search

This commit is contained in:
云服务部-叶林立
2026-08-24 23:04:29 +08:00
parent a7891f18bf
commit aff91b0972
30 changed files with 1063 additions and 222 deletions
+18 -9
View File
@@ -3,6 +3,7 @@
`pi-ssh` keeps Pi and its local tools on the local machine while exposing explicit remote tools over a persistent Node `ssh2` connection:
- `ssh_connect`
- `ssh_cd`
- `ssh_read`
- `ssh_write`
- `ssh_edit`
@@ -14,7 +15,7 @@ The extension does not override Pi's local `read`, `write`, `edit`, `find`, `gre
## Architecture
Runtime connections are pure `ssh2`; the extension does not spawn OpenSSH and does not require `sshpass`, `ControlMaster`, or passwordless login. Remote file operations use SFTP and remote shell commands use an SSH exec channel.
Runtime connections are pure `ssh2`; the extension does not spawn OpenSSH and does not require `sshpass`, `ControlMaster`, or passwordless login. Remote file operations use SFTP and remote shell commands use an SSH exec channel. The SSH transport persists, but commands intentionally use fresh non-interactive Bash processes rather than a hidden stateful PTY.
Hosts must be explicitly imported before use. OpenSSH remains only an import source: the configuration helper runs `ssh -G <alias>` once to resolve the selected alias, then stores the resulting endpoint and authentication data in the pi-ssh vault. Later changes to `~/.ssh/config` require re-importing the host.
@@ -88,20 +89,24 @@ The model first calls:
ssh_connect({ hostId: "packaging-server" })
```
It may set `remotePath` to an absolute path, `~`, or a path beginning with `~/`. After the reviewed connection succeeds, the model uses the other `ssh_*` tools to complete the requested work. Only imported host IDs are accepted; arbitrary `user@host` targets are rejected. A new connection replaces the previous active connection, and session shutdown disconnects it.
It may set `remotePath` to an absolute path, `~`, or a path beginning with `~/`. The model calls sequential `ssh_connect` as a separate step and waits for the reviewed connection to succeed before using other `ssh_*` tools. `ssh_cd` explicitly changes the active remote workspace for subsequent shell, relative file, and relative search operations; it accepts absolute paths, paths relative to the current remote cwd, and `~/` paths relative to remote HOME. The model must also call `ssh_cd` as a separate step and wait for its successful result before issuing dependent remote operations. Only imported host IDs are accepted; arbitrary `user@host` targets are rejected. Unknown IDs report a bounded list of available imported IDs without exposing credentials. A new connection replaces the previous active connection, and session shutdown disconnects it.
## Runtime behavior
- `ssh_connect` is the only runtime connection surface; it is agent-callable and permission-reviewed.
- One persistent `ssh2` client is used for the active host.
- Each `ssh_bash` call opens an exec channel and runs under `bash -lc` in the selected remote cwd.
- `ssh_connect` is the only runtime connection surface; it is agent-callable, permission-reviewed, sequential, and cancellable so connection replacement cannot overlap another tool call or remain stuck after user cancellation.
- One persistent `ssh2` client is used for the active host; no interactive shell or PTY state is retained.
- `ssh_cd` is a sequential state transition: it validates a remote directory and updates the active workspace without reconnecting. Dependent tool calls must wait for it to succeed.
- HOME/cwd probes use bounded random-marker framing, require one absolute POSIX path, and preserve the previous state on malformed output, timeout, or cancellation.
- Independent exec channels use bounded concurrency of four; shared SFTP operations remain serialized.
- Each `ssh_bash` call opens a fresh exec channel and runs under `bash -lc` in the active remote cwd. A command-local `cd` is temporary, and `cd`, `export`, alias, function, or other shell state inside one call does not persist to the next call.
- SFTP provides remote reads and writes.
- Writes use a temporary remote file and prefer OpenSSH's atomic rename SFTP extension when the server supports it.
- A pinned SHA256 host-key mismatch fails closed.
- Connection loss fails closed; the extension does not silently replay a command.
- Remote `AGENTS.md` and `CLAUDE.md` files are never discovered or injected.
- `ssh_find` and `ssh_grep` perform capability detection inside each approved call and return at most 200 bounded result lines.
- `ssh_find` and `ssh_grep` perform capability detection inside each approved call and return at most 200 bounded result lines. Search targets are always separate from the execution cwd, so `ssh_grep` can target a single remote file without attempting to enter it as a directory. A 30-second timeout reports the resolved root and instructs the model to narrow it with `ssh_find`.
- RTK treats only `ssh_bash` as a Bash output-compaction alias; it does not rewrite remote commands or process remote search/read results.
- Permission previews display resolved absolute remote paths together with the original relative or `~/` request; remote paths never enter local filesystem gates.
### Adaptive remote search
@@ -111,13 +116,15 @@ It may set `remotePath` to an absolute path, `~`, or a path beginning with `~/`.
fd → fdfind → git ls-files → find
```
`ssh_grep` defaults to literal, case-insensitive content matching with this backend order:
`ssh_grep` defaults to literal, case-insensitive content matching with this backend order. Hidden paths are excluded unless `includeHidden` is true, and `include` is a basename-only glob such as `*.ts` (globs containing `/` are rejected):
```text
ripgrep → git grep → find + grep
```
No backend is installed or uploaded. The tools use what the server already provides, report the chosen backend and truncation state, and cap `limit` at 200. A genuine no-match result succeeds with zero rows; invalid regexes, missing roots, and backend failures remain errors even though output passes through bounded `head`/`cut` stages. Narrow `path` and `pattern` when truncated rather than increasing the limit. Direct `find`, `fd`, `grep`, and `rg` commands remain forbidden through `ssh_bash`; use the structured search tools instead. Regex mode (`literal: false`) follows the selected backend's regex dialect.
No backend is installed or uploaded. The tools use what the server already provides, report the chosen backend and truncation state, and cap `limit` at 200. `literal: false` accepts the portable POSIX ERE subset shared by the backends; Git and fallback grep are explicitly run in ERE mode. Grep results use bounded NUL-delimited path/line/content records, so newline-containing filenames cannot corrupt match counts or truncation. Backend stderr is kept out of result rows and is reported as a warning on success or failure detail on error.
A genuine no-match result—including the `find + grep` fallback—succeeds with zero rows; invalid regexes, missing roots, and backend failures remain errors. Search paths accept absolute paths, relative paths, `~`, and `~/...`; `~user` expansion is rejected. Narrow `path` and `pattern` when truncated rather than increasing the limit. Direct `find`, `fd`, `grep`, and `rg` commands remain forbidden through `ssh_bash`; use the structured search tools instead.
The remote host must provide `bash`. SFTP support is required for file tools.
@@ -126,7 +133,7 @@ The remote host must provide `bash`. SFTP support is required for file tools.
All remote operations enter the bundle's existing permission chain:
- `ssh_connect` starts as `ask`, so AutoReview can verify that the direct user request names the requested imported host;
- `ssh_read`, `ssh_write`, `ssh_edit`, `ssh_find`, and `ssh_grep` start as `ask`;
- `ssh_cd`, `ssh_read`, `ssh_write`, `ssh_edit`, `ssh_find`, and `ssh_grep` start as `ask`;
- `ssh_bash` uses the full deterministic Bash policy and `decisionFloor: "ask"`;
- deterministic hard denies remain denies;
- asks enter the configured `auto-review` authorizer;
@@ -153,6 +160,8 @@ Important files:
- `index.ts` — Pi extension and tool registration
- `src/ssh2-transport.ts` — persistent ssh2, exec, and SFTP transport
- `src/remote-bash.ts` — stateless Bash adapter pinned to the active remote cwd
- `src/remote-cwd.ts` — explicit remote workspace path resolution
- `src/config.ts` — validated configuration types
- `src/vault.ts` — AES-GCM vault
- `src/import.ts``ssh -G` import helpers