pi-ssh
pi-ssh keeps Pi and its local tools on the local machine while exposing explicit remote tools over a persistent Node ssh2 connection:
ssh_connectssh_readssh_writessh_editssh_findssh_grepssh_bash
The extension does not override Pi's local read, write, edit, find, grep, or bash tools.
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.
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.
Connections are agent-controlled: when the user explicitly names an imported host as part of a concrete remote task, the model calls ssh_connect. The connection request enters the normal permission and auto-review chain before any network connection is opened. The extension does not register /ssh, --ssh, automatic session resume, or remote user-! overrides.
ProxyJump and ProxyCommand are intentionally rejected in the first ssh2 release.
Configure hosts
From the installed bundle or this repository:
./ssh_config.sh import
./ssh_config.sh import packaging-server
The helper:
- lets you select concrete aliases from
~/.ssh/config; - uses
ssh -Gto resolve HostName, User, Port, and IdentityFile; - asks whether the selected host uses a private key or password;
- asks for a private-key passphrase when needed;
- obtains and displays the server's SHA256 host-key fingerprint;
- connects with
ssh2to verify authentication; - optionally assigns a display label and group;
- writes the encrypted vault.
Other commands:
./ssh_config.sh list
./ssh_config.sh update packaging-server
./ssh_config.sh remove packaging-server
./ssh_config.sh rotate-key
Secret prompts require an interactive terminal. Passwords and passphrases are never passed as command-line arguments.
Vault
The default paths are:
${XDG_CONFIG_HOME:-$HOME/.config}/my-pi/pi-ssh/
├── hosts.enc
└── vault.key
On POSIX systems the directory is mode 700 and both files are mode 600. hosts.enc is encrypted and authenticated with AES-256-GCM; vault.key contains the adjacent random 256-bit key. Writes use a fresh IV and an atomic temporary-file rename. Plaintext configuration is never written to a temporary file.
This is deliberately a casual-disclosure boundary, not protection against compromise of the local account: anyone who can read both files can decrypt the vault. Encryption prevents the host configuration and passwords from being exposed by accidentally viewing or copying hosts.enc alone.
The encrypted payload contains host endpoints, pinned host-key fingerprints, groups, and either:
- a private-key path plus optional passphrase; or
- the server password.
Private-key contents are not copied into the vault.
Usage
Ask Pi for a concrete task on an imported host, for example:
Connect to packaging-server, inspect the API logs, and identify the recent 500 errors.
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.
Runtime behavior
ssh_connectis the only runtime connection surface; it is agent-callable and permission-reviewed.- One persistent
ssh2client is used for the active host. - Each
ssh_bashcall opens an exec channel and runs underbash -lcin the selected remote cwd. - 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.mdandCLAUDE.mdfiles are never discovered or injected. ssh_findandssh_grepperform capability detection inside each approved call and return at most 200 bounded result lines.- RTK treats only
ssh_bashas a Bash output-compaction alias; it does not rewrite remote commands or process remote search/read results.
Adaptive remote search
ssh_find performs fixed-substring path matching with this backend order:
fd → fdfind → git ls-files → find
ssh_grep defaults to literal, case-insensitive content matching with this backend order:
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.
The remote host must provide bash. SFTP support is required for file tools.
Permission-system integration
All remote operations enter the bundle's existing permission chain:
ssh_connectstarts asask, so AutoReview can verify that the direct user request names the requested imported host;ssh_read,ssh_write,ssh_edit,ssh_find, andssh_grepstart asask;ssh_bashuses the full deterministic Bash policy anddecisionFloor: "ask";- deterministic hard denies remain denies;
- asks enter the configured
auto-reviewauthorizer; - reviewer failures defer to the normal terminal prompt.
Permission evidence includes the configured host ID, resolved endpoint, port, remote cwd, and a bounded operation summary. It never includes passwords, passphrases, private-key contents, or the vault key. Remote paths are not normalized as local filesystem paths.
Security notes
- Import only servers you control or trust.
- Verify host-key fingerprints through an independent channel before accepting them.
- Treat both vault files as secrets even though
hosts.encis encrypted. - The extension's threat model does not protect credentials from malicious code already running as the same local user.
- Password keyboard-interactive mode reuses the configured password for the server's prompts; use it only with a trusted pinned host.
- Remote content reaches the model only through an explicit reviewed
ssh_*tool call.
Development
npm test
Important files:
index.ts— Pi extension and tool registrationsrc/ssh2-transport.ts— persistent ssh2, exec, and SFTP transportsrc/config.ts— validated configuration typessrc/vault.ts— AES-GCM vaultsrc/import.ts—ssh -Gimport helpersscripts/ssh-config.mjs— interactive configuration CLIpermission-integration.ts— permission-system bridge
Upstream and license
This maintained fork originates from pansapiens/pi-ssh; see UPSTREAM.md. The pure ssh2 design also references the transport architecture in @99percentpeople/pi-ssh-remote without adopting its local-tool override model. Licensed under MIT; see LICENSE.