# A segurança do SSH ProxyCommand é mais fraca do que parece?

O SSH ProxyCommand executa código local antes do comando remoto que você pretendia executar. Isso parece óbvio depois de dito, mas revisões costumam tratá-lo como um detalhe de transporte: o agente pede `ssh host uptime`, quem revisa verifica o host e o comando remoto, e o cliente inicia discretamente um comando de shell local que não entrou na decisão de aprovação.

Essa lacuna importa ainda mais quando um agente autônomo de programação pode chamar SSH. O destino remoto pode estar bem delimitado, enquanto a configuração SSH local contém aliases, inclusões, regras condicionais, um helper de proxy e uma cadeia de hosts de salto acumulados ao longo dos anos. Uma revisão segura começa pela ação no lado do cliente que o SSH executa e depois avança para a rede.

## O ProxyCommand é executado antes de a sessão remota existir

`ProxyCommand` instrui o cliente SSH a iniciar um comando local e usar sua entrada e saída padrão como transporte até o servidor SSH. O comando não é executado no destino. Ele roda na máquina que iniciou o `ssh`, sob a conta local, antes que o SSH possa autenticar o servidor pretendido.

Uma entrada comum parece inofensiva:

```
Host build-private
    HostName build.internal.example
    ProxyCommand /usr/local/bin/connect-private %h %p
```

Quando alguém executa `ssh build-private`, o SSH expande `%h` e `%p` e inicia `/usr/local/bin/connect-private build.internal.example 22`. O helper pode abrir um socket, chamar outro cliente, ler um arquivo de token, carregar uma biblioteca, consultar variáveis de ambiente ou executar um script de shell. A partir daí, o cliente SSH vê apenas um fluxo de bytes. A solicitação de aprovação pode descrever uma conexão SSH, mas a primeira ação relevante foi iniciar um programa local.

O manual do OpenSSH para `ssh_config` é claro sobre essa fronteira: `ProxyCommand` especifica o comando usado para conectar ao servidor e avisa que o comando é executado pelo shell do usuário. Essa última cláusula muda a revisão. Uma linha de comando não é uma lista de argumentos. A análise do shell, expansões, redirecionamentos, substituição de comandos e o ambiente de inicialização do shell fazem parte da ação.

Não ignore isso porque o helper está em um repositório de dotfiles. Uma configuração revisada há seis meses pode chamar um binário encontrado pelo `PATH`, carregar um arquivo em um diretório gravável ou depender de um alias que hoje aponta para outra coisa. O alvo da revisão é o caminho de execução resolvido na máquina que vai executá-lo.

## Um alias de host pode selecionar muito mais do que um host

A configuração SSH é um sistema de correspondências, não um dicionário simples. Um alias curto pode acionar definições da configuração do sistema, da configuração do usuário, de fragmentos incluídos, de hosts curinga, de canonicalização e de blocos condicionais. O comando final pode vir de um arquivo que ninguém associa ao alias.

Comece enumerando as configurações que o SSH lê. Em um cliente OpenSSH comum, isso inclui `/etc/ssh/ssh_config`, arquivos alcançados por `Include` e `~/.ssh/config`. Empacotamento da distribuição e opções de linha de comando podem alterar a lista, portanto inspecione também a chamada. Quem fornece `-F /some/file` mudou toda a superfície de revisão.

Use `ssh -G` para imprimir a configuração resolvida de um destino concreto. Para um alias de teste controlado, este é um registro útil:

```
ssh -G build-private | grep -E '^(hostname|user|port|proxycommand|proxyjump|identityfile) '
```

A saída tem este formato:

```
hostname build.internal.example
user deploy
port 22
proxycommand /usr/local/bin/connect-private %h %p
identityfile ~/.ssh/id_deploy
```

`ssh -G` mostra o que o SSH selecionou e detecta uma quantidade surpreendente de erros: uma entrada ampla de `Host *`, uma inclusão antiga ou um alias que aponta para uma conta diferente da esperada. Ele não prova que o helper é seguro. Também pode não explicar todas as condições que levaram a esse resultado de uma forma que quem revisa entenda, então siga cada diretiva relevante até seu arquivo de origem.

Trate a entrada de host como dado somente se a configuração a mantiver como dado. Se um agente pode enviar aliases arbitrários, ele pode escolher qualquer bloco `Host` correspondente que a conta local consiga ler. Se puder enviar opções `-o` arbitrárias ou um caminho de configuração, em muitos casos poderá ignorar por completo a revisão do alias. Um wrapper que aceita um comando SSH de forma livre não é uma fronteira relevante.

Uma interface melhor aceita um identificador de destino de uma pequena lista de permissões e o associa a uma conta, nome de host, porta e método de conexão fixos. O identificador pode ser `production-readonly`; os argumentos SSH subjacentes não devem vir de uma string gerada pelo agente.

## A expansão do shell transforma a configuração em uma fronteira de entrada

Como o SSH passa o `ProxyCommand` pelo shell, aspas na configuração não criam a mesma propriedade de segurança que argumentos estruturados de processo. A sintaxe do shell permanece até que ele a processe. Isso inclui espaços, caracteres curinga, referências a variáveis, redirecionamentos e substituições de comandos quando aparecem no texto do comando.

O padrão arriscado é um helper que monta seu próprio comando de shell a partir dos valores que recebe. Considere esta configuração:

```
Host *
    ProxyCommand sh -c 'relay --target %h --port %p --token \"$RELAY_TOKEN\"'
```

Ela traz várias perguntas separadas. Todo nome de host resolvido possível contém apenas a sintaxe de nome de host esperada? O `relay` interpreta `--target` como um único valor? Um ambiente controlado pelo usuário pode definir `RELAY_TOKEN` ou alterar o caminho de busca? O shell externo recebe uma string de comando com caracteres que mudam de significado antes de o `relay` começar? Dizer que o nome de host veio do SSH não responde a essas perguntas.

Tokens de porcentagem não são um sistema seguro de modelos. O OpenSSH expande tokens como `%h`, `%p`, `%r` e `%n` em muitas opções. `%n` é o argumento de host original, enquanto `%h` é o nome de host que o SSH usará após processar a configuração. Essa diferença passa despercebida com facilidade. Um comando de proxy que usa `%n` pode receber um alias amigável ou texto solicitado arbitrário onde a pessoa autora esperava um nome DNS canônico. Um comando que usa `%h` ainda pode receber um valor alterado por `HostName` ou pela canonicalização.

A correção costuma ser menos sofisticada que a configuração original. Use um helper pequeno e dedicado com caminho executável fixo, dê a ele uma sintaxe restrita e faça-o rejeitar tudo que ficar fora dela. Faça o helper chamar APIs de conexão ou um iniciador de processos por lista de argumentos, em vez de montar outra linha de shell. Se um script de shell for inevitável, valide as entradas antes de interpolá-las, coloque aspas em cada expansão para esse shell e mantenha o conjunto aceito restrito o bastante para que uma pessoa auditora consiga testá-lo.

Não substitua análise por `eval`. Equipes recorrem a ele porque deixa um wrapper orientado pela configuração aparentemente flexível. Também transforma uma única aspa esquecida em uma falha de execução local. A flexibilidade deve estar em um formato de configuração revisado, não em um shell que reanalisa texto.

## ProxyJump elimina um shell, mas não o problema de confiança

`ProxyJump`, muitas vezes escrito como `-J` ou `ProxyJump`, pede ao SSH que alcance o destino por meio de um ou mais hosts de salto SSH. Para roteamento comum por bastion, prefira-o a um `ProxyCommand` escrito à mão que apenas inicia outro comando `ssh -W`. Ele descreve a topologia pretendida e evita colocar essa topologia em um comando de shell personalizado.

Por exemplo:

```
Host build-private
    HostName build.internal.example
    User deploy
    ProxyJump bastion-admin

Host bastion-admin
    HostName bastion.example
    User relay
```

Isso é mais fácil de inspecionar do que uma string com aspas aninhadas. Ainda exige revisão cuidadosa. O cliente se autentica no host de salto, o host de salto participa da rota e seu nome, conta, chave de host, exigências de MFA, regras de encaminhamento e alcance na rede afetam a ação. Um host de salto comprometido ou mal configurado pode expor padrões de tráfego e redirecionar uma tentativa de conexão. A verificação de chaves de host continua obrigatória em cada salto SSH.

O OpenSSH permite ambas as configurações, mas `ProxyCommand` tem precedência sobre `ProxyJump` quando ambas se aplicam. Essa é uma surpresa desagradável na configuração. Quem revisa pode ver um `ProxyJump` limpo em um bloco específico do host enquanto uma regra de correspondência ampla, anterior ou posterior, fornece um comando de proxy. Verifique o resultado efetivo com `ssh -G` em vez de deduzi-lo a partir de um único bloco.

Vários hosts de salto também precisam de um motivo explícito. Não trate uma cadeia como segurança extra por padrão. Cada host adicionado traz outro uso de credencial, outra decisão de chave de host e outro ponto de auditoria. Use uma cadeia apenas quando o caminho de rede exigir e registre qual conta é esperada em cada salto.

## Include e Match exec podem executar lógica local durante a seleção

`Include` torna a configuração SSH modular, o que é útil até que um repositório, ferramenta de gerenciamento de configuração ou instalador coloque um fragmento em um diretório de correspondência ampla. O OpenSSH expande padrões curinga em `Include`; portanto, um arquivo inesperado pode alterar as configurações de um host sem tocar na configuração principal. Revise a propriedade e as permissões de gravação dos diretórios incluídos, não apenas seu conteúdo atual.

`Match` adiciona condições. `Match host`, `user`, `localnetwork` e condições relacionadas mudam quais opções se aplicam. `Match exec` merece um alerta maior: o SSH executa o comando local indicado e usa seu status de saída para decidir se o bloco corresponde. Ler a configuração pode, portanto, iniciar um executável local.

Este exemplo deixa a fronteira visível:

```
Match exec \"/usr/local/bin/on-corporate-network\"
    ProxyJump corp-bastion
```

A condição pode ser razoável se o helper for um programa fixo, pertencente ao root e com resultado simples. Ela se torna frágil quando chama um shell, lê um arquivo de projeto gravável, contata um serviço de rede ou depende do `PATH`. Não teste regras desconhecidas de `Match exec` em uma estação de trabalho cheia de credenciais ativas apenas porque você planejava executar `ssh -G`; primeiro inspecione o comando e, quando necessário, use uma conta ou máquina isolada.

O manual do OpenSSH também documenta expansão de tokens em opções de correspondência e relacionadas a comandos. Isso significa que uma condição `Match exec` pode depender do texto solicitado de host ou usuário. Mantenha valores escolhidos pelo agente fora dessas condições. Se a condição precisar de dados contextuais, derive-os de um arquivo de estado local confiável, com propriedade rigorosa, e não de uma string que atravessou uma fronteira de agente.

## O incidente geralmente começa com um helper de conveniência

Uma falha realista não precisa de uma configuração com aparência maliciosa. Imagine que uma pessoa desenvolvedora tenha adicionado isto anos atrás para alcançar uma rede privada de testes:

```
Host test-*
    ProxyCommand connect-testnet %h %p
```

`connect-testnet` foi instalado certa vez por um script de inicialização em um diretório pessoal de binários. O script chama um cliente VPN corporativo e depois inicia um comando de relay encontrado no `PATH`. Mais tarde, um agente de compilação roda sob a conta da pessoa desenvolvedora. Ele pode pedir `ssh test-cache`. A ação esperada é um comando somente leitura em uma máquina de teste.

Primeiro, o SSH corresponde a `test-*`. Depois, o shell local inicia `connect-testnet`. Em seguida, o script inicia o cliente VPN e qualquer executável que vencer a busca no `PATH`. Só após essas etapas locais o SSH inicia seu protocolo com o host nomeado. Uma auditoria que registra apenas o comando remoto perde o comportamento que alterou o estado da máquina local e selecionou a rota de rede.

O defeito não está em scripts de shell serem proibidos. O defeito está em tratar uma cadeia local de conveniência como se fizesse parte do serviço remoto. A capacidade do agente de escolher `test-cache` selecionou código local. Um agente malicioso poderia procurar aliases que correspondem a padrões mais amplos, mas um agente comum pode disparar o mesmo problema ao adivinhar um nome que por acaso existe.

Corrija o caminho real. Quando possível, substitua o curinga por destinos nomeados. Coloque o helper de relay em um caminho absoluto. Remova buscas por `PATH` dentro dele. Faça o helper aceitar apenas os valores de host e porta esperados. Tire o gerenciamento da conexão VPN de um comando de proxy por solicitação, se ele não precisar rodar em toda ação SSH. Depois, teste a configuração final com a conta e o ambiente que o agente usará.

## Aprove o método de conexão, não apenas o destino

Um fluxo de aprovação precisa mostrar informações suficientes para que uma pessoa reconheça a ação. `ssh deploy@build-private` é contexto incompleto quando o alias inicia um helper de proxy ou passa por um bastion. Quem revisa precisa do destino resolvido, da conta, da porta, do método de proxy e do caminho de salto. Caso contrário, aprova um rótulo e espera que a configuração local ainda signifique o mesmo que significava na semana passada.

Separe três decisões que as equipes costumam misturar. Primeiro, esse processo de agente pode solicitar alguma ação SSH? Segundo, ele pode usar esta identidade SSH específica? Terceiro, este destino pode usar este método de conexão local? Um sim para as duas primeiras não responde automaticamente à terceira. O helper de proxy pode alcançar outra zona de confiança ou causar efeitos locais que a conexão direta não causaria.

O Sallyport mantém as credenciais SSH fora do agente e usa seu helper `sp-ssh` incluído para ações SSH, mas isso não transforma uma configuração SSH local em uma superfície de política segura. Mantenha estreita a interface de destino do agente e inspecione toda configuração de cliente ou processo helper que participe antes que a ação com credenciais comece.

Para caminhos sensíveis, exija aprovação a cada uso da identidade SSH e faça a descrição da aprovação incluir o destino e a rota. Isso detecta um alias alterado, um bastion inesperado ou uma tentativa de uso fora do caminho normal de manutenção. Não torna útil um cartão de aprovação vago. As informações precisam estar lá.

## Teste o caminho resolvido com credenciais descartáveis

Uma revisão de configuração precisa de um teste de execução, mas faça-o com credenciais que não possam causar danos à produção. Use um host de teste, uma conta temporária e um ambiente controlado. Capture a árvore de processos do lado do cliente e os destinos de rede, se o sistema operacional permitir. O objetivo é confirmar qual executável é iniciado, quais argumentos ele recebe e se cria conexões além da rota esperada.

Uma sequência compacta de revisão é suficiente para a maioria dos aliases de host:

1. Execute `ssh -G alias` e salve as configurações resolvidas de `hostname`, `user`, `port`, `proxycommand`, `proxyjump` e identidade.
2. Rastreie cada `Include` e cada bloco correspondente de `Host` ou `Match` que forneceu esses valores.
3. Leia cada executável local no caminho de proxy ou de correspondência, incluindo scripts e seus arquivos de configuração.
4. Execute a conexão com credenciais descartáveis e verifique os hosts de salto reais, as chaves de host e os processos locais.
5. Revogue a credencial de teste após o teste e registre a rota esperada ao lado do alias.

Não confie apenas em uma conexão bem-sucedida. O sucesso prova que os bytes chegaram a um servidor SSH. Não prova que o programa certo os transportou, que o host pretendido os recebeu ou que nenhum efeito local ocorreu antes.

Um pouco de atrito é apropriado aqui. Se ninguém consegue explicar o executável por trás de um ProxyCommand, ninguém deve autorizar um agente a chamar o alias. Substitua-o, remova-o ou mantenha a rota fora do alcance do agente até que alguém assuma sua responsabilidade.

## Uma superfície SSH pequena supera uma configuração de cliente sofisticada

A configuração SSH mais segura voltada para agentes tem poucos destinos nomeados, identidades fixas, hosts de salto explícitos e nenhuma flag de configuração arbitrária. Ela não precisa de uma linguagem de política de uso geral para alcançar isso. Precisa de uma interface limitada e de uma configuração que continue simples sob inspeção.

Revise novamente quando algum destes itens mudar: uma atualização do cliente SSH, um novo script de inicialização, uma implantação de gerenciamento de configuração, um diretório de inclusão adicionado, um novo ambiente de execução do agente ou um novo host de salto. Essas mudanças costumam chegar em repositórios separados, e é exatamente por isso que o caminho de conexão se deteriora sem que ninguém perceba.

Mantenha o padrão final simples: antes que um comando remoto seja executado, você deve conseguir nomear cada executável local que o SSH inicia, cada host que ele contata, cada identidade que usa e a pessoa que aprovou essa rota. Se você não consegue fazer isso para um alias, ele não está pronto para uso autônomo.
