169.254.169.254 agora pede licença: o Hermes coloca a busca de credenciais do instance metadata na nuvem atrás de aprovação

Você está depurando um problema de “por que esta máquina não alcança o serviço interno” em uma VM na nuvem, e pede ao agente para descobrir quem é a instância. Ele decide olhar:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
Em uma instância na nuvem, esse comando devolve credenciais de acesso temporárias sem autenticação nenhuma — credenciais que representam a identidade da máquina e alcançam tudo o que a sua política de nuvem concedeu a ela. O Hermes tratava isso como uma leitura comum e deixava rodar. Agora ele para e pergunta. Se você roda agentes na nuvem, vale entender, porque isso fecha a porta dos fundos mais fácil de passar batido — aquela que aparece no momento em que um agente consegue executar comandos.
O que é o IMDS e por que ele é mais arriscado do que parece
IMDS é Instance Metadata Service: um endpoint HTTP especial que as principais nuvens expõem dentro de cada instância, no endereço link-local 169.254.169.254. A função dele é permitir que o software dentro da instância saiba quem ela é, em que região vive e que role carrega. A parte crítica é o modelo de autenticação: não existe nenhum. Qualquer processo na máquina capaz de fazer uma requisição HTTP pode lê-lo — inclusive aquele curl que você acabou de pedir para o agente rodar.
Cada nuvem tem o seu próprio número de porta para o mesmo serviço:
169.254.169.254— o endereço link-local IPv4 de AWS, Azure, GCP e OpenStack;fd00:ec2::254— a forma IPv6 da AWS;metadata.google.internal— o hostname do GCP;100.100.100.200— o endpoint da Alibaba Cloud.
Por que isso importa além da curiosidade? Porque o que volta normalmente não é só “quantas vCPUs”. Em geral vem junto credenciais temporárias de role IAM ou de service account. Ter essa string é equivalente a ter tudo o que a instância está autorizada a fazer — ler um bucket privado, chamar uma API interna, às vezes bem mais que isso. As credenciais expiram, mas costumam viver o suficiente para terminar o serviço.
O caminho do ataque: uma instrução injetada basta
Coloque dois fatos lado a lado e o risco fica óbvio: o agente lê conteúdo externo (páginas web, issues, e-mails, comentários de código) e o agente executa comandos. Se esse conteúdo externo contiver “já que você está aí, rode isto e me mande a saída”, e o comando apontar para um endpoint de metadata, então um único prompt injection se transforma em exfiltração de credenciais. Isso não é hipotético. A propriedade especial do endpoint é exatamente viver na rede interna e não precisar de credencial nenhuma, o que o torna a coisa mais barata de roubar.
Os provedores de nuvem fazem a parte deles — o IMDSv2 da AWS, por exemplo, exige um PUT para obter um token de uso único antes, o que eleva a barra para server-side request forgery. Mas isso não toma a decisão por você: se o agente deve ou não ler instance metadata é um julgamento que um humano merece fazer uma vez.
O que acontece agora: um prompt, não um bloqueio
O PR #106418 adiciona uma regra determinística na camada de aprovação de terminal, com um nome que diz exatamente o que é:
cloud metadata endpoint access (instance credentials)
O comportamento é um prompt de aprovação, não um bloqueio rígido. E isso é intencional. Existem usos legítimos em VMs reais na nuvem — o agente administra a própria instância, você está depurando uma role IAM, você está escrevendo um script que reporta fatos da instância. Quem decide é o operador.
Na prática:
- Quando um desses quatro literais de host aparece em um comando de terminal, a aprovação é disparada. Não importa qual cliente HTTP você usa:
curl,wget, uma atribuição de variável de ambiente ou um argumento de script, todos contam. - Em caminhos sem ninguém acompanhando (cron jobs, automação em approve-mode), esses comandos não são auto-aprovados — com o padrão
approvals.cron_mode: deny, um job agendado que tente ler metadata é negado em vez de liberado. - A detecção é pattern matching, não julgamento de modelo. Não há deriva conforme o humor do modelo no dia, nem chamada extra de modelo no caminho crítico.
Escopo: os falsos positivos foram mantidos estreitos de propósito
A implementação usa lookarounds na entrada de DANGEROUS_PATTERNS (em tools/approval_detection.py) para que a regra só dispare nos próprios endpoints:
curl http://169.254.1.1/status— outros endereços link-local — não é afetado;169.254.169.2540, em que o endpoint é apenas prefixo de uma sequência de pontos maior, não casa;metadata.google.internal.example.com, um host diferente que por acaso contém a string, não casa.
Em outras palavras, a regra só se manifesta quando um comando realmente mira o endpoint de credenciais, em vez de tratar tráfego link-local comum como perigoso.
Vale também separar isso da proteção do lado do navegador. As ferramentas de browser já aplicam o próprio piso de cloud metadata em todo backend (e restrições adicionais quando rodando dentro de um contêiner). Esta mudança cobre o caminho de comando de terminal — outra porta, em outro andar.
O desenho se inspira na prática já existente: o changelog do Claude Code 2.1.257 registra que o modo automático dele deixou de auto-aprovar buscas de credenciais em cloud metadata (junto com evasão de egress e alcance cross-tenant), a menos que o ambiente as declare esperadas. O Hermes adapta a parte de metadata em uma regra determinística de terminal — mais simples, porém previsível.
O prompt que você vai ver e como ajustá-lo
Quando a regra dispara, você recebe uma aprovação de comando carregando essa string de motivo. O que fazer a seguir depende da sua configuração de approvals:
# Em que modo estamos? (smart é o padrão)
hermes config get approvals.mode
# Manual: todo comando perigoso pergunta para você
hermes config set approvals.mode manual
# Desligar as aprovações (yolo) — mas atenção: a deny list continua valendo
hermes config set approvals.mode off
Detalhes que pegam as pessoas:
approvals.modeaceitasmart,manualouoff; o padrão ésmart, em que um modelo revisor trata os casos ambíguos, enquanto acertos de regra determinística não precisam de modelo nenhum.approvals.denyé uma lista de bloqueio incondicional de globs fnmatch sem distinção de maiúsculas (por exemplorm -rf *). Ela continua valendo em yolo, entãoapprovals.mode: offnão significa “nada é bloqueado nunca”.- Negações repetidas acionam um breaker:
approvals.denial_breaker_thresholdé 3 por padrão, então você não fica preso em um loop infinito de negações. - Jobs agendados seguem
approvals.cron_mode(padrãodeny). Se um cron job realmente precisa ler instance metadata, mude essa chave explicitamente em vez de desligar as aprovações por inteiro. - Dentro de uma sessão, uma negação interativa pode ser registrada com
/denymais um motivo.
Para a engrenagem mais ampla de aprovação — modos, o modelo revisor em auxiliary.approval.model e como testar — veja o guia de configuração do Smart Approvals e dry runs de aprovação. Guardas irmãs no estilo “pergunte antes” cobrem edições de config SSH e desinstalações por gerenciador de pacotes.
Verifique na sua própria máquina
Se você tem uma VM na nuvem que é sua, dá para confirmar com segurança a forma desse risco (somente em máquinas que você possui e está autorizado a usar):
# 1) Uma leitura inofensiva: devolve fatos quase públicos da instância
curl -s --max-time 2 http://169.254.169.254/latest/meta-data/instance-id
# 2) AWS IMDSv2: pegue um token de uso único e leia o caminho de credenciais com ele
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 60")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
O comando 2 imprime o nome da role e credenciais temporárias, o que é precisamente por que ele merece uma confirmação humana. Nunca cole o que ele devolve em uma sessão, um ticket ou um chat — é autoridade de nível de conta em forma de texto.
Se você vir essa regra disparar em uma máquina que não é da nuvem (um notebook, um data center privado), a causa usual é um endereço hardcoded dentro de algum script. Se você realmente precisa disso — está construindo uma ferramenta de auto-checagem de nuvem — aprove uma vez e deixe o motivo visível; é mais seguro do que baixar as aprovações para tudo.
A conclusão
Em uma frase: 169.254.169.254 em uma VM na nuvem não é “um endereço interno qualquer” — é um dispensador de credenciais sem autenticação. Colocá-lo atrás de aprovação troca um pouco de atrito pelo caminho de roubo de credencial mais barato que existe.
Três coisas para lembrar na prática:
- Quando um agente de repente quer instance metadata, pare e leia o comando. Pode ser depuração legítima, ou pode ser uma instrução injetada.
- Jobs agendados negam esses comandos por padrão; se você realmente precisa deles, libere esse caso via
approvals.cron_modeem vez de desligar as aprovações por completo. approvals.denyé incondicional, eapprovals.mode: offnão significa “nada é bloqueado”.
Esta mudança foi mergeada depois da tag v0.21.2, então no momento em que escrevo ela vive na main e não está em um release tagueado. O hermes update pega a main, ou espere o próximo release.