مكتبات SD-JWT لـ JavaScript تُسقط الإفصاحات المُعدَّلة بصمت وتُبلغ بالنجاح (CVE-2026-62374)
فجوة في المطابقة للمواصفة من جهة المُتحقِّق في مكتبات SD-JWT لـ JavaScript التابعة لـ OpenWallet Foundation تتيح لحامل بيانات الاعتماد حذف مطالبة (claim) بصمت، ولا يمنع ربط المفتاح ذلك.
تحليل هندسي آلي من مكتب خالد أبوبكر الهندسي، مبني على المصادر الأولية المذكورة أدناه. لم يراجعه شخص قبل النشر. عن المكتب
الخلاصة
أُصلحت @sd-jwt/core في الإصدار 0.20.0، لكن لا يوجد إصدار مُصحِّح مُدرج للحزمة المستقلة @sd-jwt/decode بخصوص CVE-2026-62374. حتى الإصدار 0.19.0، تُسقط الحزمتان الإفصاح (Disclosure) المُعدَّل وتُبلغان بالنجاح، ولا يمنع ربط المفتاح (key binding) الحامل من ذلك. رقِّ @sd-jwt/core الآن إذا كان أي مُتحقِّق يمنح الوصول عند غياب مطالبة العمر أو الدور أو القيد، وغيِّر تلك السياسة لتصبح رفضًا؛ أما المُتحقِّقات التي تشترط وجود المطالبات فيمكنها تطبيق التصحيح ضمن الدورة الاعتيادية.
ماذا حدث
نشر مشروع sd-jwt-js التابع لـ OpenWallet Foundation النشرة الأمنية GHSA-f9j6-8p6x-r9j6، المُتتبَّعة بالمعرّف CVE-2026-62374. وهي تمس الحزمتين @sd-jwt/core و@sd-jwt/decode (npm) في الإصدارات 0.19.0 وما قبلها.
يكمن الخلل في طريقة تعامل هذه المكتبات مع الإفصاحات (Disclosures) المُعدَّلة. لنفترض أن أحد الإفصاحات في عرض SD-JWT عُدِّل بحيث لم يعد ملخّصه (digest) يطابق المصفوفة _sd الموقّعة من المُصدِر. لا ترفض @sd-jwt/decode العرض، بل تُسقط الإفصاح غير المطابق وتعيد مجموعة مطالبات منقوصة على أنها نجاح، في حين يبقى توقيع المُصدِر صالحًا (نشرة GitHub الأمنية).
نوعان من المدخلات يقودان إلى النتيجة نفسها:
- إفصاح يظل JSON صالحًا لكنه لم يعد يطابق ملخّصه.
- إفصاح يحتوي على بايتات UTF-8 غير صالحة. وفي هذه الحالة يستبدل المسار
TextDecoder/Bufferالبايتات التالفة بالرمز U+FFFD بدلًا من أن يفشل.
تذكر النشرة أن هذا يُخلّ بمتطلبين من RFC 9901: §7.1 الخطوة 5 (رفض الإفصاحات غير المُشار إليها) و§7.1 الخطوة 3 / §4.2.1 (رفض الإفصاحات غير الصالحة).
أكّد المُبلِّغ هذا السلوك من طرف إلى طرف عبر SDJwtInstance.verify()، بما في ذلك عند اشتراط ربط المفتاح (key binding). يوقف ربط المفتاح فعلًا المهاجم الواقع على مسار الاتصال، لأن التعديل يكسر ربط sd_hash في KB-JWT. لكنه لا يوقف الحامل المرتبط بالمفتاح، الذي يظل قادرًا على تنفيذ هذا الإنقاص.
حالة الإصلاح:
- أُصلحت
@sd-jwt/coreفي الإصدار 0.20.0. - لم يُحدَّد أي إصدار مُصحِّح للحزمة المستقلة
@sd-jwt/decode(NVD، قاعدة بيانات GitHub للنشرات الأمنية).
تُدرج GitHub درجة CVSS v4 أساسية قدرها 7.1 (High)، مطابقةً لسجل الجهة المُرقِّمة (CNA)؛ ويعرض NVD أيضًا 7.1. أما الدرجة التي اقترحها المُبلِّغ نفسه وفق CVSS v3.1 في النشرة الأمنية للمستودع فكانت 6.8. وإدخال NVD مُعلَّم بالحالة Deferred.
لماذا يهم
تحدد النشرة عمليات نشر المحافظ والمُتحقِّقات المبنية على @sd-jwt/* بوصفها مُعرَّضة، لأن SD-JWT VC وملفات تعريف EU EUDIW ARF تعيد استخدام طبقة الإفصاح نفسها. وبالنسبة إلى الفرق التي تبني تحققًا على نمط EUDI Wallet، في خدمات القطاع العام أو الخدمات الخاضعة للتنظيم مثلًا، فإن هذا يقع مباشرة في مسار الثقة.
الأثر محدود النطاق لكنه حاد. لا أحد يحصل على مطالبة لم تُصدَر له، ولا يُزوَّر التوقيع. ما قد يحدث هو أن تختفي مطالبة من النتيجة بينما تظل المكتبة تُبلغ بالنجاح.
ولا يصبح ذلك مشكلة أمنية إلا إذا كان منطق التفويض لديك يعامل غياب مطالبة تقييدية على أنه إذن: غياب حد للعمر، أو قيد على الدور، أو علامة تقييد. في هذه الحالة، يكفي إسقاط إفصاح واحد لقلب القرار دون إطلاق أي خطأ. أما المُتحقِّق الذي يشترط الوجود الصريح للمطالبة قبل منح الوصول، فلا يخسر سوى معلومات، ولا يمنح شيئًا إضافيًا.
الدرجة 7.1 تبالغ في تقدير الخطر بالنسبة إلى الأطراف المعتمِدة المصمَّمة جيدًا، وتقلّل منه بالنسبة إلى المصمَّمة تصميمًا سيئًا. فالخطورة تتوقف على منطق سياستك، لا على المكتبة وحدها.
التقييم الهندسي
تقييمنا: هذا خلل حقيقي في سلامة البيانات ضمن شيفرة المُتحقِّق، وليس كسرًا تشفيريًا، وسياستك هي ما يحدد مدى إلحاحه. على المُتحقِّقات التي تسمح افتراضيًا عند غياب مطالبة تقييدية أن تعامله بوصفه عاجلًا. أما المُتحقِّقات التي تشترط الوجود الصريح للمطالبات فيمكنها تطبيق التصحيح ضمن الدورة الاعتيادية. ثلاث نقاط تحدد طريقة الاستجابة.
ربط المفتاح ليس إجراء تخفيف هنا. ستفترض فرق كثيرة أن اشتراط KB-JWT يسدّ باب التعديل. لكنه لا يسدّ إلا باب المهاجم الواقع على مسار الاتصال. والتهديد الأهم هو الحامل نفسه، الذي لديه كل الدوافع لإخفاء قيد مرتبط ببيانات اعتماده. وتؤكد النشرة حدوث الإنقاص مع اشتراط ربط المفتاح.
انقسام حالة التصحيح هو الفخ.
- الفرق التي تعتمد على
@sd-jwt/coreوحدها لديها مسار واضح: 0.20.0. - الفرق التي تستورد
@sd-jwt/decodeمباشرة لا يوجد لها إصدار مُصحِّح مُدرج في الأدلة. - ومن غير الواضح أيضًا إلى أي إصدار من
decodeتُحَلّ الإصدارة 0.20.0 من@sd-jwt/core. إذ يشير وصف CVE إلى أن@sd-jwt/decode«also used by» (أي مستخدمة أيضًا من قِبل)@sd-jwt/core.
وإلى أن يتضح ذلك، ينبغي اعتبار أي ملف قفل (lockfile) لا يزال يحلّ @sd-jwt/decode إلى 0.19.0 أو ما قبله موضع شك، حتى لو رُقِّيت core.
قد يغيّر الإصلاح السلوك. الإصلاح الصحيح يحوّل الإسقاط الصامت إلى رفض صريح. وقد تفشل الآن عروض كانت «تعمل» سابقًا، كالعروض الصادرة عن محافظ تُنتج إفصاحات مشوّهة أو زائدة. هذه هي النتيجة الصحيحة، لكن خطّط لها في اختبارات التكامل وفي طريقة إظهار أخطاء المُتحقِّق.
الجانب المتعلق بـ Python محدود. لا تسمّي الأدلة أي حزمة Python متأثرة. وتذكر النشرة أن المكتبة المرجعية sd-jwt الخاصة بـ Daniel Fett (pip 0.10.4) رفضت بيانات الاعتماد المُعدَّلة نفسها. فالمُتحقِّقات المكتوبة بـ Python ليست معنية بهذه النشرة، لكن محفظة أو مُتحقِّقًا بـ JavaScript يقع أمام واجهة خلفية بـ Python يظل معنيًا.
أما من حيث المعايرة: يسجّل إدخال SSVC الخاص بـ ADP لدى CISA قيمة Exploitation على أنها «poc» وقيمة Automatable على أنها «yes»؛ ولا تتضمن الأدلة أي إدراج في KEV أو تقرير عن هجمات فعلية. ودرجة EPSS المعروضة هي 0.226%. ووجود وصف منشور لإثبات مفهوم، إلى جانب آلية قابلة للأتمتة، سبب كافٍ لعدم تأجيل الإصلاح حيثما كانت السياسة مُعرَّضة.
هل يعنيك هذا؟
- تعتمد على
@sd-jwt/coreبالإصدار 0.19.0 أو ما قبله، مباشرة أو بشكل متعدٍّ. - تعتمد على
@sd-jwt/decodeبالإصدار 0.19.0 أو ما قبله، مباشرة أو بشكل متعدٍّ. ولا يوجد إصدار مُصحِّح مُدرج. - تعمل خدمتك بوصفها مُتحقِّقًا أو طرفًا معتمِدًا لعروض SD-JWT أو SD-JWT VC، كالتدفقات على نمط EUDI Wallet.
- يسمح منطق التفويض لديك بالوصول عند غياب مطالبة العمر أو الدور أو القيد.
- تعتمد على ربط المفتاح بوصفه حمايتك من التلاعب بالعروض.
لحصر الاعتماديات لديك:
npm ls @sd-jwt/core @sd-jwt/decode
ما الذي ينبغي فعله
- رقِّ
@sd-jwt/coreإلى 0.20.0 في كل خدمة مُتحقِّق ومحفظة، بدءًا بأي مُتحقِّق يعمل بسياسة السماح الافتراضي. فهذا هو الإصدار المُصحِّح الوحيد المُحدَّد.npm install @sd-jwt/core@0.20.0 - أعد تشغيل
npm ls @sd-jwt/decodeبعد الترقية وسجّل الإصدار الذي يُحَلّ إليه. وإذا كانت الشيفرة تستورد@sd-jwt/decodeمباشرة، فاعتبرها غير مُصحَّحة إلى أن ينشر المشرفون إرشادات. - راجع سياسة التفويض بحثًا عن السماح الافتراضي عند غياب المطالبات. هذا ليس من إرشادات المورّد: اشترط أن تكون المطالبات التقييدية موجودة صراحةً وأن تُقيَّم. وتعامل مع غياب مطالبة العمر أو الدور أو القيد على أنه رفض أو طلب تحقق إضافي، لا على أنه إذن. فهذا يزيل الأثر بصرف النظر عن إصدار المكتبة، وهو الضابط الوحيد المتاح اليوم لمستخدمي
@sd-jwt/decodeالمستقلة. - اختبر مسارات الرفض. أضف اختبارات تكامل تؤكد أن العروض التي تحتوي على إفصاحات مُعدَّلة أو غير مُشار إليها تفشل عبر نقطة التحقق العامة لديك. ولا تُظهر الأدلة ما إذا كان الإصدار 0.20.0 يُظهر حالتي الرفض كلتيهما من
verify()وdecodeSdJwt(). - للقادة التقنيين: أضيفوا التحقق من SD-JWT إلى قائمة مراقبة الاعتماديات لديكم. إذ تصف النشرة أيضًا نتيجتين مرتبطتين متوسطتي الخطورة، تتعلقان بأسماء مطالبات
_sdالمحجوزة وبالتحقق المتساهل من_sd_alg، وقد يعالجهما المشرفون في التصحيح نفسه.
ما لا نعرفه بعد
- أي إصدار من
@sd-jwt/decodeتعتمد عليه@sd-jwt/core0.20.0، وهل يتضمن منطق فك الترميز المُصحَّح. - ما الذي ينبغي أن تفعله الفرق التي تستخدم
@sd-jwt/decodeالمستقلة. فلا يوجد إصدار مُصحِّح مُدرج. - هل يرفض الإصدار 0.20.0 مدخلَي التلاعب كليهما من نقطتي الدخول العامتين
verify()وdecodeSdJwt(). فالأدلة لا تقتبس إلا الإصلاح المقترح، لا الإصلاح الذي صدر فعلًا. - سبب اختلاف التواريخ. إذ تُظهر النشرة الأمنية للمستودع النشر في 2 يوليو 2026، بينما نشر NVD في 29 أكتوبر 2026 وقاعدة بيانات GitHub للنشرات الأمنية في 2 نوفمبر 2026.
- أي المحافظ والمُتحقِّقات اللاحقة تُضمِّن هذه الحزم مع سياسات السماح الافتراضي.
سيتابع فريق التحرير صدور إصدار مُصحِّح من @sd-jwt/decode وأي تحديث للنشرة الأمنية.
المصادر
أحدث التحليلات الهندسية
الممارسة الهندسيةقراءة 1 دقيقة
Laravel 13.40.0: تغييرات في المعاملات والنسخ المتماثلة وRedis ينبغي اختبارها أولًا
إصدار فرعي يغيّر سلوك تطبيقات Laravel كثيفة الاعتماد على قواعد البيانات عند الأعطال، إضافةً إلى إصلاح ذي طابع أمني في Concurrency صدر دون أي نشرة أمنية.
قراءة 2 دقيقة
Stanza 1.14.0 يعيد فتح ثغرة تنفيذ تعليمات برمجية عبر pickle لا يستطيع weights_only=True منعها
الترقية إلى ما بعد Stanza 1.12.2 أغلقت مسارًا واحدًا لتنفيذ التعليمات البرمجية عبر pickle، لكن 1.14.0 أضاف مسارًا ثانيًا في lemmatizer لا يراه weights_only=True، ويُصلحه الإصدار 1.15.0.
قراءة 2 دقيقة
ثغرة CVE-2026-119084 في Amazon Redshift JDBC Driver: تحميل غير آمن للأصناف، والتصحيح في الإصدار 2.3.0
أصلحت AWS ثغرة انعكاس غير آمن (unsafe reflection) في Amazon Redshift JDBC Driver ضمن الإصدار 2.3.0. لكن درجة التعرض تتوقف على من يتحكم في سلاسل الاتصال لديك، وعلى ما يوجد في مسار الأصناف (classpath).