Free

دع Claude يمحو IP المصدر من DNS العام

IP المصدر خلف Cloudflare يتسرّب عبر النطاق الفرعي deploy — ثلاثة مستودعات في أربع عشرة ثانية، أُصلحت دفعةً واحدة


في تلك الظهيرة حدّقت في GitHub قليلاً. أغلقت ثلاثة مستودعات طلبات الدمج عند 16:20:31 و16:20:38 و16:20:45 — بفارق أربع عشرة ثانية.

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

كانت الإصلاحات الثلاثة من النوع نفسه. كنت أتحدث مع Claude عن خلل صغير غير ذي صلة في النشر داخل مستودع how2claude. ألقى نظرة عابرة على config/deploy.yml فقال لي إنّ IP خادم المصدر لتلك الآلة ظاهر أمام الجميع في DNS العام.

وهم خدعني لأشهر

كل مشاريعي خلف Cloudflare. السحابة البرتقالية الصغيرة في لوحة Cloudflare تعني أنّ السجلّ مُمَرَّر عبر الوكيل — يصل طلب HTTP أولاً إلى عقدة edge في Cloudflare ثم إلى المصدر عندي. لا يظهر IP المصدر في ردود DNS؛ ما تُعيده DNS هو IP من نوع anycast تابع لـCloudflare. هذا الجزء كان سليماً.

لكنّي أنشر باستخدام Kamal. يقوم Kamal بـSSH إلى الخادم ليُشغّل docker. لا يستطيع SSH العبور خلال HTTP proxy، لذا احتجت إلى hostname غير مُمَرَّر عبر Cloudflare لأقوم بـSSH عبره. كان إعدادي حينها:

# config/deploy.yml
servers:
  web:
    - deploy.how2claude.com   # ← سجلّ A مباشر إلى IP المصدر، سحابة رمادية (DNS only)

في Cloudflare كان how2claude.com وwww.how2claude.com برتقاليَّين. أما deploy.how2claude.com فكان رمادياً. يجب أن يكون رمادياً، وإلا لما وصل SSH إلى الجهاز.

الرمادي يعني DNS عاماً. يستطيع أيّ شخص أن يُشغّل:

$ dig deploy.how2claude.com +short
<IP المصدر عندي>

ثم إنّ نمط التسمية deploy.<domain> بحد ذاته علامة فارقة — امسح النطاقات الفرعية deploy.* على قائمة من نطاقات SaaS الشائعة، وسيكون لديك حصاد من IP خوادم المصدر التي كان يُفترض أن تختبئ خلف Cloudflare.

تجاوزٌ لـWAF، تجاوزٌ لتحديد المعدل، تجاوزٌ لحماية DDoS. على بُعد أمر dig واحد.

لماذا انتبه Claude

كنّا نتحدث في موضوع آخر. طلبت منه أن يُراجع وثيقة نشر كتبتها للتو؛ فتح config/deploy.yml للمقارنة، ووصل إلى السطر السابع — - deploy.how2claude.com — توقّف لحظة، ثم قال شيئاً قريباً من هذا:

هذا الـhostname يُحلّ عبر DNS العام، أليس كذلك؟ يعني أنّ أيّ مَن يُشغّل استعلام DNS يحصل على IP المصدر، وبالتالي يُتجاوَز وكيل edge الخاص بـCloudflare.

تجمّدتُ ثانيةً. هذا الأمر كان عليّ أن أنتبه إليه — كان يكفي أن أنظر مرّةً ثانية إلى أيقونة السحابة الرمادية أثناء إعداد Cloudflare وأسأل نفسي ما الذي تعنيه — لكنّي لم أفعل. كنت أتعامل مع ذلك الـhostname كأنّه شيء "داخلي"، لمجرّد أنّ بادئته deploy.. منحَه دماغي بهدوء خصوصيةً لم تكن له يوماً.

Claude لا يحمل هذا الانحياز. يقرأ سلسلة نصية في حقل yaml ويطرح السؤال الميكانيكي: كيف تتحوّل هذه السلسلة إلى IP؟ الجواب: DNS عام. الاستنتاج: IP المصدر عام.

الإصلاح: alias في /etc/hosts

تبيّن أنّ الإصلاح بسيط لدرجة محرجة. يطلب Kamal من عميل SSH المحلي أن يحلّ الـhostname، أي يكفي أن يُحَلّ هذا الـhostname على جهازي وحده — لا حاجة لأن يُحَلّ في الإنترنت كلّه.

اقصُر الـhostname في الـyaml:

servers:
  web:
    - deploy.how2claude    # ← انتبه: من دون .com

ثم أضِف سطراً إلى /etc/hosts عندي:

198.51.100.42  deploy.how2claude

تحتاج عقد تشغيل CI التي تنفّذ النشر إلى السطر نفسه (متغيّر بيئة، أو مباشرةً echo >> /etc/hosts في الـworkflow).

النتيجة:

  • لا يوجد سجلّ deploy.how2claude.* في DNS العام في أيّ مكان
  • لا يستطيع المهاجمون استخراج الـIP عبر DNS — وعلى الأقل أُغلق هذا المسار
  • تستمرّ أوامر kamal deploy / app exec / app logs بالعمل، لأنّ /etc/hosts يُستشار قبل DNS
  • يصبح Cloudflare أنظف: يختفي ذلك النطاق الفرعي المُحرج ذو السحابة الرمادية

ملحق قاعدة البيانات يحتاج إلى التغيير نفسه:

accessories:
  db:
    image: postgres:17
    host: deploy.how2claude   # ← الـhostname نفسه

نقل Claude هذا الأمر إلى المستودعات الثلاثة

هذا هو الجزء الذي أردتُ تدوينه.

بعد أن أنجزنا إصلاح how2claude، كنت سأنتقل إلى سياق آخر. أوقفني Claude: «أنت تستخدم Kamal أيضاً في smarts وpickful، أليس كذلك؟ دعني أتفقّدهما».

تفقّدهما.

  • smarts: deploy.smarts.md — المشكلة نفسها، سجلّ A عام
  • pickful: deploy.pickful.ai (production)، وdeploy.pickful.xyz (alpha)، فضلاً عن deploy-test1.pickful.ai ميت منذ زمن، وdeploy.staging.yml يُشير إلى نطاق متقاعد، blockgeek.com

كان pickful الأكثر تشابكاً بين الثلاثة لأنّ فيه عدّة destinations (production / alpha / test2) وحملاً تاريخياً. كَنَس Claude بالمناسبة destinations staging وtest1 الميتة — لم يكن لها سبب للوجود، مجرّد ضوضاء.

الـcommits النهائية:

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)

أربع عشرة ثانية. صحيح أنّ هذا فقط وقت إفراغ GitHub لقائمة الدمج — أمّا العمل الفعلي فقد توزّع على الساعتين السابقتين. لكن الشكل العملي كان: اكتشاف واحد، وإصلاح في ثلاثة مستودعات.

لو كنت أعمل وحدي، فالنسخة الواقعية ستكون: أصلحه في how2claude، أكتب TODO يقول "افعل الشيء نفسه في smarts وpickful"، ثم أُشاهد ذلك الـTODO راقداً في القائمة ثلاثة أشهر. عشتُ هذا التسلسل بحذافيره مرّات عديدة.

قائمة فحص يمكنك نسخها

إن كنت تنشر باستخدام Kamal أو Capistrano أو أيّة أداة نشر تعتمد على SSH:

  1. افتح config/deploy*.yml وgrep عن host: وservers:. أدرج كلّ hostname يظهر.
  2. شغّل dig +short على كلّ منها. كلّ ما يُعيد IP المصدر بدلاً من لا شيء يكون مُسرَّباً.
  3. تجوّل في لوحة CDN (Cloudflare وما شابه) وانتبه للنطاقات الفرعية ذات "السحابة الرمادية" — تلك مجموعة المرشّحين.
  4. أعِد تسميتها إلى alias من دون TLD (deploy.<project>)، وضع الـIP في /etc/hosts.
  5. حدّث وظيفة النشر في CI أيضاً. على GitHub Actions: echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.
  6. احذف سجلّ A العام الأصلي.

إن كان فريقك يدير عدّة مشاريع تتشارك النمط البنيوي نفسه، فاكتب grep من سطر واحد يمسح كلّ deploy*.yml في كلّ المستودعات؛ هذا أوثق من فحصها واحداً واحداً.

الدرس الفعلي

لم يخفق Cloudflare، ولم يخفق Kamal — كلتا الأداتين كانتا تؤدّيان عملهما تماماً.

الدرس هو: القيم الافتراضية تُضلّلك بهدوء. deploy.<your-domain>.com يبدو كاسم داخلي، لكنّ DNS لا يعرف مفهوم "الداخلي" — سجلّ A هو سجلّ A، ومتى نُشر فهو عام. قد يمنحك الاسم وهم الخصوصية، وأيقونة السحابة الرمادية الصغيرة في لوحة Cloudflare ليست سوى الصيغة البصرية لذلك الوهم نفسه: تجلس هناك بهدوء، بلا تحذير أحمر، لكنّها في الحقيقة تخبرك "هذا السجلّ لا يمرّ بي".

دع Claude يقرأ إعدادات النشر لديك مرّة واحدة. هو لا يشاركك ذلك الوهم.