Cloudflare の裏のオリジン IP が deploy サブドメインから漏れる——3 リポジトリを 14 秒で一括修正
その日の午後、しばらく GitHub を眺めていた。3 つのリポジトリで PR がそれぞれ 16:20:31、16:20:38、16:20:45 にクローズされていた——14 秒の間隔。
smarts #38:deploy.smarts.md → deploy.smartshow2claude #13:deploy.how2claude.com → deploy.how2claudepickful #118:deploy.pickful.ai / deploy.pickful.xyz → deploy.pickful / deploy.pickful-alpha3 つとも同じ種類のバグ修正だった。もともとは how2claude のリポジトリで関係ない小さなデプロイの不具合について Claude と話していただけ。それが config/deploy.yml をついでに眺めて、「このマシンのオリジン IP、公開 DNS で丸見えになってますよ」と教えてくれた。
私のプロジェクトはすべて Cloudflare の後ろにある。Cloudflare コンソールのオレンジ色の雲アイコンは「プロキシ経由」の意味で、HTTP リクエストはまず Cloudflare のエッジノードに着き、そこから私のオリジンに転送される。オリジン IP は DNS の応答には現れず、DNS が返すのは Cloudflare の anycast IP。ここまでは正しい。
しかし私は Kamal でデプロイしている。Kamal はサーバーに SSH して docker を動かす。SSH は HTTP プロキシを通せないので、Cloudflare にプロキシされていない ホスト名が SSH 用に必要だった。当時の私の設定はこうだった:
# config/deploy.yml
servers:
web:
- deploy.how2claude.com # ← オリジン IP に直接向く A レコード、グレー雲(DNS only)
Cloudflare コンソールでは how2claude.com と www.how2claude.com はオレンジ。deploy.how2claude.com はグレー。グレーである必要があった——そうでないと SSH が届かないからだ。
グレーは公開 DNS という意味だ。誰でもこれができる:
$ dig deploy.how2claude.com +short
<私のオリジン IP>
そして deploy.<domain> という命名規則自体が分かりやすい目印——よくある SaaS ドメインの deploy.* サブドメインを一括スキャンすれば、Cloudflare の後ろに隠れているはずだったオリジン IP がまとめて取れる。
Cloudflare の WAF を迂回、レート制限を迂回、DDoS 防御を迂回。dig 一発の話。
その日の話題は別件だった。書いたばかりのデプロイドキュメントをチェックしてもらおうと思って、Claude は照合のために config/deploy.yml を開き、7 行目の - deploy.how2claude.com まで読んで、一瞬止まってから、こんな感じのことを言った:
このホスト名は公開 DNS で解決されますよね?つまり誰でも DNS クエリでオリジン IP を取得できて、Cloudflare のエッジプロキシは迂回されることになります。
私はそのとき少し固まった。これは気付いていてよかった話で——Cloudflare を設定したときにグレー雲アイコンをもう一目見て、それが何を意味するか考えればよかった——でも、考えなかった。deploy. というプレフィックスが付いているという理由だけで、私はそのホスト名を「内部の何か」として扱っていた。私の頭が静かに、本当は存在しないプライバシーを与えていた。
Claude はそういうバイアスを共有しない。yaml フィールドの文字列を読んで、機械的に問う:この文字列はどうやって IP に解決されるのか?答えは公開 DNS。結論:オリジン IP は公開されている。
/etc/hosts のエイリアス修正は驚くほど簡単だった。Kamal はローカルの SSH クライアントにホスト名の解決を任せている。だからこのホスト名は 私のマシンで 解決できればよく——インターネット全体で解決される必要はない。
yaml のホスト名を短くする:
servers:
web:
- deploy.how2claude # ← 注意:.com がない
そして /etc/hosts に 1 行追加:
198.51.100.42 deploy.how2claude
CI の deploy job 用にも同じ行が必要(環境変数か、ワークフローで echo >> /etc/hosts)。
結果:
deploy.how2claude.* レコードがどこにも存在しないkamal deploy / app exec / app logs のすべてが従来どおり動く。/etc/hosts は DNS より先に参照されるからデータベースアクセサリも同じく変更:
accessories:
db:
image: postgres:17
host: deploy.how2claude # ← 同じホスト名
ここからが書きたかった部分だ。
how2claude の修正をシップして、私が次の作業に切り替えようとしたところで、Claude が止めた:「smarts と pickful も Kamal 使ってますよね?確認しに行きます」。
行ってきた。
deploy.smarts.md —— 同じ問題、A レコード公開deploy.pickful.ai(production)、deploy.pickful.xyz(alpha)、それに長らく死んでいた deploy-test1.pickful.ai と、過去の blockgeek.com ドメインを参照していた deploy.staging.ymlpickful は他の 2 つより少しややこしい。複数の destination(production / alpha / test2)と歴史的な遺物がある。Claude はついでに死んでいる staging と test1 destination も掃除してくれた——どうせ誰も使っていない、ただのノイズだった。
最終的なコミット:
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)
14 秒の間。もちろんこれは GitHub のマージキューが消化された時刻で、実際の作業は前 2 時間に分散していた——でも効果としては:1 つの発見、3 つのリポジトリで修正済み。
私 1 人でやっていたら、現実的にはこうなる:how2claude で修正して、TODO に「smarts と pickful も同じ修正」と書いて、その TODO がリストに 3 ヶ月座る。自分でその展開を何度も見てきた。
Kamal、Capistrano、SSH ベースのどんなデプロイツールでも:
config/deploy*.yml を開いて host: と servers: を grep。出てきたホスト名を全部リストアップ。dig +short を実行。空ではなくオリジン IP が返ってきたら、それは漏れている。deploy.<project>)にリネームして、IP を /etc/hosts に入れる。echo "$IP deploy.<project>" | sudo tee -a /etc/hosts。もし複数のプロジェクトで同じインフラパターンを共有しているなら、すべてのリポジトリの deploy*.yml を grep するワンライナーを書いた方が、1 つずつ確認するより信頼できる。
Cloudflare が悪いわけではない、Kamal の設計が悪いわけでもない——両方とも、するべきことを正確にしていた。
教訓は:デフォルト値は静かにあなたを騙す。deploy.<your-domain>.com は内部の名前のように聞こえるが、DNS には「内部」という概念がない。A レコードは A レコードで、公開された瞬間から公開されている。名前があなたにプライバシーの錯覚を与え、Cloudflare ダッシュボードのあの小さなグレー雲アイコンは、その錯覚の視覚バージョンに過ぎない——画面上では静かに、赤い警告もなく座っているが、実は「このレコードは私を通っていない」と告げている。
Claude にあなたのデプロイ設定を一度読ませてみてほしい。Claude はその錯覚を共有しない。