حادثة · برمجية npm خبيثة ذاتية الانتشار
دودة Shai-Hulud في npm: ما فعلته على Mac للمطور وما كان Kilobat سيعرضه
في سبتمبر 2025 انتشرت دودة عبر npm: سرقت كل حزمة مصابة رموز المطور أو نظام CI، واستخدمت أي رمز npm وجدته لنشر إصدارات مصابة من حزم أخرى لصاحبها، وسرّبت الأسرار عبر GitHub. وبدأت موجة ثانية أكبر في نوفمبر قبل اكتمال التثبيت.
باختصار
- الحماية: لم يكن ليساعد
لا شيء يُحظر على Mac. ذهبت حركة البيانات إلى npm وGitHub وواجهات السحابة؛ واستُدعي webhook.site من مشغّلات GitHub، لا من أجهزة المطورين.
- حارس الوكلاء: لم يكن ليساعد
لم تدخل قاعدة webhook.site حيز التنفيذ، لأن الطلب جاء من مشغّل GitHub Actions. وكانت الوجهات الأخرى مضيفين معروفين أو عناوين محلية.
- سجل شبكة الوكلاء: محدودة
في تثبيت أجراه وكيل، كان سيعرض TruffleHog وcurl وهو يستدعي واجهة GitHub و
npm publishومضيفين سحابيّين يُستخدمون للمرة الأولى، جميعهم ضمن تثبيت واحد.
ما حدث
تقول GitHub إنها أُبلغت بالهجوم في 14 سبتمبر 2025. أبلغت Socket وStepSecurity أولًا عن @ctrl/tinycolor 4.1.1 و4.1.2 ونحو 40 حزمة أخرى؛ وخلال أسبوع تجاوز العدد 500، بما فيها حزم نشرتها CrowdStrike. شغّل كل إصدار مصاب ملف bundle.js بحجم 3.6 MB من خطاف postinstall، على macOS وLinux فقط.
استخرج البرنامج النصي متغيرات البيئة، ونزّل ماسح الأسرار مفتوح المصدر TruffleHog من إصداراته على GitHub وشغّله على نظام الملفات كله، وجرّب نقاط بيانات التعريف السحابية التي تمنح بيانات اعتماد للأجهزة السحابية. وباستخدام بيانات اعتماد AWS أو Google Cloud، عرض الأسرار في مديري أسرار تلك الحسابات وقرأها.
باستخدام رمز npm من ~/.npmrc أو البيئة، تحقّق من الرمز عبر نقطة whoami في npm، وبحث عن حزم أخرى لصاحبها، وأضاف نفسه إلى كل منها مع زيادة إصدار التصحيح ونشرها. وهذا ما جعله دودة: أصاب رمز مطور واحد كل حزمة يستطيع نشرها.
باستخدام رمز GitHub، أنشأ مستودعًا عامًا باسم Shai-Hulud يضم البيانات المجمّعة في data.json مشفّر بترميز base64 مرتين، ودفع فرع shai-hulud بسير عمل، .github/workflows/shai-hulud-workflow.yml، إلى المستودعات التي يستطيع الرمز الوصول إليها، ونسخ في بعض الحالات مستودعات مؤسسات خاصة إلى مستودعات عامة موسومة بـ «Shai-Hulud Migration». عمل سير العمل على GitHub وأرسل أسرار المستودع إلى عنوان webhook.site. ووجدت Wiz أن webhook عُطّل بسبب الاستخدام المفرط، لكن سير العمل طبع الأسرار أيضًا في سجلاته.
استخدمت الموجة الثانية، التي رُفعت بين 21 و23 نوفمبر، ملفات جديدة هي setup_bun.js وbun_environment.js، وعملت في مرحلة preinstall، واستمرت في الخلفية على أجهزة المطورين حتى لا تبدو عمليات التثبيت بطيئة. ونشرت الأسرار في مستودعات يرد فيها Shai-Hulud في الوصف، بلغ عددها أكثر من 25,000 وفق Wiz، وسجّلت الأجهزة المصابة كمشغّلات GitHub Actions ذاتية الاستضافة باسم SHA1HULUD، لتتيح منفذًا للعودة.
التسلسل الزمني
- 14 سبتمبر 2025
أُبلغت GitHub بدودة ذاتية التكاثر في حزم npm (GitHub).
- 15 سبتمبر 2025
أبلغت Socket وStepSecurity عن
@ctrl/tinycolorوأكثر من 40 حزمة أخرى. - 16 سبتمبر 2025
أحصت Socket نحو 500 حزمة متأثرة، منها حزم CrowdStrike؛ وتابعت Aikido وآخرون انتشارها.
- 22 سبتمبر 2025
أزالت GitHub أكثر من 500 حزمة، وجعلت npm تحظر الملفات المرفوعة التي تحمل مؤشرات الدودة، وأعلنت النشر المحلي بالمصادقة الثنائية فقط، والرموز الدقيقة لمدة 7 أيام، والنشر الموثوق.
- 23 سبتمبر 2025
نصح تنبيه CISA بتغيير بيانات اعتماد المطورين وحظر الاتصالات الصادرة إلى webhook.site.
- 21–23 نوفمبر 2025
رُفعت حزم الموجة الثانية إلى npm (Wiz وAikido).
- 24–26 نوفمبر 2025
ظهرت آلاف المستودعات التي تحتوي على أسرار مسرّبة؛ وبدأت GitHub إزالتها وإلغاء الرموز المسرّبة في 26 نوفمبر.
كيف بدا على الشبكة
على Mac المطور، ذهبت معظم الاتصالات إلى مضيفين يستخدمهم المطور طوال اليوم. جلب الحزمة المصابة من registry.npmjs.org، ثم استدعى /-/whoami و/v1/search، ونزّل حزمًا أخرى لصاحبها ونشر إصدارات جديدة: عملية رفع من npm أثناء التثبيت. وعلى api.github.com قرأ الحساب وأنشأ مستودع Shai-Hulud ودفع الفروع وملفات سير العمل؛ وتُظهر شفرة StepSecurity بعد فك تمويهها أن وحدة الباب الخلفي فعلت ذلك باستخدام curl. وجاء TruffleHog من تنزيلات إصدارات GitHub.
باستخدام بيانات اعتماد سحابية، وصل أيضًا إلى واجهات AWS Secrets Manager وGoogle Secret Manager. وجرّب عنوانَي بيانات التعريف 169[.]254[.]169[.]254 وmetadata[.]google[.]internal اللذين لا يستجيبان إلا على الأجهزة السحابية. بدأت شجرة العمليات من npm مرورًا بصدفة إلى node bundle.js، الذي شغّل trufflehog، وهو برنامج bash يشغّل curl وnpm publish.
لم يُتصل بالمضيف غير المعتاد الوحيد، webhook[.]site، من Mac أصلًا. فقد كان في سير العمل الذي دفعته الدودة إلى GitHub، لذا جاء الطلب من مشغّل تستضيفه GitHub عند تنفيذ سير العمل.
المؤشرات
كما توثقها المصادر. النطاقات نصوص وليست روابط، وقد عُطلت صيغتها ([.]) حيث فعل المصدر ذلك. معظم هذه البنية اختفى منذ زمن: تساعد المؤشرات في فحص السجلات والملفات والحسابات القديمة، لا حظر حملات اليوم.
| المؤشر | النوع | أين يظهر | ملاحظة |
|---|---|---|---|
webhook[.]site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7 | عنوان URL | مشغّل GitHub Actions | الجهة التي أرسل إليها سير العمل المحقون أسرار كل مستودع. عطّلتها المنصة. |
api.github.com | المضيف (مشروع) | جهاز Mac | استدعاءات /user و/user/repos ودفع الفروع وملفات سير العمل أثناء تثبيت npm. |
registry.npmjs.org/-/whoami, /v1/search?text=maintainer:… | استدعاءات واجهة npm | جهاز Mac | التحقق من الرمز والبحث عن حزم صاحبها، ثم npm publish. |
secretsmanager.*.amazonaws.com, secretmanager.googleapis.com | واجهات السحابة | جهاز Mac الخاص بك، CI | فقط حيث توفرت بيانات اعتماد سحابية. |
46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09 | بصمة SHA-256 لـ bundle.js | node_modules | حمولة سبتمبر. |
shai-hulud branch, .github/workflows/shai-hulud-workflow.yml | الفرع والملف | مستودعات GitHub | سير العمل الذي أرسل الأسرار إلى webhook وإلى السجلات. |
Shai-Hulud repository with data.json; “Shai-Hulud Migration” | مستودعات GitHub | حساب GitHub | البيانات المجمّعة والمستودعات الخاصة المنسوخة إلى مستودعات عامة ذات اللاحقة -migration. |
setup_bun.js, bun_environment.js, SHA1HULUD | الملفات واسم المشغّل | node_modules، GitHub | حمولة موجة نوفمبر والمشغّل ذاتي الاستضافة الذي سجّلته. |
ما كان Kilobat سيعرضه أو يحظره
- سجل شبكة الوكيل. في تثبيت أجراه وكيل، كانت الجلسة ستجمع المضيفين حسب العملية: يبدأ
trufflehogأثناء التثبيت، ويستدعيcurlالعنوان api.github.com، ويرسلnpmإلى registry.npmjs.org، ومع وجود بيانات اعتماد سحابية على Mac، تظهر مضيفات واجهات AWS أو Google بعلامة المرة الأولى. وتُتبع العمليات التي استمرت في الخلفية بعد التثبيت، لأن العمليات المسلّمة إلى launchd تظل منسوبة للوكيل. - حركة التطبيقات. حتى دون وكيل، تعرض قائمة تطبيقات Kilobat أدوات سطر الأوامر التي ليست جزءًا من macOS مع مسار ملفها التنفيذي، لذا يظهر
trufflehogلم تثبّته أو عملية node ترفع إلى GitHub أثناء عملها. - الحماية. لا شيء على Mac. لا يُعد npm وGitHub ومزودو السحابة تهديدات، والمضيف الوحيد الذي طُلب من المدافعين حظره اتصلت به مشغّلات GitHub.
- حماية الوكيل. تذكر قاعدة التقاط الطلبات webhook.site وترفضه للوكيل وكل ما يبدأه، لكن الطلب هنا جاء من مشغّلات GitHub ولم يأتِ قط من Mac. عنوان بيانات التعريف محلي للوصلة، ويعامله Kilobat كشبكة محلية.
أين لم يكن Kilobat ليساعد
- حركة البيانات التي أحدثت الضرر على npm وGitHub استخدمت رموز المطور الصالحة نفسها وتبدو كنشر ودفع عاديين.
- حدث تسريب البيانات عبر webhook وطباعة الأسرار في سجلات سير العمل على مشغّلات GitHub. يهم حظر webhook.site، كما أوصت CISA، خصوصًا حيث يعمل CI.
- يسجل Kilobat المضيفين وعدد البايتات، ولا يسجل المحتوى أبدًا، لذا لا يستطيع تحديد ما وجده TruffleHog أو ما تلقّاه المستودع.
- لا تُنسب إلى الوكيل عمليات التثبيت التي تعمل داخل حاويات Docker أو بواسطة مضيف إضافات المحرر في VS Code.
- أُزيلت الحزم المصابة منذ ذلك الحين، ولا يصل سجل الوكيل الممتد 30 يومًا إلى عام 2025؛ وما يمكن فحصه الآن هو ملفات القفل والذاكرة المؤقتة وحساب GitHub.
تحقق من Mac الآن
قارن ملفات القفل بقوائم الحزم المنشورة من Socket وStepSecurity وWiz. لحزمة واحدة:
npm ls @ctrl/tinycolorابحث في مجلد المشروع عن ملفات حمولة أي من الموجتين.
find . -path "*/node_modules/*" \( -name bundle.js -o -name setup_bun.js -o -name bun_environment.js \) -printقارن أي
bundle.jsببصمة سبتمبر. التطابق يعني أنها الدودة؛ أما ملفات bundle.js الأخرى فشائعة وغير ضارة عادةً.find . -path "*/node_modules/*" -name bundle.js -exec shasum -a 256 {} + | grep 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09اعرض مستودعات GitHub التي يرد Hulud في اسمها أو وصفها.
gh repo list --limit 1000 --json name,description --jq '.[] | select((.name + " " + (.description // "")) | test("hulud"; "i")) | .name'في Settings ‹ Actions ‹ Runners لكل مؤسسة ومستودع، أزل أي مشغّل ذاتي الاستضافة لم تُعدّه، خصوصًا المشغّل المسمّى
SHA1HULUD. على Mac، إذا لم تشغّل مشغّلًا بنفسك، فمن المفترض ألا يطبع هذا شيئًا.pgrep -fl "Runner.Listener|Runner.Worker"إذا وُجد أي تطابق، فغيّر رموز npm وGitHub ومفاتيح السحابة، ثم اعرض رموز npm لحذف القديمة.
npm token listلمنع تشغيل برامج التثبيت النصية دون طلبك، عطّلها في npm وشغّلها لكل مشروع عند الحاجة.
npm config set ignore-scripts trueفي المرة القادمة، راجع ما وصل إليه كل تثبيت أجراه وكيل. انتبه لعمليات الرفع إلى سجل npm واستدعاءات GitHub API أثناء التثبيت.
kilobat agent-log --all
المصادر
- مدونة GitHub: خطتنا لسلسلة توريد npm أكثر أمانًا
- CISA: اختراق واسع لسلسلة التوريد يؤثر في منظومة npm
- StepSecurity: دودة Shai-Hulud ذاتية التكاثر تخترق أكثر من 500 حزمة npm
- Socket: اختراق حزمة tinycolor الشهيرة على npm في هجوم سلسلة توريد يؤثر في أكثر من 40 حزمة
- Wiz: هجوم Shai-Hulud على سلسلة توريد npm
- Aikido: مهاجمو S1ngularity/nx يضربون مجددًا
- Wiz: هجوم Sha1-Hulud 2.0 على سلسلة التوريد
- Unit 42: دودة «Shai-Hulud» تخترق منظومة npm في هجوم سلسلة توريد
تم التحقق في 1 نوفمبر 2026.
اعرف ما تتصل به وكلاؤك وتطبيقاتك
Kilobat تطبيق لشريط قوائم Mac يحدد التطبيق والعملية والوكيل وراء كل اتصال، ويبعدها عن المضيفين المعروفين بسوئهم، ويسجل الوجهات التي بلغتها كل جلسة وكيل.
brew install seifip/kilobat/kilobat