يمكن لواجهة برمجة تطبيقات (API) مبنية بإطار عمل Express وتحمل الاسم api-service أن تجتاز الاختبارات وتُبنى كصورة حاوية (OCI image)، ومع ذلك تتضمن في النهاية شيفرة برمجية لم ينوِ أحد اعتمادها. يحدث هذا عندما يضيف مطور الاسم expresss (بخطأ إملائي) بدلاً من express في ملف package.json؛ حيث يقوم السجل العام (public registry) بجلب الحزمة ذات الاسم المشابه، وتتولى بيئة تشغيل GitHub Actions تثبيتها، لتصبح جزءاً من صورة الإصدار النهائية. لم يحتج المهاجم هنا إلى صلاحيات الكتابة في المستودع، أو مفتاح نشر مسروق، أو إمكانية الوصول إلى مجموعة خوادم الإنتاج (production cluster). لذا، يبرز سؤال أكثر تحديداً وفائدة للمدافعين: كيف يمكننا منع تسلل شيفرة برمجية غير مُراجعة إلى عملية البناء من خلال آلية حل التبعيات؟ سنتتبع مسار api-service بدءاً من ملف الإعدادات (manifest)، مروراً بعملية البناء في نظام التكامل المستمر (CI)، ووصولاً إلى الصورة المنشورة.
[المصادر][/المصادر]
يمر مسار البناء عبر عدة حدود للثقة قبل أن تصل الصورة إلى مرحلة النشر؛ إذ يوفر سجل الحزم العام الشيفرة البرمجية بناءً على الاسم المذكور في ملف الإعدادات، وتنفذ بيئة تشغيل CI عملية التثبيت، بينما يخزن سجل المخرجات (artifact registry) الصورة الناتجة تمهيداً لنشرها لاحقاً. ويمكن لحزمة خبيثة أن تسعى لتنفيذ شيفرة برمجية أثناء عملية البناء، أو إدخال تبعية متعدية (transitive dependency) ضارة، أو سرقة رمز مميز للنشر (publishing token) متاح لبيئة التشغيل. يوضح المسار التالي هذه الحدود: [المصادر][/المصادر]
يوضح هذا الرسم البياني سبب كون اختيار التبعيات (dependencies) قراراً يتعلق بسلسلة التوريد وليس مجرد تفصيل برمجي محلي. فبمجرد أن تقوم أداة التنفيذ (runner) بجلب حزمة ما، تصبح محتوياتها جزءاً من مدخلات عملية البناء، وقد تقوم بتنفيذ إجراءات برمجية مرتبطة بالتثبيت (installation hooks) ما لم تفرض خط أنابيب العمليات (pipeline) قيوداً عليها. [المصادر][/المصادر]
يُعد ملف التعريف (manifest) خط الدفاع الأول، لأن الأمر npm install يتعامل مع كل اسم حزمة مُعلن عنه كتعليمات لتحديد موقع الكود وجلبه. في خدمة api-service، يُفترض استخدام الحزمة express بالإصدار 4.18.3، بينما يمثل الاسم المبتكر expresss بالإصدار 1.0.0 خطأً مطبعياً يشير إلى حزمة مختلفة. يُعرف هذا النوع من الهجمات بـ “الاستيلاء عبر الأخطاء المطبعية” (typosquatting)، حيث ينشر المهاجم حزمة تحمل اسماً مشابهاً لتبعية موثوقة، معتمداً على الخطأ البشري في اختيارها.
[المصادر][/المصادر]
يسجل ملف القفل (lockfile) إصدار الحزمة الذي تم تحديده، والبيانات الوصفية للسجل (registry metadata)، وبصمة التحقق من السلامة (integrity digest) الخاصة بمخطط تبعيات معين. يجعل هذا السجل عمليات التثبيت اللاحقة قابلة للتكرار (أي تعطي نفس النتائج) ولكن فقط بعد أن تكون الحزمة الخاطئة قد قُبلت وأُدرجت بالفعل في المستودع (committed). وبالتالي، يظهر الفشل في عملية المراجعة في مكانين: يحتوي ملف package.json على تبعية مباشرة غير متوقعة، ويحتوي ملف package-lock.json على إدخال جديد للحزمة المحددة وقيمة للتحقق من السلامة. ما هو اسم الحزمة أو الإدخال في ملف القفل الذي كان ينبغي أن يؤدي إلى فشل مراجعة إضافة الحزمة expresss؟
[المصادر][/المصادر]
يوضح هذا الكود أن نجاح عملية التثبيت لا يُعد دليلاً على اختيار التبعية (dependency) المقصودة؛ فقد قام المثبِّت بحل اسم الحزمة expresss تماماً كما طُلب منه، ثم سجل النتيجة في ملف القفل (lockfile)، وتكرار هذا الخطأ بشكل حتمي يظل خطأً. إن مراجعة فروقات التبعيات تعني التعامل مع أي اسم غير مألوف، أو إصدار غير متوقع، أو إدخال جديد للتحقق من السلامة (integrity entry)، باعتبارها كوداً برمجياً يدخل في حزمة الإصدار.
قاعدة المراجعة يثبت ملف القفل ما اختاره المثبِّت، لكنه لا يثبت أن هذا الاختيار كان مصرحاً به أو آمناً.
قد يبدأ الأمر باسم عام (public name) مكتوب بشكل خاطئ في ملف البيان (manifest)، ولكن يمكن استبدال حزمة خاصة حتى لو كان اسمها صحيحاً. يحدث ما يُعرف بـ “الخلط بين التبعيات” (Dependency confusion) عندما تقوم عملية البناء بحل حزمة من سجل غير مقصود؛ وعادة ما يحدث ذلك لأن حزمة عامة تحمل الاسم نفسه -أو إصداراً أعلى يطابق النطاق المطلوب- تتفوق على الحزمة الداخلية. يحتاج المهاجم فقط إلى القدرة على النشر في السجل الذي تعتمد عليه عملية البناء، ولا يحتاج إلى الوصول لمستودع المصدر الخاص.
بالنسبة لخدمة api-service، يجب دائماً حل تبعية داخلية مثل @acme/auth-client من السجل الخاص بالمؤسسة. يسمح نطاق الإصدارات الواسع باستخدام إصدارات مستقبلية تفي بالمتطلبات المعلنة، بينما تحدد أولوية السجل المكان الذي يبحث فيه المثبِّت. يحدد ملف القفل النتيجة لعملية بناء معينة (committed build)، لكنه لا يستطيع تصحيح تعيين السجل الذي يوجه عملية إعادة البناء إلى مصدر خاطئ، كما لا يمكنه الحماية من تحديث متعمد لملف القفل يفشل المراجعون في فحصه. يتمثل هدف التحكم في ضمان حل التبعيات بشكل حتمي من السجل المقصود لكل نطاق حزمة (package scope).
توضح المقارنة الفرق بين الحدود التي يتجاوزها كل تهديد وبين الأدلة التي تكشفه: