
ماسح لهجوم سلسلة التوريد keyv/cacheable: يكتشف حزم npm المخترقة، ويتحقق من تجزئات الحمولة، ويعثر على زرع الاستمرارية في وضعَي المستودع والمضيف.
الإنجليزية | Português
ماسح مستقل لحادث سلسلة التوريد keyv/cacheable ("Shai-Hulud: Here We Go Again", 4 أغسطس 2026) — ما يزيد عن 440 حزمة npm تم اختراقها بواسطة دودة ذاتية الانتشار تسرق بيانات اعتماد السحابة/CI وتزرع وسائل استمرارية مع مفتاح الرجل الميت.
يكتشف، في دقائق وبدون تثبيت أي شيء:
package-lock.json وnpm-shrinkwrap.json وyarn.lock (الإصدار 1 وBerry) وpnpm-lock.yaml وbun.lock — بما في ذلك الاعتماديات غير المباشرة، مع السلسلة الكاملة (مثل: eslint → file-entry-cache → flat-cache → [email protected]);node_modules (الاسم + تجزئة SHA-256 للقطع الأثرية المعروفة);SUSPECT.claude/settings.json و.vscode/tasks.json، قطع أثرية مؤقتة (bun-dl-*);keyv/cacheable تم اختراقه؛ نشر المهاجم نسخًا جديدة مع خطاف "preinstall": "node setup.mjs" — كود يُنفَّذ قبل تثبيت الحزمة، بصلاحيات من شغّل npm install.setup.mjs ينزّل بيئة تشغيل Bun من GitHub وينفّذ الحمولة فيها — تهرب من الأدوات التي تراقب عمليات node فقط.Math_Symbol.js (~728 كيلوبايت، مُشوَّه) يسرق بيانات الاعتماد: بيانات تعريف مثيل AWS، مفاتيح AWS/GCP/Azure، رموز Vault، حسابات خدمة Kubernetes، أسرار GitHub Actions، رموز npm، بالإضافة إلى فحص regex عام للمفاتيح الخاصة ورموز bearer على القرص.npm install/npm ci مع تفعيل سكربتات دورة الحياة منذ 2026-08-04 09:35 UTC. مع --ignore-scripts، لم يعمل الخطاف..claude/settings.json (SessionStart) و.vscode/tasks.json (folderOpen) تنفّذ المُحمِّل عند فتح المجلد المستنسَخ — بدون npm install، وبدون تثبيت أي شيء. يشمل ذلك من استنسخ المستودع للتحقيق في الحادث ووكلاء البرمجة بالذكاء الاصطناعي الذين فتحوا الدليل — واحدة من أولى الحالات العامة لخطافات وكلاء الذكاء الاصطناعي (.claude/) المستخدمة كناقل هجوم لسلسلة التوريد.الغرسة تثبّت "مراقبًا" (gh-token-monitor) يُبقيه حيًّا LaunchAgent (ماك) أو خدمة مستخدم systemd + loginctl enable-linger (لينكس). كل 60 ثانية يتحقق من رمز GitHub المسروق مقابل الواجهة البرمجية. طالما أن الرمز يعمل، لا يحدث شيء. عندما تتحول الاستجابة إلى 4xx — أي في اللحظة التي تلغي فيها الرمز — ينفّذ، عبر eval، محتويات ~/.config/gh-token-monitor/handler: أمر تعسّفي يعرّفه المهاجم عن بُعد. التحليلات العامة لا تعرف ما يحتويه — قد يكون تدميرًا للبيانات، أو إعادة غرس، أو فدية، أو لا شيء. الخطر غير قابل للتقييم؛ لهذا فإن ترتيب الاستجابة مطلق.
ثلاث خصائص تغيّر الاستجابة:
host.[email protected] مع مصادقة SLSA ناجحة. شهادة الإثبات (Provenance) تشهد على سلامة البناء، وليس المصدر: سير العمل الشرعي جمّع كودًا مُلوَّثًا مسبقًا.package.json (خطاف preinstall) وملفين جديدين أُضيفا إلى الحزمة (setup.mjs، Math_Symbol.js).eslint → file-entry-cache → flat-cache → keyv. لهذا يعرض الماسح السلسلة في كل نتيجة.scan.mjs له الخصائص التالية — مهمة لأي شخص يستجيب لحادث سلسلة توريد:
npm install. راجع scan.mjs بالكامل في 15 دقيقة قبل تشغيله.--update (تنزيل قائمة IoC جديدة)، صريحة واختيارية.docker run --network=none أو جهاز معزول: فقط انسخ scan.mjs + iocs.json.المتطلب: Node.js ≥ 18 (أي جهاز عليه npm يملكه بالفعل). نزّل الملفين — scan.mjs + iocs.json — وهذا كل شيء: لا تثبيت.
تنبيه: إذا استنسخت هذا المستودع كاملًا، فإن مجلد
fixtures/يحتوي مؤشرات IoC خاملة مستخدمة في الاختبارات (أسماء ونسخ حقيقية، محتوى وهمي — لا برمجيات خبيثة). يتخطاه الماسح تلقائيًا ويحذّر في المخرجات؛ النتائج منه لا تظهر إلا إذا فحصته عمدًا.
هناك وضعا تشغيل يجيبان عن سؤالين مختلفين، وهذا ما يحدد أين تنفّذ:
repo يقرأ ملفات القفل وnode_modules — وملفات القفل تعيش في git، لذا يمكن مركزيته: شخص واحد يفحص كل مستودع في الشركة.host يبحث عن الغرسة (المراقب، LaunchAgent/systemd، خطافات IDE)، وهي تعيش على الجهاز الذي نُفِّذ فيه الكود — وهذا ليس في git ولا يمكن مركزته.node scan.mjs repo /folder/with/all/the/repos --json=result.json --html=report.html
يجيب عن "أي المشاريع معرَّضة" في دقائق، دون إشراك أحد. يقبل مسارات متعددة؛ ويتنقل عبر الأدلة الفرعية (المستودعات الأحادية ومساحات العمل مشمولة).
لكل مشروع ظهرت فيه نتيجة، حدّد من تعامل معه منذ 2026-08-04 09:35 UTC (git log، سجلات CI). هؤلاء الأشخاص يشغّلون على أجهزتهم:
node scan.mjs # current directory + host, in ~30 seconds
يشمل النطاق: أي شخص (أ) شغّل npm install/npm ci في النافذة الزمنية؛ أو (ب) استنسخ فقط وفتح المجلد في VS Code أو وكيل ذكاء اصطناعي — الناقل ب لا يتطلب تثبيتًا.
بما أن التكلفة ~30 ثانية والقمع قد يتسرّب (استنساخ شارد، مشروع شخصي)، فإن الرسالة الداخلية الأكثر أمانًا هي: كل مطوّر يشغّل node scan.mjs مرة واحدة ويرسل --json/--html إلى AppSec. الإرسال يدوي بالتصميم — الماسح بلا تليمترية (صفر اتصال خارجي).
الأولوية القصوى: هنا تعيش بيانات الاعتماد الأكثر قيمة. هنا للماسح دوران مختلفان — واحد للماضي، وواحد للمستقبل:
فرز الماضي — لا تفحص العدّاء لتقرر. السؤال "هل أُصيب هذا العدّاء؟" لا يجاب عنه بالفحص: إذا ثبّتت أي وظيفة (job) نسخة متأثرة بدون --ignore-scripts منذ 08-04، فإن بيانات الاعتماد سُرقت بالفعل في تلك اللحظة، ونادرًا ما يحتفظ مضيف العدّاء بأدلة (العدّاؤون المؤقتون يدمرون الحاوية عند نهاية الوظيفة؛ والمراقب يمسح نفسه في ~24 ساعة). ما يجيب عنه هو ملفات قفل الخطوة 1 وسجلات CI. إذا كان الجواب "نعم، ثبّت": أعد بناء العدّاء وأدر أسراره — العدّاؤون مؤقتون، لا سبب لتنظيفهم.
الوقاية من الآن فصاعدًا — نعم، شغّله في خط الأنابيب. أضف الماسح كخطوة بناء، في وضع repo، بعد السحب (checkout) وقبل npm install. لا يفحص مضيف العدّاء — بل يفحص الكود المزمع تثبيته، ورمز الخروج يوقف البناء قبل أن تتاح الفرصة لـ preinstall الخبيث للعمل:
# example (GitHub Actions / GitLab CI — adapt):
- run: node scan.mjs repo . --json # exit 0 clean · 1 findings · 2 COMPROMISED
- run: npm ci --ignore-scripts # only runs if the previous step passed
node scan.mjs # scan the current directory + the host
node scan.mjs repo /path/a /path/b
node scan.mjs host # persistence/implants on the machine only
node scan.mjs repo . --json=result.json --html=report.html
node scan.mjs --update # update iocs.json (the only network operation)
| المستوى | المعنى | الإجراء |
|---|---|---|
COMPROMISED | نسخة خبيثة مثبتة في node_modules، حمولة مؤكدة بالتجزئة، أو وجود غرسة استمرارية | تعامل مع المضيف كمخترَق؛ اتبع ترتيب الاستجابة — نظّف الغرسة قبل تدوير بيانات الاعتماد |
EXPOSED | نسخة خبيثة مقيدة (pinned) في ملف قفل، بدون دليل تنفيذ | قيّد نسخة آمنة، احذف node_modules، وأعد التثبيت مع --ignore-scripts |
AT_RISK | نطاق (^/~) في package.json يسمح بنسخة خبيثة | قيّد إصدارًا دقيقًا (pin) أو احجبه عند وكيل السجل (registry proxy) |
SUSPECT | استدلالي (اسم ملف للدودة بتجزئة غير مطابقة، سكربت دورة حياة مشبوه) | افحص يدويًا — قد يكون نسخة جديدة أو إيجابية كاذبة |
INFO | ناقل موجود لكن بدون IoC (مثل مهمة folderOpen عامة) | راجع |
--html ينتج تقريرًا مكتفيًا ذاتيًا مع طابع زمني، واسم مضيف، وإصدار قائمة IoC، و SHA-256 للماسح نفسه — قابل للاستخدام كمرفق لإشعار حادث وكسجل تدقيق.
لا تلغِ أو تُدر أي بيانات اعتماد بعد — ذلك هو مشغّل الفخ. التسلسل:
1. اعزل — افصل الجهاز عن الشبكة. الأمر آمن: بدون استجابة HTTP لا يوجد 4xx، ولا ينطلق الفخ، ويتوقف تسريب البيانات. لا تُطفئ الجهاز (الذاكرة المتطايرة دليل).
2. حافظ — قبل حذف أي شيء (المراقب يدمر نفسه في ~24 ساعة):
mkdir -p /tmp/evidence && cp -r ~/.config/gh-token-monitor /tmp/evidence/ 2>/dev/null
cp /tmp/gh-token-monitor.*.log /tmp/evidence/ 2>/dev/null
shasum -a 256 /tmp/evidence/* 2>/dev/null
ملف handler هو أمر المهاجم الذي كان سيُنفَّذ — لا تشغّله ولا تلصقه في طرفية (shell)؛ تعامل معه كنص خامل. ملف started_at يحدد نافذة التعرض (سيطلبه المدقق والجهة التنظيمية).
3. استأصل — اقتل عملية المراقب أولًا، ثم:
# macOS
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
rm -f ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
# Linux
systemctl --user disable --now gh-token-monitor.service
loginctl disable-linger "$USER"
rm -f ~/.config/systemd/user/gh-token-monitor.service
# both
rm -rf ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh /tmp/bun-dl-*
أزل أيضًا الخطافات الخبيثة من .claude/settings.json و.vscode/tasks.json، والملفات setup.mjs/Math_Symbol.js/math_init.js، وامسح المخابئ (~/.npm/_cacache, pnpm, yarn). شغّل node scan.mjs host مرة أخرى حتى يعود نظيفًا.
4. أدر — فقط بعد تنظيف كل مضيف متأثر والتحقق منه (مراقب حي واحد كافٍ لإطلاق المشغل). ألغِ رمز npm أولًا (يوقف انتشار الدودة)؛ ثم GitHub (PATs، مفاتيح النشر)، AWS/GCP/Azure، Vault، Kubernetes، أسرار CI — وأي سر كان على القرص، لأنه جرى فحص regex عام.
5. دقّق — الدودة تتصرف باسمك: ابحث عن مستودعات موصوفة بـ Shai-Hulud: Here We Go Again في مؤسساتك، وإصدارات npm نُشرت بشكل غير متوقع منذ 08-04 (ألغِ نشرها وأبلغ المستهلكين)، وعن استخدامات بيانات الاعتماد في CloudTrail/سجلات التدقيق ضمن نافذة started_at.
بعد ذلك: احذف node_modules، وأعد التثبيت من ملف قفل نظيف مع --ignore-scripts. عدّاؤو CI والمضيفون مع تنفيذ مؤكد: أعد البناء من الصفر دائمًا — نُفِّذ كود تعسفي، وقائمة القطع الأثرية المعروفة ليست ضمانًا للاكتمال.
المؤسسات الخاضعة للتنظيم (البرازيل): يمكن أن يطلق الاختراق المؤكد مع وصول إلى بيانات الاعتماد التزامات إبلاغ (Res. CMN 4.893/2021، Res. BCB 85/2021؛ LGPD art. 48 إذا كانت بيانات شخصية معنية). وثّق الجدول الزمني بتوقيت UTC —
started_at، الاكتشاف، الاحتواء، الاستئصال، التدوير — وأكّد المواعيد النهائية مع الفريق القانوني/قسم الامتثال.
الحادث نشط والقائمة تنمو. لسحب أحدث قائمة (manifest):
node scan.mjs --update # the only operation that touches the network
--update يجلب iocs.json من هذا المستودع (securest8/npm-incident-response)، ولا من طرف ثالث أبدًا — Securest8 هي بوابة التنظيم. تحصل على ما نُشر آخر مرة هنا.
قائمة الحزم تأتي من خلاصة Wiz العامة؛ التجزئات ونطاقات C2 ومؤشرات IoC للاستمرارية والنسخ الآمنة ثابتة ومنتقاة في tools/gen-iocs.mjs. توجد لقطة من CSV الخاصة بـ Wiz في tools/keyv-packages.csv لإمكانية إعادة الإنتاج والتشغيل دون اتصال.
node tools/gen-iocs.mjs # fetch the latest Wiz CSV, regenerate iocs.json + refresh the snapshot
node tools/gen-iocs.mjs --offline # regenerate from the committed snapshot, no network
node tools/gen-iocs.mjs --allow-shrink # allow a package count lower than the snapshot (guarded by default)
المولّد متطابق (idempotent): يحافظ على manifest_version الحالي ولا يعيد كتابة الملف عندما لا يتغير شيء جوهري، ويرفض كتابة قائمة (manifest) فارغة أو منكمشة (حماية من خلاصة مصبوبة/مغيّرة في المنبع).
الأتمتة: .github/workflows/update-iocs.yml يشغّل المولّد يوميًا (وعند الطلب) ويُسجّل الالتزامات (commits) في main فقط عندما تتغير مؤشرات IoC فعلًا — بحيث يتتبع --update لدى المستخدمين خلاصة Wiz خلال يوم تقريبًا، مع تاريخ كامل قابل للتدقيق في السجلات. حوّله إلى خطوة سحب (pull request) — كما هو مذكور في سير العمل — عندما يهدأ الحادث وتريد دمجًا يدويًا بدلًا من ذلك.
node test/run-tests.mjs # 15 assertions against fixtures/demo-repo
fixtures/demo-repo مستودع اختبار يحتوي مؤشرات IoC خاملة (أسماء ونسخ حقيقية، محتوى وهمي — لا برمجيات خبيثة). عند فحص مستودع الماسح نفسه، يُتخطى مجلد fixtures/ تلقائيًا (مع تحذير في المخرجات)؛ وتفحصه الاختبارات بتمرير المسار صراحةً.
أداة لحادث واحد، صُممت للفرز السريع خلال النافذة النشطة للهجوم — وليست بديلًا عن Socket أو Snyk أو ما يماثلها. البحث ومؤشرات IoC: Socket.dev، Wiz Research (الملف CSV العام)، Kodem Security.
صيانة بواسطة Securest8. رخصة MIT.