IP serwera źródłowego za Cloudflare'em wycieka przez subdomenę deploy — trzy repo, czternaście sekund, naprawione za jednym zamachem
Tamtego popołudnia chwilę wpatrywałem się w GitHuba. Trzy repozytoria pozamykały PR-y o 16:20:31, 16:20:38 i 16:20:45 — w odstępie czternastu sekund.
smarts #38: deploy.smarts.md → deploy.smartshow2claude #13: deploy.how2claude.com → deploy.how2claudepickful #118: deploy.pickful.ai / deploy.pickful.xyz → deploy.pickful / deploy.pickful-alphaWszystkie trzy poprawki dotyczyły tej samej klasy buga. Pierwotnie rozmawiałem z Claude'em o nieistotnym drobiazgu deploya w how2claude. Mimochodem rzucił okiem na config/deploy.yml i powiedział mi, że IP serwera źródłowego tej maszyny stoi na widoku w publicznym DNS-ie.
Wszystkie moje projekty siedzą za Cloudflare'em. Mała pomarańczowa chmurka w panelu Cloudflare oznacza „rekord proxiowany" — żądania HTTP najpierw lądują na węźle edge Cloudflare, a stamtąd idą do mojego origin-a. IP origin nigdy nie pojawia się w odpowiedziach DNS; DNS zwraca anycast IP Cloudflare. Ta część była zrobiona dobrze.
Ale deployuję Kamalem. Kamal robi SSH na serwer, żeby uruchomić docker. SSH nie przejdzie przez HTTP-proxy, więc potrzebowałem hostname'u nieproxiowanego przez Cloudflare, żeby się przez SSH dostać. Moja konfiguracja wyglądała tak:
# config/deploy.yml
servers:
web:
- deploy.how2claude.com # ← rekord A wprost do IP origin, szara chmurka (DNS only)
W Cloudflare how2claude.com i www.how2claude.com były pomarańczowe. deploy.how2claude.com był szary. Musiał być szary, w przeciwnym razie SSH nie dotarłoby do maszyny.
Szary znaczy publiczny DNS. Każdy może wykonać:
$ dig deploy.how2claude.com +short
<moje IP origin>
A sama konwencja nazewnictwa deploy.<domena> jest już sama w sobie tropem — przeskanuj subdomenę deploy.* po liście popularnych domen SaaS i masz żniwo IP origin, które miały siedzieć schowane za Cloudflare'em.
Obejście Cloudflare WAF-a, obejście rate limitingu, obejście ochrony DDoS. W odległości jednego dig-a.
Rozmawialiśmy o czymś innym. Poprosiłem go, żeby przejrzał świeżo napisany dokument deploya; otworzył config/deploy.yml do porównania, doszedł do linii 7 — - deploy.how2claude.com — zatrzymał się i powiedział mniej więcej tak:
Ten hostname rozwiązuje się przez publiczny DNS, prawda? To znaczy, że każdy z zapytaniem DNS dostaje to IP origin, a edge proxy Cloudflare jest pomijany.
Zamarłem na sekundę. To powinienem był zauważyć — wystarczyło spojrzeć drugi raz na ikonkę szarej chmurki przy konfiguracji Cloudflare i zapytać siebie, co ona oznacza — ale nie zrobiłem tego. Traktowałem ten hostname jako coś „wewnętrznego", tylko dlatego, że prefiks brzmiał deploy.. Mój mózg po cichu nadał mu prywatność, której nigdy nie miał.
Claude tego uprzedzenia nie ma. Czyta string w polu yaml i zadaje mechaniczne pytanie: jak ten string staje się IP-kiem? Odpowiedź: publiczny DNS. Wniosek: IP origin jest publiczne.
/etc/hostsFix okazał się żenująco prosty. Kamal prosi lokalny klient SSH o rozwiązanie hostname'u, więc ten hostname musi rozwiązywać się tylko na mojej maszynie — nie w całym internecie.
Skróć hostname w yaml-u:
servers:
web:
- deploy.how2claude # ← uwaga: bez .com
Potem dodaj linię do mojego /etc/hosts:
198.51.100.42 deploy.how2claude
Runnery CI, które deployują, potrzebują tej samej linii (zmienna środowiskowa albo wprost echo >> /etc/hosts w workflow).
Wynik:
deploy.how2claude.*kamal deploy / app exec / app logs dalej działają, bo /etc/hosts jest sprawdzany przed DNS-emAkcesorium bazy danych wymaga tej samej zmiany:
accessories:
db:
image: postgres:17
host: deploy.how2claude # ← ten sam hostname
To jest część, którą chciałem zapisać.
Po wypchnięciu fixu how2claude już chciałem przełączać kontekst. Claude mnie zatrzymał: „W smarts i pickful też używasz Kamala, prawda? Sprawdźmy te dwa."
Sprawdził.
deploy.smarts.md — ten sam problem, publiczny rekord Adeploy.pickful.ai (production), deploy.pickful.xyz (alpha), plus dawno martwy deploy-test1.pickful.ai i deploy.staging.yml wskazujący na wycofaną domenę blockgeek.comPickful był najbardziej zagmatwany z całej trójki, bo miał kilka destinations (production / alpha / test2) i historyczny balast. Przy okazji Claude wymiótł martwe destinations staging i test1 — nie miały już racji bytu, czysty szum.
Końcowe commity:
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)
Czternaście sekund. Oczywiście to tylko GitHub opróżniający kolejkę merge'ów — prawdziwa robota była rozłożona na poprzednie dwie godziny. Ale efektywny kształt wyglądał tak: jedno znalezisko, trzy repozytoria załatwione.
Gdybym pracował sam, realistyczna wersja brzmi: poprawiam w how2claude, piszę TODO „zrób też smarts i pickful" i patrzę, jak to TODO leży na liście przez trzy miesiące. Tę dokładną sekwencję widziałem u siebie wielokrotnie.
Jeśli deployujesz Kamalem, Capistrano albo dowolnym narzędziem opartym o SSH:
config/deploy*.yml i wykonaj grep po host: i servers:. Wymień każdy hostname, który się pojawia.dig +short na każdym z nich. Cokolwiek zwraca IP origin zamiast pustki, wycieka.deploy.<project>) i włóż IP do /etc/hosts.echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.Jeśli twój zespół prowadzi kilka projektów ze wspólnym wzorcem infrastruktury, napisz jednolinijkowego grepa, który przejedzie wszystkie deploy*.yml we wszystkich repozytoriach, — to pewniejsze niż sprawdzanie pojedynczo.
Cloudflare nie zawiódł. Kamal nie zawiódł. Obydwa narzędzia robiły dokładnie to, co miały robić.
Lekcja jest taka: wartości domyślne wprowadzają cię w błąd po cichu. deploy.<twoja-domena>.com brzmi jak wewnętrzna nazwa, ale DNS nie zna pojęcia „wewnętrzny" — rekord A to rekord A, i z chwilą opublikowania jest publiczny. Nazwa może dać ci iluzję prywatności, a ta mała ikonka szarej chmurki w panelu Cloudflare to tylko wizualna postać tej samej iluzji: siedzi sobie cicho, bez czerwonych ostrzeżeń, ale w istocie mówi: „ten rekord nie idzie przeze mnie".
Pozwól Claude'owi raz przejrzeć twoją konfigurację deploya. On tej iluzji nie podziela.