# Comandos SSH forçados podem conter uma conta de serviço de IA?

Um agente de IA nunca deve receber uma credencial SSH que signifique «faça tudo o que esta conta puder fazer». Isso não é um limite de permissões. É um convite para descobrir uma forma de contorná-lo, geralmente por meio de um argumento que você não esperava, uma conexão encaminhada que se esqueceu de desativar ou um script de implantação que confia demais em quem o chama.

Comandos SSH forçados dão ao servidor remoto a palavra final sobre o que será iniciado depois da autenticação. Eles funcionam bem para contas de implantação e diagnóstico porque substituem uma capacidade vaga, o acesso a um shell remoto, por uma operação nomeada que você controla e pode inspecionar. Eles não substituem a aprovação humana do uso da credencial. Mantenha esses dois controles separados: uma pessoa decide se um agente pode usar a credencial, e o servidor decide qual ação restrita essa credencial pode executar.

## Um comando forçado limita a execução, não a autenticação

Um comando forçado instrui o sshd a executar um programa escolhido pelo servidor mesmo quando o cliente solicita um shell ou fornece outro comando. O cliente ainda precisa se autenticar primeiro. Essa distinção parece óbvia até que uma conta de serviço apareça na configuração de um agente e as pessoas comecem a tratar um login bem-sucedido como se fosse uma implantação aprovada.

O OpenSSH oferece esse controle em dois lugares. Você pode anexar `command="/path/to/wrapper"` a uma chave pública em `authorized_keys` ou definir `ForceCommand` em `sshd_config` para um usuário ou grupo. Nos dois casos, o sshd registra o comando solicitado pelo cliente na variável de ambiente `SSH_ORIGINAL_COMMAND` e inicia o programa forçado no lugar dele.

O manual do OpenSSH `sshd(8)` é direto sobre a primeira parte: uma opção `command` força a execução do comando especificado depois da autenticação. O manual também documenta que o comando original continua disponível para esse programa forçado. É nesse segundo detalhe que muitos projetos frágeis falham. O wrapper recebe uma string de um cliente não confiável. Ele precisa interpretar essa string como uma solicitação, não enviá-la para um shell.

Use a forma por chave quando uma conta tiver várias credenciais cuidadosamente separadas. Uma credencial de release pode iniciar o wrapper de implantação, enquanto uma credencial de operações pode iniciar um wrapper de diagnóstico somente leitura. Assim, a intenção fica visível em `authorized_keys`, e você pode remover uma credencial sem alterar os outros acessos da conta.

Use `ForceCommand` quando a própria conta nunca puder oferecer um shell geral, independentemente da forma como ela se autentica. Isso inclui uma senha que você se esqueceu de desativar, uma futura autoridade certificadora ou um administrador que adiciona outra chave pública sem copiar as opções necessárias. Um bloco `Match User deploy` torna a regra difícil de ignorar durante uma revisão.

Não use nenhuma das duas formas para transformar uma conta de administrador humano em uma conta de automação. Em algum momento, as pessoas precisarão de um shell real para fazer reparos. Dê à automação uma conta Unix separada, uma credencial separada, um wrapper de comandos separado e limites de propriedade compatíveis com a tarefa.

## O servidor deve controlar o ponto de entrada da implantação

Uma conta de implantação deve entrar em um único script sob seu controle, não em um interpretador de comandos genérico. O script pode aceitar um vocabulário pequeno de solicitações, mas deve escolher sozinho o caminho do repositório, o diretório de destino, a unidade de serviço e o executável.

Esta entrada em `authorized_keys` restringe uma única credencial a um wrapper e nega recursos de conexão que não têm lugar em uma conta de implantação:

```text
restrict,command="/usr/local/libexec/release-gate" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... release-agent
```

A opção `restrict` é útil porque o OpenSSH a documenta como uma abreviação que desativa o encaminhamento de portas, o encaminhamento do agente, o encaminhamento X11 e a alocação de pseudo-terminal. Seu comportamento exato depende das opções de OpenSSH compatíveis com o servidor, portanto teste-a na versão que você executa. Se o seu ambiente exigir opções explícitas para revisão ou compatibilidade, escreva-as:

```text
command="/usr/local/libexec/release-gate",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... release-agent
```

O wrapper não deve aceitar um comando de implantação livre. Dê aos chamadores verbos fixos e um valor restrito. Por exemplo, os chamadores podem solicitar um release apenas por uma revisão imutável:

```text
ssh release@deploy.example "release 9f2a7c6d1e4b8a03"
```

Um wrapper seguro pode aceitar apenas essa gramática:

```sh
#!/bin/sh
set -eu

request=${SSH_ORIGINAL_COMMAND-}
case "$request" in
  "release "[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]* )
    revision=${request#release }
    case "$revision" in
      *" "*|*[!0-9a-f]*)
        echo "invalid revision" >&2
        exit 64
        ;;
    esac
    exec /usr/local/libexec/run-release "$revision"
    ;;
  *)
    echo "unsupported remote request" >&2
    exit 64
    ;;
esac
```

Este exemplo ainda precisa de uma verificação de comprimento se o formato da sua revisão exigir uma. Um wrapper de produção deve aceitar um ID completo de objeto imutável ou um identificador de release cujo formato você defina. Não aceite um nome de branch como `main` se o chamador puder movê-lo entre a aprovação e a implantação. Uma branch é um ponteiro. Uma revisão imutável permite que o registro de aprovação, o log de implantação e o artefato resultante se refiram à mesma coisa.

O script `run-release` deve usar caminhos absolutos e definir seu próprio ambiente. Não dependa de um `PATH`, diretório de trabalho, localidade, `GIT_DIR`, `GIT_SSH_COMMAND` ou `LD_PRELOAD` fornecido pelo chamador. Um início mínimo seria:

```sh
#!/bin/sh
set -eu
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH
unset CDPATH ENV BASH_ENV GIT_DIR GIT_WORK_TREE GIT_SSH_COMMAND
cd /srv/release-repo

revision=$1
/usr/bin/git cat-file -e "$revision^{commit}"
/usr/local/libexec/build-and-activate "$revision"
```

A conta deve ser proprietária apenas dos arquivos que precisa alterar. Se precisar reiniciar um serviço, conceda um único comando restrito no `sudoers`, com argumentos fixos, em vez de acesso sem senha a um gerenciador de pacotes ou shell genérico. Uma conta de implantação que pode gravar no próprio wrapper, alterar o próprio `authorized_keys` ou editar a unidade que executa seu código geralmente consegue recuperar um controle amplo. Verifique esses caminhos, não apenas a configuração do SSH.

## `SSH_ORIGINAL_COMMAND` é entrada, não uma linha de comando

O erro mais comum em comandos forçados é esta linha:

```sh
sh -c "$SSH_ORIGINAL_COMMAND"
```

Essa linha cancela o controle que você acabou de instalar. O cliente pode solicitar `release goodrev; curl ... | sh`, substituição de comandos, saída redirecionada ou um argumento cuidadosamente entre aspas que chegue a uma ferramenta privilegiada. Um wrapper que chama `eval`, `sh -c`, `bash -c` ou faz uma expansão sem aspas recriou o acesso a um shell remoto com outro nome de arquivo.

Não tente criar um analisador completo de shell. Você não precisa de um. Defina um protocolo pequeno de propósito e rejeite tudo que ficar fora dele. Para uma conta de implantação, uma solicitação pode ser um verbo mais um identificador. Para uma conta de diagnóstico, pode ser uma palavra exata, como `health` ou `version`.

Um wrapper de encaminhamento para diagnósticos pode evitar a análise por completo:

```sh
#!/bin/sh
set -eu

case "${SSH_ORIGINAL_COMMAND-}" in
  health)
    exec /usr/local/libexec/report-health
    ;;
  queue-depth)
    exec /usr/local/libexec/report-queue-depth
    ;;
  version)
    exec /usr/local/libexec/report-version
    ;;
  "")
    echo "a diagnostic name is required" >&2
    exit 64
    ;;
  *)
    echo "diagnostic is not allowed" >&2
    exit 64
    ;;
esac
```

Esses scripts de diagnóstico também precisam controlar seus argumentos. `report-health` deve chamar binários fixos contra sockets locais fixos ou nomes de serviços conhecidos. Ele não deve aceitar um parâmetro de host e executar `curl "$host"`, nem aceitar um filtro de journal e passá-lo para um shell. Um comando somente leitura ainda pode vazar credenciais de banco de dados, topologia interna, valores de ambiente ou dados de clientes.

Muitas pessoas afirmam que um comando shell cuidadosamente entre aspas é suficiente porque o agente que o chama é confiável. Essa afirmação desmorona quando o agente segue instruções hostis de um repositório, confunde um valor com uma instrução ou comete um erro comum. O servidor remoto não consegue saber se uma solicitação perigosa veio de uma ação maliciosa ou de uma chamada de ferramenta entusiasmada demais. Ele vê apenas a entrada. Torne sua decisão determinística.

Se precisar de entradas estruturadas, envie um formato limitado e analise-o com um parser que rejeite campos extras. JSON não é automaticamente mais seguro, porque um wrapper shell ainda pode tratá-lo incorretamente. Uma solicitação pequena, como `release <64 lowercase hex characters>`, é mais fácil de validar, documentar, testar e auditar do que um bloco JSON com campos opcionais.

## O encaminhamento pode contornar o espírito da restrição

Um comando forçado não impede automaticamente que um cliente autenticado use o SSH como túnel. O manual do OpenSSH trata a execução de comandos e o encaminhamento como controles separados. Se você adicionar apenas `command="..."`, um cliente ainda poderá pedir ao sshd que encaminhe uma porta local para um serviço interno, dependendo do restante da configuração do servidor.

Isso importa porque uma conta restrita pode ter acesso de rede que o agente não deveria ter. Um agente que não consegue executar `/usr/bin/ps` no host remoto ainda pode alcançar uma porta de banco de dados por esse host se o encaminhamento continuar aberto. Nesse caso, a conta deixou de ser uma identidade de implantação e se tornou um ponto de passagem pela rede.

Para uma conta que não precisa de uma sessão interativa, negue todos estes recursos, a menos que consiga explicar por que a conta precisa de algum deles:

- encaminhamento TCP
- encaminhamento do agente
- encaminhamento X11
- alocação de pseudo-terminal
- variáveis de ambiente controladas pelo usuário

`restrict` cobre as quatro primeiras categorias em implantações modernas do OpenSSH. Se a conta realmente precisar de uma exceção, não remova todo o conjunto de restrições. O OpenSSH oferece opções como `permitopen="host:port"` para limitar o destino do encaminhamento. Trate isso como um projeto de acesso separado e teste tanto os destinos permitidos quanto os negados.

Também inspecione o acesso de saída à rede do wrapper. Um script de implantação que pode buscar URLs arbitrárias, clonar repositórios arbitrários ou enviar dados arbitrários para fora tem um canal amplo, mesmo que o encaminhamento SSH esteja desativado. Fontes de artefatos fixas e revisões fixadas reduzem essa exposição. Regras de firewall ou credenciais específicas do serviço talvez precisem lidar com o restante.

## A aprovação deve acontecer antes da abertura da conexão

Um comando forçado reduz o dano que um uso aprovado do SSH pode causar. Ele não responde se o processo atual do agente deve usar a credencial. Essa decisão pertence ao limite da credencial, antes que o agente crie uma conexão SSH.

Isso é especialmente importante para agentes autônomos de programação. Um repositório pode instruir o agente a executar um comando de implantação. A saída de uma ferramenta pode solicitá-lo. Uma dependência comprometida pode conduzi-lo nessa direção. Se a credencial estiver no ambiente ou no sistema de arquivos do agente, ele poderá usá-la sem que uma pessoa veja o momento do uso.

Mantenha as chaves privadas SSH fora do processo do agente e solicite uma aprovação quando uma nova execução do agente pedir acesso pela primeira vez. Para contas de alto impacto, peça aprovação sempre que a credencial for usada. O comando forçado remoto então coloca um limite rígido na ação autorizada por essa aprovação.

O Sallyport aplica essa separação mantendo as chaves SSH em seu cofre criptografado, autorizando novos processos de agentes por sessão por padrão e executando o SSH por meio do auxiliar `sp-ssh`, em vez de entregar a chave ao agente.

Não confunda um cartão de aprovação com a autorização do servidor. A aprovação responde: «Este processo pode usar esta credencial agora?» O servidor responde: «O que esta credencial pode fazer depois do login?» Você precisa das duas respostas porque elas falham de maneiras diferentes. A aprovação pode impedir um processo inesperado. Comandos forçados podem impedir que um processo aprovado transforme uma credencial de release em um shell.

Torne a descrição da aprovação útil. Nomeie o ambiente e a ação no rótulo da credencial, como `production release` ou `staging diagnostics`. Um rótulo chamado `deploy-key-2` obriga o revisor a lembrar do histórico durante uma interrupção. É assim que aprovações rotineiras se transformam em cliques automáticos.

## Separe implantação e diagnóstico antes que a lista permitida cresça

Implantação e diagnóstico parecem semelhantes porque ambos precisam de SSH, mas têm fluxos de dados e modos de falha diferentes. Sempre que possível, coloque-os atrás de contas separadas ou de credenciais com comandos forçados separados.

Uma conta de implantação altera o estado. Ela pode buscar uma revisão fixa, criar um artefato, substituir um diretório de release e reiniciar um serviço. Sua saída deve informar a revisão, o destino, o status de saída e uma mensagem curta de falha. Ela não precisa de acesso arbitrário a logs, inspeção de processos ou consultas ao banco de dados.

Uma conta de diagnóstico lê o estado. Ela pode informar o resultado de um endpoint de saúde, uma contagem limitada de fila, a versão de um serviço ou o final de um log local cuidadosamente filtrado. Ela não deve reiniciar serviços, rotacionar arquivos, consultar todos os processos ou ler caminhos arbitrários. Assim que um wrapper de diagnóstico aceitar um nome de arquivo, unidade, host ou opção de comando fornecido pelo usuário, revise novamente seu modelo de entrada.

Uma conta combinada começa com uma lista aparentemente inocente:

```text
release <revision>
health
logs <service>
restart <service>
```

Depois alguém precisa de `logs api --since`, outra pessoa precisa de uma reinicialização de emergência, e o wrapper começa a repassar argumentos para `journalctl` ou `systemctl`. Logo, o código contém casos especiais que ninguém consegue explicar. Separe as contas antes que isso aconteça. Credenciais separadas permitem exigir uma aprovação mais rigorosa para mudanças de produção e, ao mesmo tempo, manter um fluxo de diagnóstico de menor risco.

Cada ação deve gerar um registro que informe o que o wrapper aceitou, não apenas a string opaca do comando SSH. Para um release, registre a revisão imutável e o nome do destino. Para um diagnóstico, registre o diagnóstico nomeado e se ele foi bem-sucedido. Mantenha valores secretos fora dos argumentos dos comandos e dos logs. Se uma ação precisar de um segredo, o script remoto deve obtê-lo por seu próprio mecanismo controlado, em vez de aceitá-lo do cliente SSH.

## Teste os caminhos de negação a partir de um cliente descartável

Uma conta restrita só está realmente restrita depois que você testa as solicitações que ela precisa rejeitar. Execute estas verificações a partir de uma conta descartável ou de um host de teste antes de confiar na configuração em um ambiente de produção. Os exemplos presumem que a credencial já está instalada no servidor.

```sh
ssh release@deploy.example "release 9f2a7c6d1e4b8a03"
ssh release@deploy.example
ssh release@deploy.example "id"
ssh release@deploy.example "release 9f2a; id"
ssh -N -L 15432:db.internal:5432 release@deploy.example
ssh -tt release@deploy.example "health"
```

O primeiro comando deve chegar apenas ao wrapper de release se a revisão atender às regras. Os três comandos seguintes devem falhar com a mensagem de negação do wrapper e um status de saída diferente de zero. A tentativa de encaminhamento de porta deve falhar antes de estabelecer um listener. A solicitação de terminal deve falhar ou ser executada sem terminal, dependendo de como o cliente informa a alocação negada.

Depois, teste os casos menos óbvios. Tente espaços no início e no fim, tabulações, um comando vazio entre aspas, caracteres de nova linha, um argumento muito longo, espaços em branco Unicode, substituições de comandos, redirecionamentos e argumentos duplicados. Se o shell ou o wrapper normalizar algum deles para uma solicitação aceita, torne a gramática mais rígida.

Verifique as permissões e a propriedade dos arquivos da conta como parte do teste. Um invasor que possa substituir `/usr/local/libexec/release-gate` não precisa contornar o SSH. Uma conta que possa alterar sua própria fonte de implantação também pode alterar o código executado com mais privilégios. Inspecione toda a cadeia: `authorized_keys`, configuração do sshd, wrapper, scripts de implantação, definições de serviço, diretórios graváveis e qualquer entrada no `sudoers`.

## Audite a solicitação dos dois lados do limite

Os logs remotos explicam o que o servidor aceitou. Os logs do limite da credencial explicam qual processo local solicitou a capacidade de se conectar. Mantenha os dois, porque nenhum consegue responder à pergunta do outro.

No lado remoto, registre o sucesso da autenticação, a operação aceita pelo wrapper forçado, a revisão imutável ou o nome do diagnóstico, um identificador da solicitação e o status final. Envie esses registros para um local que a conta de serviço não possa reescrever. Não registre o `SSH_ORIGINAL_COMMAND` bruto se os chamadores puderem colocar material secreto nele, e não permita que seu protocolo aceite material secreto em primeiro lugar.

No lado local, mantenha a identidade da sessão e a solicitação individual de ação SSH. O Sallyport registra execuções de agentes e chamadas individuais em diários separados, dentro de um único log de auditoria criptografado e encadeado por hash. `sp audit verify` pode verificar essa cadeia offline sem uma chave do cofre.

Uma cadeia de hashes não transforma uma permissão ruim em uma boa. Ela facilita detectar alterações posteriores no histórico registrado. Isso é útil depois de uma implantação malsucedida, de uma aprovação contestada ou de uma solicitação de comando inesperada. Também impõe uma disciplina bem-vinda: defina cedo o vocabulário de ações para que os registros digam algo que uma pessoa consiga entender.

A primeira implementação deve ser sem graça. Crie uma conta Unix dedicada, um wrapper forçado, uma operação com gramática de entrada fixa, encaminhamento desativado e um teste que prove que `ssh account@host` não oferece um shell. Adicione recursos somente quando conseguir nomear sua entrada, saída, acesso a arquivos, acesso à rede e a pessoa que deve aprová-los.
