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
+16 -8
View File
@@ -5,6 +5,7 @@
Pi and its default tools remain local. Explicit collision-free tools perform selected operations on one configured remote server:
- `ssh_connect`
- `ssh_cd`
- `ssh_read`
- `ssh_write`
- `ssh_edit`
@@ -33,7 +34,7 @@ The resulting host ID is the only runtime selector. When a direct user request n
ssh_connect({ hostId: "<host-id>", remotePath?: "/absolute/or/~/path" })
```
`ssh_connect` is the only runtime connection surface. It is a model tool governed by `pi-permission-system`; there is no `/ssh` command, `--ssh` flag, session-resume reconnect, or user `!` remote-shell override.
`ssh_connect` is the only runtime connection surface. It is a sequential, cancellable model tool governed by `pi-permission-system`: the agent calls it as a separate step and waits for success before dependent remote tools, so establishing or replacing a connection cannot overlap another tool call. Unknown IDs return at most ten sorted imported IDs and never endpoint credentials. There is no `/ssh` command, `--ssh` flag, session-resume reconnect, or user `!` remote-shell override.
Arbitrary `user@host`, port overrides, ProxyJump, and ProxyCommand are not supported in the first pure-ssh2 version. Unsupported imported configuration is rejected rather than ignored.
@@ -69,11 +70,13 @@ Import displays the observed host-key algorithm and SHA256 fingerprint for expli
## Transport
One persistent `ssh2.Client` belongs to the active host. Connection loss fails closed and no operation is automatically replayed.
One persistent `ssh2.Client` belongs to the active host. The transport persists, but there is no persistent interactive shell or PTY. Connection loss fails closed and no operation is automatically replayed.
### Shell
`ssh_bash` opens exec channels. Commands run through `bash -lc` after changing to the selected remote cwd. Output streams through the normal Pi Bash operations callback. Abort or timeout closes the channel without reconnecting or replaying.
`ssh_bash` opens a fresh exec channel and non-interactive Bash process for every call. Commands run through `bash -lc` after changing to the active remote cwd. The cwd supplied by Pi's local Bash factory is ignored at the transport adapter boundary; only connection state may select the remote cwd. A command-local `cd` is intentionally temporary; shell-local `cd`, exports, aliases, functions, and other process state do not persist across calls. Output streams through the normal Pi Bash operations callback. Abort or timeout closes the channel without reconnecting or replaying.
`ssh_cd` is the explicit persistent workspace operation and declares Pi's sequential execution mode so it cannot overlap sibling tool calls. It resolves absolute paths, paths relative to the active remote cwd, and `~/` paths relative to remote HOME; validates the directory by executing `pwd` from it; and updates connection state without reconnecting. The model calls it as a separate step and waits for success before issuing subsequent relative file/search operations or `ssh_bash` calls that depend on the updated cwd.
### Files
@@ -92,10 +95,13 @@ Remote paths map from Pi's local factory cwd into the selected remote cwd, but p
Search tools execute one capability-adaptive, bounded shell pipeline inside the already approved tool call. They never install or upload binaries.
- `ssh_find`: `fd``fdfind``git ls-files``find`; fixed filename/path substring semantics.
- `ssh_grep`: `rg``git grep``find -exec grep`; literal case-insensitive semantics by default.
- each backend emits at most `limit + 1` lines so truncation is explicit; public `limit` is 1200; each returned line is capped.
- relative paths resolve against remote cwd and `~/` against remote home.
- `ssh_grep`: `rg``git grep``find + grep`; literal case-insensitive semantics by default, optional hidden-path inclusion, basename-only include globs, and a portable POSIX ERE subset when literal mode is disabled.
- each backend emits at most `limit + 1` NUL-delimited path/line/content records so truncation is explicit and newline filenames remain one result; public `limit` is 1200; each returned line is capped.
- relative paths resolve against remote cwd and `~/` against remote home; other `~user` forms are rejected.
- the search target root is passed to the backend as an absolute argument, while the search process executes from the active remote cwd; a single-file `ssh_grep` target is never used as a process cwd.
- searches time out after 30 seconds with the resolved root and explicit guidance to narrow it before retrying.
- user strings are single-quoted as shell arguments and NUL/newline input is rejected.
- backend stdout contains only parseable results; stderr is captured independently for warnings and failure diagnostics.
- capability detection occurs only within the reviewed search call.
- search output is normalized by `pi-ssh` and excluded from RTK compaction.
@@ -106,7 +112,7 @@ Direct search commands remain denied through `ssh_bash`; structured search tools
There is one permission gate: `pi-permission-system`.
- `ssh_connect` defaults to `ask`; its preview resolves the imported host ID to the non-secret endpoint, port, and requested/default cwd before connection.
- `ssh_read`, `ssh_write`, `ssh_edit`, `ssh_find`, and `ssh_grep` default to `ask`.
- `ssh_cd`, `ssh_read`, `ssh_write`, `ssh_edit`, `ssh_find`, and `ssh_grep` default to `ask`; remote path extractors disable local path normalization for all six tools, while previews resolve relative and `~/` requests against the active remote cwd/HOME and display both requested and absolute paths.
- `ssh_bash` is a Bash-semantic `shellTools` alias with `decisionFloor: "ask"`.
- Bash hard denies remain denies.
- All asks enter the configured authorizer chain.
@@ -114,8 +120,10 @@ There is one permission gate: `pi-permission-system`.
Evidence includes configured host ID, endpoint, port, remote cwd, and a bounded operation summary, never credentials.
Connection HOME/cwd discovery and `ssh_cd` validation use a bounded random-marker probe over a cancellable exec channel. The parser accepts exactly one framed absolute POSIX path; startup banners, malformed/multiline values, overflow, timeout, or cancellation cannot update connection state. Independent exec operations use at most four concurrent SSH channels, while SFTP operations remain serialized.
## Session lifecycle
Connections are created only by an approved `ssh_connect` call after session start. They are not persisted or automatically resumed. Connecting another imported host disposes the previous client, and session shutdown disposes the active client.
The system prompt states that default tools are local and `ssh_*` tools are remote after a connection becomes active. It does not include remote file content.
The system prompt states that default tools are local, `ssh_*` tools are remote, and each `ssh_bash` call starts a fresh non-interactive shell after a connection becomes active. It directs persistent workspace changes through `ssh_cd` and does not include remote file content.