Free

Deixe o Claude apagar o IP de origem do DNS público

O IP de origem atrás do Cloudflare vaza pelo subdomínio deploy — três repos, catorze segundos, tudo consertado de uma vez


Naquela tarde fiquei olhando o GitHub por um tempo. Três repos fecharam PRs às 16:20:31, 16:20:38 e 16:20:45 — catorze segundos de diferença.

  • smarts #38: deploy.smarts.mddeploy.smarts
  • how2claude #13: deploy.how2claude.comdeploy.how2claude
  • pickful #118: deploy.pickful.ai / deploy.pickful.xyzdeploy.pickful / deploy.pickful-alpha

Os três fixes eram da mesma classe de bug. Eu estava conversando com o Claude sobre um detalhe de deploy não relacionado em how2claude. Ele deu uma olhada de passagem em config/deploy.yml e me disse que o IP de origem daquela máquina estava à mostra no DNS público.

Uma ilusão que tinha me enganado por meses

Todos os meus projetos estão atrás do Cloudflare. Aquela nuvenzinha laranja no painel do Cloudflare significa "registro proxiado" — as requisições HTTP chegam primeiro num nó edge do Cloudflare, e dali para a minha origem. O IP de origem nunca aparece nas respostas DNS; o que o DNS retorna é um IP anycast do Cloudflare. Essa parte estava certa.

Mas eu deploy com Kamal. Kamal faz SSH no servidor para rodar docker. SSH não passa por proxy HTTP, então eu precisava de um hostname não proxiado pelo Cloudflare para fazer SSH. Minha config era:

# config/deploy.yml
servers:
  web:
    - deploy.how2claude.com   # ← registro A direto pro IP de origem, nuvem cinza (DNS only)

No Cloudflare, how2claude.com e www.how2claude.com estavam em laranja. deploy.how2claude.com estava em cinza. Tinha que estar em cinza, senão o SSH não chegava na máquina.

Cinza significa DNS público. Qualquer um pode rodar:

$ dig deploy.how2claude.com +short
<meu IP de origem>

E a convenção de nomes deploy.<domínio> por si só já é uma pista — varra o subdomínio deploy.* numa lista de domínios SaaS comuns e você tem uma colheita de IPs de origem que deveriam estar escondidos atrás do Cloudflare.

Pula o WAF do Cloudflare, pula rate limiting, pula proteção DDoS. Tudo a um dig de distância.

Por que o Claude reparou

A gente estava falando de outra coisa. Pedi pra ele revisar um doc de deploy que eu tinha acabado de escrever; ele abriu config/deploy.yml pra cruzar referências, chegou na linha 7 — - deploy.how2claude.com — pausou, e disse mais ou menos isso:

Esse hostname resolve por DNS público, certo? Ou seja, qualquer um com uma query DNS consegue esse IP de origem, e o proxy edge do Cloudflare é contornado.

Travei por um segundo. Eu deveria ter visto isso — bastava ter olhado duas vezes pro ícone de nuvem cinza ao configurar o Cloudflare e perguntado o que ele significava — mas não vi. Eu tratava aquele hostname como algo "interno", apenas porque o prefixo era deploy.. Meu cérebro silenciosamente concedeu a ele uma privacidade que nunca existiu.

O Claude não compartilha esse viés. Ele lê uma string num campo yaml e faz a pergunta mecânica: como essa string vira um IP? Resposta: DNS público. Conclusão: o IP de origem é público.

O fix: um alias no /etc/hosts

O fix acabou sendo embaraçosamente simples. O Kamal pede ao cliente SSH local pra resolver o hostname, então o hostname só precisa resolver na minha máquina — não na internet inteira.

Encurta o hostname no yaml:

servers:
  web:
    - deploy.how2claude    # ← repare: sem .com

Depois adiciona uma linha no meu /etc/hosts:

198.51.100.42  deploy.how2claude

Os runners de CI que fazem deploy precisam da mesma linha (variável de ambiente, ou direto echo >> /etc/hosts no workflow).

Resultado:

  • Nenhum registro deploy.how2claude.* existe no DNS público em lugar nenhum
  • Atacantes não conseguem o IP via DNS — pelo menos esse caminho fechou
  • Todo kamal deploy / app exec / app logs continua funcionando, porque /etc/hosts é consultado antes do DNS
  • O Cloudflare fica mais limpo: aquele subdomínio constrangedor de nuvem cinza simplesmente some

O accessory do banco precisa da mesma mudança:

accessories:
  db:
    image: postgres:17
    host: deploy.how2claude   # ← mesmo hostname

O Claude carregou isso pros três repos

Essa é a parte que eu queria registrar.

Quando terminamos o fix do how2claude, eu ia trocar de contexto. O Claude me parou: "Você também usa Kamal em smarts e pickful, né? Deixa eu conferir aqueles."

Conferiu.

  • smarts: deploy.smarts.md — mesmo problema, registro A público
  • pickful: deploy.pickful.ai (production), deploy.pickful.xyz (alpha), mais um deploy-test1.pickful.ai morto há muito tempo e um deploy.staging.yml apontando pra um domínio aposentado, blockgeek.com

Pickful era o mais bagunçado dos três porque tinha múltiplos destinations (production / alpha / test2) e bagagem histórica. O Claude varreu de passagem os destinations mortos de staging e test1 — não tinham mais razão de existir, eram só ruído.

Commits finais:

smarts       6482472   2026-04-27 16:20:31  Use private deploy.smarts alias instead of public deploy.smarts.md (#38)
how2claude   ccc0344   2026-04-27 16:20:38  Use private deploy.how2claude alias instead of public deploy.how2claude.com (#13)
pickful      e6bf9af   2026-04-27 16:20:45  Deploy config hygiene + privatize hostnames (#118)

Catorze segundos. Claro, isso é só o GitHub esvaziando uma fila de merge — o trabalho real ficou espalhado nas duas horas anteriores. Mas a forma efetiva foi: um achado, três repos consertados.

Se eu tivesse trabalhado sozinho, a versão realista é: conserto em how2claude, escrevo um TODO com "fazer smarts e pickful também", e vejo aquele TODO deitado na lista por três meses. Vivi exatamente essa sequência várias vezes.

Um checklist pra copiar

Se você deploya com Kamal, Capistrano, ou qualquer ferramenta baseada em SSH:

  1. Abra config/deploy*.yml e dê grep em host: e servers:. Liste todos os hostnames que aparecem.
  2. Rode dig +short em cada um. Qualquer um que retornar um IP de origem em vez de nada está vazando.
  3. Passe pelo painel do seu CDN e procure subdomínios de "nuvem cinza" — esse é o conjunto candidato.
  4. Renomeie pra um alias sem TLD (deploy.<project>), e coloque o IP no /etc/hosts.
  5. Atualize também o job de deploy do CI. No GitHub Actions: echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.
  6. Apague o registro A público original.

Se seu time gerencia vários projetos com o mesmo padrão de infra, escreva um grep one-liner que varra todo deploy*.yml em todos os repos, em vez de checar um por um.

A lição de verdade

O Cloudflare não falhou. O Kamal não falhou. As duas ferramentas estavam fazendo exatamente o que tinham que fazer.

A lição é: valores padrão te enganam em silêncio. deploy.<seu-domínio>.com soa como um nome interno, mas o DNS não tem o conceito de "interno" — um registro A é um registro A, e uma vez publicado, ele é público. Um nome pode te dar a ilusão de privacidade, e aquele iconezinho de nuvem cinza no Cloudflare é só a versão visual da mesma ilusão: senta lá quietinho, sem aviso vermelho, mas o que ele tá te dizendo é "esse registro não passa por mim".

Deixe o Claude ler sua config de deploy uma vez. Ele não compartilha a ilusão.