7 min de leitura

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

A segurança do SSH ProxyCommand exige revisar a execução do shell local, expansões, inclusões e o roteamento por hosts de salto antes de agentes alcançarem um host remoto.

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

Mantenha credenciais fora do prompt
O Sallyport executa a ação SSH com credenciais e devolve o resultado sem expor a chave privada.

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

Saiba qual agente está fazendo o pedido
Aprove o novo processo do agente antes que ele possa solicitar uma ação SSH pelo Sallyport.

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

Use um modelo simples de aprovação
Os três controles fixos do Sallyport mantêm a aprovação de SSH fácil de entender, sem criar regras de política para ajustar.

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.

FAQ

O ProxyCommand é executado na máquina local?

Não. O SSH inicia o ProxyCommand no cliente antes de ter um transporte até o destino. Trate o comando e todos os programas que ele aciona como execução local, sob a identidade da pessoa ou do agente que executa o SSH.

Como vejo qual ProxyCommand o SSH vai usar?

Use ssh -G alias para inspecionar a configuração resolvida de um host nomeado e leia cada arquivo de configuração correspondente. Faça a inspeção a partir de uma conta de teste controlada quando a configuração usar Match exec, porque essa condição pode executar um comando local enquanto o SSH lê a configuração.

O ProxyJump é mais seguro que o ProxyCommand?

Em geral, sim. O ProxyJump descreve um salto SSH sem passar um comando de shell ao shell local, portanto tem menos riscos de aspas e expansões. Ainda assim, ele cria uma fronteira de confiança no host de salto e exige a mesma revisão de chaves de host e contas.

Um alias de host SSH pode causar injeção de comandos?

Sim, quando o shell recebe texto controlado por um invasor como parte da linha de comando do ProxyCommand. Aliases de host, nomes de host canônicos, nomes de usuário, portas, variáveis de ambiente e configurações incluídas podem influenciar o comando final se você não os restringir deliberadamente.

Que risco de segurança um host de salto adiciona ao SSH?

Um host de salto pode observar e retransmitir o fluxo de bytes, além de se tornar o ponto em que suas próprias credenciais SSH, configuração ou regras de encaminhamento importam. Ele não deve virar silenciosamente um host de administração genérico só por ser conveniente.

Por que Match exec é arriscado no ssh_config?

Match exec permite que o SSH execute um comando local para decidir se a configuração posterior se aplica. É útil em condições restritas e confiáveis, mas transforma a leitura da configuração em um caminho de execução de código local e merece a mesma revisão que um script helper.

Devo usar caminhos absolutos no ProxyCommand?

Caminhos absolutos impedem que uma alteração no PATH escolha outro helper, mas não tornam esse helper seguro. Você ainda precisa verificar permissões, argumentos, arquivos de configuração e as identidades com que ele se conecta.

Posso permitir com segurança que um agente de IA use SSH?

Não permita que o agente forneça argumentos SSH, aliases de host ou caminhos de configuração arbitrários. Dê a ele um pequeno conjunto de destinos revisados e mantenha os mecanismos locais de conexão fora da superfície de entrada dele.

Um cofre de credenciais SSH torna o ProxyCommand seguro?

Autorização de agentes e isolamento de segredos controlam quem pode solicitar uma ação e quem vê as credenciais. Eles não inspecionam uma configuração SSH local em busca de expansão de shell, Include ou helpers executáveis, portanto a revisão da configuração continua necessária.

Qual é a primeira coisa a auditar em uma configuração de cliente SSH?

Primeiro, enumere os aliases de host que um agente pode alcançar e execute ssh -G para cada um em um ambiente controlado. Remova entradas personalizadas de ProxyCommand que você não consegue explicar e substitua as restantes por helpers de caminho absoluto revisados ou por ProxyJump, quando fizer sentido.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov