Free

Biarkan Claude menghapus IP origin dari DNS publik

IP origin di balik Cloudflare bocor lewat subdomain deploy — tiga repo, empat belas detik, beres sekaligus


Sore itu saya menatap GitHub sebentar. Tiga repo menutup PR pada 16:20:31, 16:20:38, dan 16:20:45 — selisih empat belas detik.

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

Ketiganya memperbaiki bug kelas yang sama. Awalnya saya hanya ngobrol dengan Claude di repo how2claude tentang masalah deploy kecil yang tidak terkait. Sambil lalu, ia menengok config/deploy.yml dan memberi tahu saya bahwa IP origin dari mesin itu terbuka lebar di DNS publik.

Ilusi yang menipu saya selama berbulan-bulan

Semua proyek saya berada di belakang Cloudflare. Ikon awan oranye kecil di dashboard Cloudflare berarti "record di-proxy" — request HTTP mendarat di edge node Cloudflare dulu, lalu ke origin saya. IP origin tidak pernah muncul di respon DNS; yang dikembalikan DNS adalah IP anycast Cloudflare. Bagian itu sudah benar.

Tapi saya deploy dengan Kamal. Kamal melakukan SSH ke server untuk menjalankan docker. SSH tidak bisa lewat HTTP proxy, jadi saya butuh hostname yang tidak di-proxy Cloudflare untuk SSH. Setup saya saat itu:

# config/deploy.yml
servers:
  web:
    - deploy.how2claude.com   # ← record A langsung ke IP origin, awan abu (DNS only)

Di Cloudflare, how2claude.com dan www.how2claude.com warnanya oranye. deploy.how2claude.com warnanya abu. Harus abu, kalau tidak SSH tidak bisa sampai ke mesin.

Abu artinya DNS publik. Siapa pun bisa menjalankan:

$ dig deploy.how2claude.com +short
<IP origin saya>

Dan konvensi penamaan deploy.<domain> itu sendiri sudah jadi penanda yang kentara — scan subdomain deploy.* di daftar domain SaaS umum, dan kamu dapat panen IP origin yang seharusnya bersembunyi di balik Cloudflare.

Lewati WAF Cloudflare, lewati rate limiting, lewati proteksi DDoS. Sejauh satu dig.

Kenapa Claude menyadarinya

Topik kami sebenarnya hal lain. Saya minta dia mereview dokumen deploy yang baru saya tulis; ia membuka config/deploy.yml untuk cross-check, sampai ke baris 7 — - deploy.how2claude.com — berhenti sebentar, lalu mengatakan kira-kira begini:

Hostname itu diresolve lewat DNS publik kan? Berarti siapa pun yang melakukan query DNS bisa dapat IP origin itu, dan edge proxy Cloudflare jadi terlewat.

Saya terdiam sebentar. Ini seharusnya sudah saya lihat — saya punya cukup alasan untuk perhatikan ikon awan abu saat mengonfigurasi Cloudflare dan menanyakan apa artinya — tapi saya tidak. Saya memperlakukan hostname itu seperti sesuatu yang "internal", semata-mata karena prefiksnya deploy.. Otak saya diam-diam memberinya privasi yang sebenarnya tidak pernah ada.

Claude tidak punya bias itu. Ia membaca string di field yaml dan bertanya secara mekanis: bagaimana string ini berubah menjadi IP? Jawaban: DNS publik. Kesimpulan: IP origin bersifat publik.

Perbaikannya: alias di /etc/hosts

Perbaikannya ternyata memalukan saking sederhananya. Kamal meminta SSH client lokal untuk meresolve hostname, jadi hostname itu hanya perlu resolve di mesin saya — tidak perlu resolve di seluruh internet.

Pendekkan hostname di yaml:

servers:
  web:
    - deploy.how2claude    # ← perhatikan: tidak ada .com

Lalu tambahkan satu baris ke /etc/hosts saya:

198.51.100.42  deploy.how2claude

Runner CI yang melakukan deploy butuh baris yang sama (env var, atau langsung echo >> /etc/hosts di workflow).

Hasil:

  • Tidak ada record deploy.how2claude.* di DNS publik di mana pun
  • Penyerang tidak bisa mengambil IP via DNS — setidaknya jalur itu tertutup
  • Semua kamal deploy / app exec / app logs tetap jalan, karena /etc/hosts dicek sebelum DNS
  • Cloudflare jadi lebih bersih: subdomain awan abu yang canggung itu lenyap

Accessory database juga butuh perubahan yang sama:

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

Claude membawa ini ke ketiga repo

Ini bagian yang ingin saya tulis.

Setelah perbaikan how2claude di-ship, saya akan beralih ke konteks lain. Claude menahan saya: "Kamu juga pakai Kamal di smarts dan pickful, kan? Coba saya cek dua itu."

Dia cek.

  • smarts: deploy.smarts.md — masalah yang sama, record A publik
  • pickful: deploy.pickful.ai (production), deploy.pickful.xyz (alpha), plus deploy-test1.pickful.ai yang sudah lama mati dan satu deploy.staging.yml yang merujuk ke domain pensiun, blockgeek.com

Pickful paling berantakan dari ketiganya karena punya beberapa destination (production / alpha / test2) dan beban historis. Claude sekalian menyapu destination staging dan test1 yang mati — sudah tidak ada yang pakai, hanya jadi noise.

Commit akhir:

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)

Empat belas detik. Tentu itu cuma GitHub mengosongkan antrean merge — kerja sebenarnya tersebar di dua jam sebelumnya. Tapi bentuk efektifnya: satu temuan, tiga repo terselesaikan.

Kalau saya kerjakan sendiri, versi realistisnya: perbaiki di how2claude, tulis TODO bertuliskan "lakukan smarts dan pickful juga", dan saksikan TODO itu tergeletak di list selama tiga bulan. Saya sudah mengalami urutan persis itu berkali-kali.

Checklist yang bisa kamu copy

Kalau kamu deploy dengan Kamal, Capistrano, atau alat deploy berbasis SSH apa pun:

  1. Buka config/deploy*.yml dan grep host: dan servers:. Daftarkan setiap hostname yang muncul.
  2. Jalankan dig +short di tiap-tiap hostname. Yang mengembalikan IP origin alih-alih kosong, itu bocor.
  3. Buka dashboard CDN (Cloudflare dll.) dan lihat subdomain "awan abu" — itu kandidatnya.
  4. Rename ke alias tanpa TLD (deploy.<project>), masukkan IP-nya ke /etc/hosts.
  5. Update juga deploy job di CI. Di GitHub Actions: echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.
  6. Hapus record A publik aslinya.

Kalau tim kamu punya beberapa proyek yang berbagi pola infra yang sama, tulis grep one-liner yang menyapu semua deploy*.yml di semua repo, lebih bisa diandalkan daripada cek satu per satu.

Pelajaran sebenarnya

Cloudflare tidak gagal. Kamal tidak gagal. Kedua tool melakukan persis yang seharusnya mereka lakukan.

Pelajarannya: default value diam-diam menyesatkan kamu. deploy.<domainmu>.com terdengar seperti nama internal, tapi DNS tidak punya konsep "internal" — record A adalah record A, dan begitu dipublikasikan, ia publik. Sebuah nama bisa memberi ilusi privasi, dan ikon awan abu kecil di dashboard Cloudflare itu hanyalah versi visual dari ilusi yang sama: duduk di sana diam-diam, tanpa peringatan merah, tapi yang sebenarnya ia katakan adalah "record ini tidak lewat saya".

Coba minta Claude membaca config deploy kamu sekali. Dia tidak punya ilusi itu.