Free

Lascia che Claude cancelli il tuo IP di origine dal DNS pubblico

L'IP di origine dietro Cloudflare trapela dal sottodominio deploy — tre repo, quattordici secondi, sistemati in una passata


Quel pomeriggio fissai GitHub per un attimo. Tre repo avevano chiuso PR alle 16:20:31, 16:20:38 e 16:20:45 — quattordici secondi di distanza.

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

Tutti e tre sistemavano la stessa classe di bug. Stavo parlando con Claude di un problemino di deploy non correlato in how2claude. Di passaggio, ha dato un'occhiata a config/deploy.yml e mi ha detto che l'IP di origine di quella macchina era esposto in piena vista nel DNS pubblico.

Un'illusione che mi aveva ingannato per mesi

Tutti i miei progetti stanno dietro Cloudflare. La nuvoletta arancione nella dashboard di Cloudflare significa "record proxato" — le richieste HTTP atterrano prima su un nodo edge di Cloudflare, poi vanno al mio origin. L'IP di origine non appare mai nelle risposte DNS; quello che il DNS restituisce è un IP anycast di Cloudflare. Quella parte era a posto.

Ma faccio deploy con Kamal. Kamal fa SSH al server per girare docker. SSH non può passare attraverso un proxy HTTP, quindi mi serviva un hostname non proxato da Cloudflare per fare SSH. La mia configurazione era:

# config/deploy.yml
servers:
  web:
    - deploy.how2claude.com   # ← record A diretto all'IP di origine, nuvola grigia (DNS only)

Su Cloudflare, how2claude.com e www.how2claude.com erano arancioni. deploy.how2claude.com era grigio. Doveva essere grigio, altrimenti SSH non poteva raggiungere la macchina.

Grigio significa DNS pubblico. Chiunque può eseguire:

$ dig deploy.how2claude.com +short
<il mio IP di origine>

E la convenzione di nomi deploy.<dominio> è di per sé un segnale chiaro — scansiona la sottosezione deploy.* su una lista di domini SaaS comuni e raccogli un mucchio di IP di origine che dovrebbero stare nascosti dietro Cloudflare.

WAF di Cloudflare bypassato, rate limiting bypassato, protezione DDoS bypassata. A un dig di distanza.

Perché Claude se ne è accorto

Stavamo parlando d'altro. Gli avevo chiesto di rivedere un documento di deploy che avevo appena scritto; ha aperto config/deploy.yml per fare il confronto, è arrivato alla riga 7 — - deploy.how2claude.com — ha fatto una pausa e ha detto più o meno questo:

Quel hostname si risolve via DNS pubblico, no? Significa che chiunque faccia una query DNS prende quell'IP di origine, e il proxy edge di Cloudflare viene aggirato.

Mi sono bloccato per un attimo. Questo avrei dovuto vederlo — sarebbe bastato guardare due volte l'icona della nuvola grigia mentre configuravo Cloudflare e chiedermi cosa significasse — ma non l'ho fatto. Trattavo quel hostname come qualcosa di "interno", solo perché il prefisso era deploy.. Il mio cervello gli concedeva in silenzio una privacy che non aveva mai avuto.

Claude non condivide quel bias. Legge una stringa in un campo yaml e fa la domanda meccanica: come si trasforma questa stringa in un IP? Risposta: DNS pubblico. Conclusione: l'IP di origine è pubblico.

La fix: un alias in /etc/hosts

La fix si è rivelata imbarazzantemente semplice. Kamal chiede al client SSH locale di risolvere l'hostname, quindi l'hostname deve risolversi solo sulla mia macchina — non su tutta internet.

Accorcia l'hostname nello yaml:

servers:
  web:
    - deploy.how2claude    # ← attenzione: senza .com

Poi aggiungi una riga al mio /etc/hosts:

198.51.100.42  deploy.how2claude

I runner di CI che fanno deploy hanno bisogno della stessa riga (env var, o direttamente echo >> /etc/hosts nel workflow).

Risultato:

  • Nessun record deploy.how2claude.* esiste nel DNS pubblico, da nessuna parte
  • Gli attaccanti non possono ricavare l'IP via DNS — almeno quella strada è chiusa
  • Tutti i kamal deploy / app exec / app logs continuano a funzionare, perché /etc/hosts viene consultato prima del DNS
  • Cloudflare diventa più pulito: quella sottosezione imbarazzante con la nuvola grigia sparisce e basta

L'accessory del database vuole lo stesso cambio:

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

Claude ha portato questa cosa su tutti e tre i repo

Questa è la parte che volevo mettere giù.

Una volta spedita la fix di how2claude, stavo per cambiare contesto. Claude mi ha fermato: "Usi Kamal anche su smarts e pickful, vero? Fammi controllare quei due."

L'ha fatto.

  • smarts: deploy.smarts.md — stesso problema, record A pubblico
  • pickful: deploy.pickful.ai (production), deploy.pickful.xyz (alpha), più un deploy-test1.pickful.ai morto da tempo e un deploy.staging.yml che puntava a un dominio in pensione, blockgeek.com

Pickful era il più ingarbugliato dei tre perché aveva più destinations (production / alpha / test2) e zavorra storica. Di passaggio Claude ha ripulito le destinations morte staging e test1 — non avevano più ragione di esistere, solo rumore.

Commit finali:

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)

Quattordici secondi. Certo, è solo GitHub che svuota una coda di merge — il lavoro vero era distribuito nelle due ore precedenti. Ma la forma effettiva era: una scoperta, tre repo sistemati.

Se avessi lavorato da solo, la versione realistica sarebbe stata: aggiusto in how2claude, scrivo un TODO "fare anche smarts e pickful", e guardo quel TODO restare nella lista per tre mesi. Ho vissuto esattamente quella sequenza molte volte.

Una checklist da copiare

Se fai deploy con Kamal, Capistrano o qualunque tool di deploy basato su SSH:

  1. Apri config/deploy*.yml e fa' il grep di host: e servers:. Elenca ogni hostname che compare.
  2. Esegui dig +short su ognuno. Tutto ciò che restituisce un IP di origine invece di niente sta perdendo.
  3. Passa per la dashboard del tuo CDN (Cloudflare, ecc.) e cerca le sottosezioni con la "nuvola grigia" — è il set candidato.
  4. Rinominale in un alias senza TLD (deploy.<project>) e metti l'IP in /etc/hosts.
  5. Aggiorna anche il job di deploy in CI. Su GitHub Actions: echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.
  6. Cancella il record A pubblico originale.

Se il tuo team gestisce più progetti che condividono lo stesso pattern infrastrutturale, scrivi un grep di una riga che spazzoli tutti i deploy*.yml di tutti i repo, è più affidabile di controllarli uno alla volta.

La vera lezione

Cloudflare non ha fallito. Kamal non ha fallito. Entrambi gli strumenti facevano esattamente quello che dovevano fare.

La lezione è: i valori di default ti ingannano in silenzio. deploy.<tuo-dominio>.com suona come un nome interno, ma il DNS non ha il concetto di "interno" — un record A è un record A, e una volta pubblicato è pubblico. Un nome può darti l'illusione della privacy, e quell'iconcina di nuvola grigia nella dashboard di Cloudflare è solo la versione visiva della stessa illusione: se ne sta lì in silenzio, senza alert in rosso, ma in realtà ti sta dicendo "questo record non passa per me".

Fai leggere a Claude la tua config di deploy una volta. Non condivide l'illusione.