ssh-cli 0.5.4

Native Rust CLI that gives LLMs (Claude Code, Cursor, Windsurf) the ability to operate remote servers via SSH over stdin/stdout
Documentation
# Multiplataforma

> **0.5.4** — release de segurança e agent-native. Corrige DoS remoto pré-auth no banner SSH (A1), impede que bits setuid enviados pelo servidor caiam no arquivo baixado (A3), fecha a janela de leitura pública em chaves privadas ACME/mTLS (A2) e adiciona flags de redução de payload (`--select`, `--filter`, `--limit`, `--sort`, `--dedupe-by`, `--count-only`, `--truncate-content`, `--max-output-bytes`) aplicadas antes da serialização. BREAKING: falha parcial multi-host agora sai com exit **1** (era 65); `--bind` fora do loopback exige `--i-accept-network-exposure`. Novo evento `tunnel_closed`.


> Fuja de cola SSH específica de SO com um binário Rust portátil.

- Leia este documento em [inglês](CROSS_PLATFORM.md).
- Linha de produto: 0.5.4.


## A dor que você já conhece
- Wrappers daemon Node diferem por package manager e versão de runtime do host.
- Scripts SSH só-shell vazam segredos em histórico e listas de processos.
- Convenções de path divergem entre homes de config Linux, macOS e Windows.
- Agentes precisam de uma superfície de comando que morre após cada run em qualquer lugar.


## Matriz de suporte

| Plataforma | Status | Notas |
| --- | --- | --- |
| Linux gnu | Suportado | Alvo principal de desenvolvimento |
| Linux musl | Suportado | Use `--features musl-allocator` quando necessário |
| macOS | Suportado (type-check) | arm64 + x86_64; pode precisar remover quarentena do Gatekeeper; verificado por `scripts/check_cross_targets.sh` |
| Windows | Suportado (type-check) | UTF-8 CP 65001 + VT no boot; config via ProjectDirs; verificado por `scripts/check_cross_targets.sh` |
| WSL1 / WSL2 | Suportado | Detectado via `WSL_*` / `/proc/version`; trate como Linux |
| Containers | Suportado | Monte o dir de config ou passe `--config-dir`; doctor reporta `runtime.is_container` |
| Termux (Android) | Melhor esforço | Detectado via `TERMUX_*`; bionic quando o target existir |
| WASM / WASI | Não distribuído | `russh` precisa de sockets reais; fora do produto |
| Automação de browser | N/A | Sem descoberta Chrome/chromedriver (produto só SSH) |


## Crypto / transporte (G-TLS)
- Mesma stack crypto em todo SO suportado: **SSH-2** via `russh` + **aws-lc-rs** (não TLS/HTTPS, não OpenSSL, não `native-tls`, não `rustls` de produto).
- Host keys: arquivo TOFU no dir de config da plataforma (`directories` / XDG no Linux).
- Compressão de canal SSH é **somente `none`** (sem zlib) em todas as plataformas.
- Gates de release desta política são **locais** (`cargo deny`, testes residuais) — sem workflow cloud obrigatório.


## Linux
- Prefira `cargo install ssh-cli --locked` em `~/.cargo/bin`.
- Espere config XDG em `~/.config/ssh-cli/` por omissão.
- Garanta mode 0600 após o primeiro save de `config.toml` e `secrets.key`.
- Cifragem at-rest padrão guarda blobs em `config.toml`; mantenha backup offline de `secrets.key`.


## macOS
- Mesmo path de install cargo que no Linux.
- Limpe quarentena com `xattr -d com.apple.quarantine` quando o Gatekeeper bloquear o binário.
- Espere config sob application support/project dirs macOS resolvidos por `directories` 6.
- Backend de keyring para a primary-key é opcional via `--use-keyring` após `secrets init --keyring`.


## Windows
- Instale via Rustup e cargo em toolchain suportada (MSRV 1.85.0).
- No arranque o binário define code page **65001 (UTF-8)** e habilita
  **virtual terminal processing** para cores ANSI em conhost / PowerShell 5.1.
- Use completions PowerShell de `ssh-cli completions powershell`.
- Prefira arquivos de chave com paths explícitos em vez de atalhos de home Unix.
- Dirs de config/projeto vêm de `directories`; use `vps doctor --json` para ver o vencedor.
- Componentes de path local limitados a 255 bytes; path total perto do legado
  `MAX_PATH` (260) é rejeitado salvo prefixo estendido `\\?\`.
- Nomes no registry VPS rejeitam devices reservados do Windows (`CON`, `NUL`, `COM1`, …).


## Containers
- Copie o binário para imagens distroless ou distro sem Node.
- Persista o dir de config (ou passe `--config-dir`) para memória multi-run (`config.toml`, `known_hosts`, `secrets.key`, `active`).
- Mantenha semântica one-shot; não empacote a CLI como sidecar de longa duração sem timeout de tunnel.
- Nunca embuta segredos live ou `secrets.key` em layers de imagem.
- Marcadores de runtime (`/.dockerenv`, `/run/.containerenv`, `KUBERNETES_SERVICE_HOST`,
  `container=`) aparecem como `runtime.is_container` em `vps doctor --json`.


## Diagnóstico de runtime
- `ssh-cli vps doctor --json` embute o objeto `runtime`:
  `os`, `arch`, `is_wsl`, `is_container`, `is_ci`, `is_termux`, `sandbox`
  (`flatpak` | `snap` | null).
- Instalação sob Flatpak/Snap emite **warning** no boot (filesystem/keyring podem diferir).
- Detecção nunca faz shell-out (`uname`, `systemd-detect-virt` não são usados).


## Processos externos (G-PROC)
- **Código de produto em runtime nunca spawna filhos locais.** SSH, SCP e tunnels
  usam Rust puro (`russh`) — sem OpenSSH `ssh`/`scp`/`ssh-keygen` no host do agente.
- Elevação remota empacota `sudo`/`su` + `sh -c` **no host alvo** só via canal SSH
  (aspas; senhas no stdin do canal). Isso não é `Command` local.
- Build opcional: `git` em `build.rs` para HEAD curto (fallback env / `.commit_hash`
  / `unknown`). `Stdio` explícito null/piped; sem shell.
- Testes opcionais: fixtures `ssh-keygen` para chaves OpenSSH reais; skip se ausente.
- Toolchain MSRV **1.85.0** ≥ **1.77.2** (CVE-2024-24576 BatBadBut). Produto não
  invoca `.bat`/`.cmd`. Job Objects / process groups para árvores locais: **N/A**.
- Comandos remotos rejeitam bytes **NUL** antes do packing; CR/LF multi-linha ok.


## Suporte a shell
- Completions via `clap_complete`: **Bash, Zsh, Fish, PowerShell, Elvish**
  (`ssh-cli completions <shell>`).
- Nushell não está no enum padrão `clap_complete::Shell`; gere via tooling externo
  se precisar.
- Prefira arrays argv explícitos em runtimes de agente a eval de string shell.
- Prefira flags stdin de segredo a embutir senhas no histórico do shell.


## Paths de arquivo e XDG
- Resolva o vencedor com `ssh-cli vps doctor --json` (inclui campos `secrets_*`).
- Sobrescreva só em testes via `--config-dir` (o produto não lê `SSH_CLI_HOME`).
- Mantenha `known_hosts`, `active` e `secrets.key` como arquivos irmãos de `config.toml`.
- Escritas atômicas + flock protegem processos one-shot concorrentes no mesmo config.
- Sem store runtime de produto em `.env` para segredos, idioma ou config home.
- `locale show|set|clear` funciona em todas as plataformas (XDG / ProjectDirs via `directories`).
- Material de `tls mtls/*` e `tls acme/*` fica sob o dir de config da plataforma `tls/` (XDG no Linux).


## Superfície completa da CLI (agnóstica de SO)

O inventário de comandos é idêntico em todo SO suportado. Descubra em runtime com `ssh-cli commands` / `ssh-cli schema`.

| Superfície | Comandos |
| --- | --- |
| Registry VPS | `vps add` `vps list` `vps remove` `vps edit` `vps show` `vps path` `vps doctor` `vps export` `vps import` |
| Sessão | `connect` |
| Exec remoto | `exec` `sudo-exec` `su-exec` |
| SCP | `scp upload` `scp download` (somente arquivos regulares) |
| SFTP | `sftp upload` `sftp download` `sftp ls` `sftp mkdir` `sftp rmdir` `sftp rm` `sftp stat` `sftp rename` |
| Rede | `tunnel` (local, `--reverse`, `--socks5`, `--remote-socket`) `health-check` |
| Segredos | `secrets status` `secrets init` `secrets reencrypt` |
| Descoberta | `completions` `commands` `schema` `doctor` (alias root de `vps doctor`) |
| Locale | `locale show` `locale set` `locale clear` |
| TLS | `tls provider` `tls paths` |
| TLS mTLS | `tls mtls list` `tls mtls import` `tls mtls show` `tls mtls remove` |
| TLS ACME | `tls acme account create` `tls acme account show` `tls acme issue` `tls acme complete` `tls acme status` `tls acme list` |

Globals (todas as plataformas): `--lang`, `-v`/`-vv`/`-vvv` (escopo na crate G2/G14; `RUST_LOG` ambiente ignorado), `-q`, `--config-dir`, `--no-color`, `--output-format`/`--json`, `--disable-sudo`, `--replace-host-key`, `--allow-plaintext-secrets`, `--secrets-key-file`, `--use-keyring`, `--timeout`, `--max-concurrency` (fan-out de frota 1..=64), `--fail-fast`, `--scp-file-concurrency`.

- Prefira linha de produto **0.5.3+** para SFTP (integridade de upload G1) em toda plataforma; verifique o destino com checksum.
- Sem `.env` de produto — só XDG / ProjectDirs / `--config-dir` / flags CLI.


## Portabilidade dos modos de tunnel
- Os quatro modos de `tunnel` existem em todo SO suportado: forward local padrão, `--reverse`, `--socks5` e `--remote-socket`.
- `--remote-socket <CAMINHO>` nomeia um socket Unix no **servidor**, não nesta máquina, então o **cliente pode rodar no Windows**: localmente ele só fala TCP e pede ao servidor que alcance o socket.
- O que decide o `--remote-socket` é o suporte do servidor à extensão `direct-streamlocal@openssh.com`, detectada no wire em vez de adivinhada pela plataforma local. Cliente Windows contra servidor OpenSSH Linux funciona; cliente Linux contra servidor sem a extensão não.
- `--remote-socket` valida somente o que não pode ser válido em host POSIX algum: caminho vazio, caminho relativo ou byte NUL embutido, cada um com exit **64**. Checar existência local seria ativamente errado, porque o caminho pertence a um filesystem que esta máquina não vê.
- `--socks5` abre um listener TCP local e fala RFC 1928 no-auth CONNECT, então não tem dependência de plataforma alguma.
- `--reverse` pede ao servidor que escute; se o servidor permite esse bind é governado por `GatewayPorts` e `AllowTcpForwarding` dele, que são configurações do servidor e não traço da plataforma do cliente.
- O `--bind` **local** é parseado como endereço IP pelo clap em todo SO, então um typo falha com exit **2** antes de qualquer handshake SSH; qualquer bind roteável exige `--i-accept-network-exposure` em toda plataforma.
- O bind **remoto** sob `--reverse` é comparado como texto, e não parseado como IP (a RFC 4254 admite nomes e a string vazia), então um typo ali dá exit **64** do guard de exposição. Os dois comportamentos são idênticos em toda plataforma, porque nenhuma das checagens consulta o SO do host. Note também que `--bind` é aceito e então ignorado sob `--reverse` em toda plataforma.


## Portabilidade SCP / SFTP
- SCP é somente arquivos regulares em toda plataforma (sem transferência recursiva de diretório). Use `sftp upload|download --recursive` para árvores (sem seguir symlink).
- Prefira linha de produto **0.5.3+** para SFTP em toda plataforma (integridade de upload G1); verifique o destino com checksum.
- Downloads com falha ou em andamento usam path irmão terminando em `.ssh-cli.partial`, depois rename no lugar (padrão atômico agnóstico de plataforma).
- Upload faz stream em blocos de 32 KiB em todo SO (evita carregar o arquivo inteiro na RAM).
- Preserve de mtime/mode segue estilo OpenSSH com remoto `-p` / linha `T`; em Unix APIs locais de permissão aplicam modes; no Windows bits de permissão podem não bater com octal Unix — não assuma fidelidade POSIX ACL completa.
- Matriz real-SSH **E01–E18** (E10–E14 SCP; **E17/E18** SFTP checksum/árvore) em `scripts/e2e_real_ssh.sh` é validada principalmente em hosts Linux; prefira `sshd` local / VPS throwaway. Nunca execute tempestades de falha de autenticação em hosts de produção (banimentos fail2ban).


## Performance por alvo
- Cold start Linux é a baseline sob alvo de 100 ms.
- Builds musl podem trocar características de allocator; habilite `musl-allocator` quando necessário.
- RTT de rede domina operações remotas em todo SO.


## Agentes validados por plataforma
- Hosts Linux são a superfície principal de validação para runs de subprocesso de agente.
- macOS e Windows seguem o mesmo contrato CLI e JSON schemas.
- Aliases root de descoberta funcionam em todo SO: `ssh-cli doctor` (alias de `vps doctor`) e `ssh-cli schema` (catálogo embarcado / um corpo de schema).
- Contratos JSON (`event` scp-transfer, `tunnel_listening`, flags de auth em tunnel/health) são idênticos em todo SO; veja AGENTS.pt-BR.md e docs/schemas/.
- Tunnel `--bind` tem padrão `127.0.0.1` (loopback) em toda plataforma; sobrescreva só ao expor o listener de propósito.
- Agentes em container devem preservar exit codes e separação stdout/stderr.
- Tracing padrão é nível error para manter stderr do agente livre de prosa INFO salvo `-v`/`-vv`/`-vvv` (escopo na crate; `RUST_LOG` ambiente é ignorado).
- Parseie contratos de máquina só do stdout; trate tracing em stderr como log fora de contrato; envelopes de erro JSON usam stderr quando o modo JSON está ativo.
- Helpers de E2E SSH real ficam em `scripts/e2e_real_ssh.sh` (anti-leak; só local; **E01–E18**; nunca tempestades de auth em produção / política fail2ban).
- O carimbo de `ssh-cli --version` (versão Cargo + hash git + `-dirty` opcional) é agnóstico de SO.
- Sem store runtime de produto em `.env` — só XDG / `--config-dir` / flags CLI.


## Matriz multi-OS local (G-E2E-18)
- Código de produto: módulos `src/platform/{linux,macos,windows}.rs` — comportamento multi-OS é só local.
- Binários multi-arch locais via `scripts/dist_multiarch.sh` (e `Cross.toml`) quando toolchains cross / Docker estiverem instalados.
- **Sem CI cloud obrigatória de produto no GitHub Actions** — mantenedores validam principalmente em Linux; cheque notas de path length / agent socket em macOS e Windows antes de taggear um release.
- **Não** reintroduza `.github/workflows` para CI de produto (política: CLI one-shot, sem CI cloud de produto).