Free

Deja que Claude borre tu IP de origen del DNS público

La IP de origen detrás de Cloudflare se filtra por el subdominio deploy — tres repos, catorce segundos, todos arreglados de una


Esa tarde me quedé mirando GitHub un rato. Tres repos cerraron PRs a las 16:20:31, 16:20:38 y 16:20:45 — catorce segundos de diferencia.

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

Los tres arreglos eran de la misma clase de bug. Yo había estado hablando con Claude sobre un detalle de despliegue no relacionado en how2claude. De pasada, miró el config/deploy.yml y me dijo que la IP de origen de esa máquina estaba a la vista en el DNS público.

Una ilusión que me había engañado durante meses

Todos mis proyectos están detrás de Cloudflare. La nubecita naranja en el dashboard de Cloudflare significa "registro proxificado" — las peticiones HTTP llegan primero a un nodo edge de Cloudflare y de ahí a mi origen. La IP de origen no aparece en las respuestas DNS; lo que el DNS devuelve es una IP anycast de Cloudflare. Esa parte estaba bien.

Pero despliego con Kamal. Kamal hace SSH al servidor para correr docker. SSH no puede pasar por un proxy HTTP, así que necesitaba un hostname no proxificado por Cloudflare al que conectarme. Mi configuración era:

# config/deploy.yml
servers:
  web:
    - deploy.how2claude.com   # ← registro A directo a la IP de origen, nube gris (DNS only)

En Cloudflare, how2claude.com y www.how2claude.com estaban en naranja. deploy.how2claude.com estaba en gris. Tenía que estar en gris, si no, SSH no podía llegar al servidor.

Gris significa DNS público. Cualquiera puede correr:

$ dig deploy.how2claude.com +short
<mi IP de origen>

Y la convención de nombres deploy.<dominio> es en sí misma una pista — barre el subdominio deploy.* sobre una lista de dominios SaaS comunes y obtienes una cosecha de IPs de origen que se suponía estaban escondidas detrás de Cloudflare.

Te saltas el WAF de Cloudflare, te saltas el rate limiting, te saltas la protección DDoS. A un dig de distancia.

Por qué Claude lo notó

Estábamos hablando de otra cosa. Le pedí que revisara un documento de despliegue que acababa de escribir. Abrió config/deploy.yml para contrastar, llegó a la línea 7 — - deploy.how2claude.com — hizo una pausa, y dijo más o menos esto:

Ese hostname se resuelve por DNS público, ¿verdad? Eso significa que cualquiera con una consulta DNS obtiene esa IP de origen, y el proxy edge de Cloudflare queda evitado.

Me quedé un segundo. Esto debería haberlo visto antes — habría bastado con mirar dos veces el icono de nube gris al configurar Cloudflare y preguntarme qué significaba — pero no lo hice. Trataba ese hostname como algo "interno", solo porque el prefijo era deploy.. Mi cerebro le concedía calladamente una privacidad que nunca tuvo.

Claude no comparte ese sesgo. Lee un string en un campo yaml y hace la pregunta mecánica: ¿cómo se convierte este string en una IP? Respuesta: DNS público. Conclusión: la IP de origen es pública.

El arreglo: un alias en /etc/hosts

El arreglo resultó ser ridículamente simple. Kamal le pide al cliente SSH local que resuelva el hostname, así que el hostname solo necesita resolverse en mi máquina — no en todo internet.

Acortamos el hostname en yaml:

servers:
  web:
    - deploy.how2claude    # ← ojo: sin .com

Y añadimos una línea en mi /etc/hosts:

198.51.100.42  deploy.how2claude

Los runners de CI que despliegan necesitan la misma línea (una variable de entorno, o directamente echo >> /etc/hosts en el workflow).

Resultado:

  • Ningún registro deploy.how2claude.* existe en DNS público en ningún sitio
  • Los atacantes no pueden sacar la IP por DNS — al menos esa vía queda cerrada
  • Todos los kamal deploy / app exec / app logs siguen funcionando, porque /etc/hosts se consulta antes que DNS
  • Cloudflare se ve más limpio: ese subdominio incómodo de nube gris desaparece

El accessory de la base de datos necesita el mismo cambio:

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

Claude llevó esto a los tres repos

Esta es la parte que quería escribir.

Cuando terminamos el arreglo de how2claude, yo iba a cambiar de contexto. Claude me detuvo: "También usas Kamal en smarts y pickful, ¿no? Déjame revisar esos."

Lo hizo.

  • smarts: deploy.smarts.md — mismo problema, registro A público
  • pickful: deploy.pickful.ai (production), deploy.pickful.xyz (alpha), más un deploy-test1.pickful.ai muerto hace tiempo y un deploy.staging.yml que apuntaba a un dominio retirado, blockgeek.com

Pickful era el más enredado de los tres porque tenía varias destinations (production / alpha / test2) y peso histórico. Claude limpió de paso los destinations muertos de staging y test1 — ya no tenían razón de existir, eran solo ruido.

Commits finales:

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)

Catorce segundos. Claro, eso es solo GitHub vaciando una cola de merge — el trabajo real estuvo repartido en las dos horas anteriores. Pero la forma efectiva fue: un hallazgo, tres repos arreglados.

Si lo hubiera hecho yo solo, la versión realista es: arreglarlo en how2claude, anotar un TODO con "hacer smarts y pickful también", y ver ese TODO acostado en la lista durante tres meses. He vivido exactamente esa secuencia muchas veces.

Una checklist que puedes copiar

Si despliegas con Kamal, Capistrano, o cualquier herramienta basada en SSH:

  1. Abre config/deploy*.yml y hace grep de host: y servers:. Lista todos los hostnames que aparezcan.
  2. Corre dig +short sobre cada uno. Cualquiera que devuelva una IP de origen en lugar de nada está filtrando.
  3. Recorre el dashboard de tu CDN y busca subdominios de "nube gris" — ese es el conjunto candidato.
  4. Renómbralos a un alias sin TLD (deploy.<project>) y mete la IP en /etc/hosts.
  5. Actualiza también el job de deploy en CI. En GitHub Actions: echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.
  6. Borra el registro A público original.

Si tu equipo gestiona varios proyectos que comparten el mismo patrón de infraestructura, escribe un grep de una línea sobre los deploy*.yml de todos los repos en lugar de revisarlos uno a uno.

La lección de verdad

No falló Cloudflare. No falló Kamal. Las dos herramientas hacían exactamente lo que tenían que hacer.

La lección es: los valores por defecto te engañan en silencio. deploy.<tu-dominio>.com suena a nombre interno, pero el DNS no tiene el concepto de "interno" — un registro A es un registro A, y una vez publicado, es público. Un nombre puede darte la ilusión de privacidad, y ese pequeño icono de nube gris en Cloudflare es solo la forma visual de la misma ilusión: ahí sentado tranquilo, sin avisos en rojo, pero lo que en realidad te dice es "este registro no pasa por mí".

Deja que Claude lea tu config de deploy una vez. No comparte la ilusión.