كل webhook نرسله موقّع، كي يتمكن المستقبِل من إثبات أنه منّا
الـ webhook باب مفتوح ما لم يستطع المستقبِل التحقق ممّن طرق. نظام إرسال الأحداث لدينا يوقّع كل حمولة بـ HMAC ويرفض الاتصال بالشبكات الخاصة. إليك نموذج التهديد.
عنوان webhook هو صندوق بريد عام. فأيّ شخص يعرف العنوان يستطيع نشر حدثٍ مزوّر عبره: عميل محتمل (lead) مزيّف، وميزانية مزيّفة، وإلحاح مزيّف، والمستقبِل الساذج سيثق بكل ذلك. إن webhook غير المصادَق عليه ليس تسليمًا؛ بل هو اقتراح.
إن آلية إرسال الأحداث (event dispatch) لدينا توقّع كل حمولة (payload). فكل عملية تسليم صادرة تُجزّأ تجزئةً (hashed) بخوارزمية HMAC-SHA256 باستخدام سرٍّ خاص بكل نقطة نهاية، وتُحمل النتيجة في ترويسة `X-Webhook-Signature`. ويعيد المستقبِل احتساب التجزئة بالسرّ المشترك ويرفض كل ما لا يطابق. هذا الفحص الواحد يحوّل «طلبًا يدّعي أنه عميل محتمل» إلى «طلبٍ صادرٍ منّا بدليلٍ قاطع». كما يجعل الحمولات قابلةً لكشف العبث (tamper-evident) أثناء النقل: غيّرْ حرفًا واحدًا من الجسم، فتنكسر التوقيعة.
المصادقة نصف نموذج التهديد (threat model) فحسب. فمُرسِلٌ يقبل إرسال POST إلى أي مكان هو أداة تزوير طلبات من جانب الخادم (server-side request forgery) تنتظر توجيهها إلى الداخل. لذا نتحقّق قبل الإرسال من الوجهة ونرفض النطاقات الخاصة والحلقية (loopback): 10.0.0.0/8 و172.16.0.0/12 و192.168.0.0/16 والنطاق المحلّي للوصلة (link-local) وما شابهها. وبالاقتران مع مهلة طلبٍ صارمة (request timeout)، فإن نقطة نهاية مُساء تهيئتها أو خبيثة لا تستطيع تحويل مُرسِلنا إلى مسبارٍ ضد شبكة داخلية أو إلى اتصالٍ معلّق.
ما نرفض فعله: إرسال حمولات غير موقّعة، أو إعادة المحاولة بشكل أعمى نحو ثقبٍ أسود، أو السماح لتعريف نقطة نهاية بأن يهرّب عنوانًا داخليًا. التوقيع دون حمايات SSRF كبابٍ أمامي مقفل بجوار نافذة مفتوحة؛ ونحن نغلق كليهما.
المبدأ: ينبغي أن يكون تسليم العميل المحتمل قابلًا للتحقق عند الطرف المستقبِل، وعاجزًا عن مهاجمة أي شيء في طريق خروجه. إن لم يستطع نظام إدارة علاقات العملاء (CRM) لديك أن يميّز عملاءنا المحتملين عن عملاء غريب، فالتكامل زخرفي. فالدليل التشفيري إلى جانب سياسة خروج (egress) صارمة هو ما يجعل التسليم بين آلة وأخرى أمرًا يمكنك فعلًا الاعتماد عليه في الثانية صباحًا.