
إضافة WordPress تكشف عن خادم MCP عبر REST API، مع نموذج الأمان كنقطة محورية -- تسدّ ثغرة تجاوز OAuth من نوع CVE-2026-15015 من خلال عدم وجود هذا السطح أصلاً.
بعبارات بسيطة: هذا يتيح لوكيل ذكاء اصطناعي قراءة موقع ووردبريس والكتابة إليه بشكل محدود عبر كلمة مرور تطبيق ووردبريس حقيقية — نفس نوع بيانات الاعتماد التي يصدرها wp-admin بالفعل — ولا شيء غير ذلك. لا يوجد نموذج تسجيل، ولا تسجيل عميل OAuth، ولا نقطة نهاية لإصدار الرموز من أي نوع هنا. إضافة في هذا المجال نفسه أصدرت بالضبط ذلك، وهكذا خرج مهاجم غير مُصادَق بصلاحيات مسؤول كاملة.
إضافة ووردبريس تُعرّض خادم MCP عبر REST API، مع نموذج الأمان كجوهر الموضوع، لا كبند تسويقي.
CVE-2026-15015، بدرجة CVSS 9.8: عرّض MountDev AI MCP Connector for WordPress نقطة نهاية OAuth 2.1 Dynamic Client Registration متاحة للعموم إلى جانب نقطة نهاية تفويض لا تتحقق أيضًا من هوية الطالب. يسجّل المهاجم عميل OAuth الخاص به — دون الحاجة إلى أي بيانات اعتماد، فنقطة النهاية غير مُصادَقة بحكم التصميم، وهذا ما يعنيه "التسجيل الديناميكي للعملاء" — ويمرّره عبر نقطة نهاية التفويض دون إشراف، ويحصل على رمز حامل مرتبط بحساب مسؤول. سيطرة كاملة على الموقع: المحتوى، المستخدمون، الإعدادات. لم يكن هناك أي خطأ في التهيئة؛ فالتدفق عمل تمامًا كما يُفترض أن يعمل تسجيل عميل OAuth. لكن ما كان ينبغي أبدًا أن يكون قابلًا للوصول دون وجود مسؤول مسبقًا للموافقة عليه.
الإصلاح الذي يمكن تعميمه ليس "تنفيذ OAuth بعناية أكبر". بل هو عدم وجود هذا السطح أصلًا. هذه الإضافة لا تسجّل أي نقطة نهاية لتسجيل العملاء، ولا نقطة نهاية تفويض، ولا نقطة نهاية لإصدار الرموز من أي نوع. لا يوجد شيء يمكن للمهاجم التسجيل ضده، لأنه لا يوجد هنا ما يمنح بيانات الاعتماد. المصادقة هي كلمة مرور تطبيق ووردبريس أنشأها مسؤول بالفعل لمستخدم محدد من wp-admin — بيانات اعتماد موجودة فقط لأن إنسانًا يملك manage_options اختار إنشاءها، مسبقًا، خارج نطاق هذه الإضافة تمامًا.
لا واحدة منهما جديدة بمفردها؛ لكن تشغيل كلتيهما، عن قصد، بشكل مستقل عن الأخرى، هو ما يغلق ثغرة في أي منهما:
1. كلمة مرور التطبيق فقط — أبدًا جلسة كوكيز. دالة ووردبريس الخاصة is_user_logged_in() تكون صحيحة أيضًا لتبويب متصفح يحمل كوكيز وnonce صالحين. Auth::permission_callback() يرفض هذا المسار عن قصد: فهو يستمع إلى إجراء application_password_did_authenticate الذي يُطلقه نواة ووردبريس تحديدًا عند نجاح مصادقة كلمة مرور التطبيق عبر Basic-auth، ويشترط هذه العلامة، لا مجرد "وجود مستخدم مسجّل الدخول". عميل MCP ليس تبويب متصفح يحمل nonce؛ وقبول مصادقة الكوكيز هنا سيفتح مسارًا على شكل CSRF إلى سطح أدوات يمكن أن يُطلب منه، بالعربية، تعديل المحتوى، دون أي فائدة تُذكر مقارنة بالباب الواحد الذي تحتاجه هذه الإضافة فعليًا.
2. صلاحية لكل أداة، بالإضافة إلى بوابة كتابة لا تستطيع الصلاحية وحدها فتحها. ToolRegistry::call() يتحقق من user_can($user_id, $capability) من جديد عند كل استدعاء — edit_posts للمنشورات، manage_woocommerce للطلبات. وبشكل منفصل، أداة الكتابة الوحيدة، create_draft_post، تتطلب إضافيًا أن يكون مسؤول قد فعّلها من الإعدادات → WP Secure MCP، وهي معطّلة افتراضيًا. كلمة مرور تطبيق تحمل edit_posts لا تزال غير قادرة على كتابة أي شيء حتى يقوم إنسان بتبديل ذلك المفتاح — فحص الصلاحية وبوابة على مستوى الموقع هما قفلان مختلفان، والاختبارات تُثبت أن أياً منهما لا يُغني عن الآخر في أي من الاتجاهين.
limit، من 1 إلى 20) تحدّ من كمية ما يمكن أن يعيده استدعاء واحد؛ لكن استدعاء WP_Query أو wc_get_orders مكلف بشكل مشروع لا يزال يكلّف ما يكلّفه.كل مدخل tools/list لأي أداة يحمل تعليقات readOnlyHint / destructiveHint، حتى يستطيع العميل أن يقرر ما إذا كان سيسأل إنسانًا قبل الاستدعاء دون الحاجة إلى معرفة مسبقة بما تفعله.
composer install --no-dev
انسخ (أو أنشئ رابطًا رمزيًا لـ) مجلد الإضافة إلى wp-content/plugins/، فعّلها من wp-admin، ثم أنشئ كلمة مرور تطبيق للحساب الذي يجب أن يعمل الوكيل باسمه: المستخدمون → ملفك الشخصي → كلمات مرور التطبيق.
الكتابة معطّلة افتراضيًا. للسماح بـ create_draft_post: الإعدادات → WP Secure MCP → أدوات الكتابة. لا يزال فحص الصلاحية لكل أداة ساريًا فوق هذا — فتبديل المفتاح على مستوى الموقع لا يمنح أحدًا صلاحية لم يكن يملكها بالفعل.
55 اختبارًا، كلها نقية — بلا تثبيت ووردبريس، بلا قاعدة بيانات، بلا Docker. tests/bootstrap.php يستبدل دوال ووردبريس التي يستدعيها src/ (current_user_can، get_post، wp_insert_post، ...) بنفس الطريقة التي تُختبر بها إضافة ووردبريس عادةً اختبارًا وحديًا دون تحميل ووردبريس نفسه.
composer install
vendor/bin/phpunit --testdox
أثقل تغطية تقع حيث تهمّ أكثر:
ProtocolTest — 17 اختبارًا ضد سجل وهمي، لا شيء خاص بووردبريس على الإطلاق. التحقق من غلاف JSON-RPC، كل رمز خطأ، وقاعدة الإشعارات في JSON-RPC 2.0 التي تنص على أن الطلب الذي لا يحمل حقل id لا يحصل على رد، لأي طريقة، حتى المشوّهة — فلا يوجد مكان يذهب إليه الخطأ.ToolRegistryTest — يُثبت أن فحص الصلاحية وبوابة الكتابة مستقلان في كلا الاتجاهين: صلاحية مفقودة تحجب الاستدعاء حتى عند تفعيل الكتابة، وبوابة كتابة معطّلة تحجب الاستدعاء حتى عند وجود الصلاحية.AuthTest — الاختبار الأهم هنا هو test_logged_in_user_without_application_password_authentication_is_refused: مستخدم ووردبريس حقيقي ومُحلَّل لا يزال مرفوضًا، لأن جلسة الكوكيز لم تُطلب أبدًا.يشغّل CI اختبارات PHPUnit عبر PHP 8.1–8.3 وPHP_CodeSniffer مقابل مجموعة قواعد WordPress Coding Standards عند كل دفعة.
ما لا يشغّله CI بعد عند كل دفعة: اختبار تكامل حي مع WordPress + WooCommerce عبر @wordpress/env — المصادقة بكلمة مرور تطبيق حقيقية ضد REST API حقيقي وقاعدة بيانات حقيقية، المكافئ للنسخة الحية من مهمة CI الخاصة بـ pg-readonly-mcp مع Postgres حقيقي. إنه مكتوب ومُدروس، وموصول كمهمة workflow_dispatch في ci.yml بدلًا من بوابة مطلوبة، لأن بيئة التطوير الخاصة بهذا المستودع واجهت فشلًا محليًا في Docker Desktop جعل من المستحيل تجربة wp-env قبل التشغيل الحقيقي الأول لهذا الـ workflow. ذُكر هذا هنا بدلًا من تركه ليكتشفه شخص آخر — نفس القاعدة التي يطبّقها pg-readonly-mcp على فجوة الفرق في المحلّل غير المُختبر لديه.
wp-secure-mcp.php تهيئة الإضافة: تحميل المحمّل التلقائي، وربط مسار REST واحد
src/
Protocol.php إرسال JSON-RPC 2.0. صفر استدعاءات لووردبريس. نقي.
ToolRegistryInterface.php الوصلة التي يتحدث إليها Protocol بدلًا من ووردبريس مباشرة
ToolRegistry.php القفلان: صلاحية لكل أداة، وبوابة كتابة على مستوى الخادم
Auth.php permission_callback لكلمة مرور التطبيق فقط
Settings.php مفتاح المسؤول لبوابة الكتابة
RestController.php تهيئة رفيعة: مسار واحد، يفوّض فورًا
Tools/ أدوات v1 الأربع
tests/
bootstrap.php بدائل دوال ووردبريس
ProtocolTest.php, ToolRegistryTest.php, AuthTest.php, ...
bin/smoke-test.sh سكربت تكامل wp-env الحي (انظر الاختبارات، أعلاه)
إذا قُبل طلب أو رُفض يومًا لسبب يعيش في wp-secure-mcp.php أو RestController.php بدلًا من Auth أو ToolRegistry أو Protocol، فهذا خطأ — فالحكم ينتمي إلى طبقة أدنى، حيث يمكن اختباره بمصفوفة عادية ولا شيء غيرها.
MIT.
| الأداة | الصلاحية | للقراءة فقط | ملاحظات |
|---|
search_posts | edit_posts | نعم | بحث بالكلمات المفتاحية. مثبّت على post_status=publish — لا يعيد أبدًا المسودات أو المنشورات الخاصة، حتى لمن يستطيع رؤيتها في wp-admin. |
get_post | edit_posts | نعم | الجلب بواسطة المعرّف. يرفض منشورًا حقيقيًا بمعرّف حقيقي إذا لم تكن حالته publish — فالمعرّف ليس تجاوزًا لنفس القاعدة التي يفرضها search_posts. |
list_recent_orders | manage_woocommerce | نعم | الحالة، الإجمالي، العملة، التاريخ فقط. أبدًا اسم العميل أو بريده الإلكتروني أو عنوانه — فرؤية الطلبات لا تتطلب تسليم الوكيل بيانات العميل الشخصية، سواء وُجد فحص صلاحية أم لا. |
create_draft_post | edit_posts | لا | post_status هو 'draft' حرفيًا في المصدر، وليس وسيطًا. لا تستطيع هذه الأداة النشر تحت أي مدخل، حتى مع تفعيل الكتابة. معطّلة على مستوى الموقع حتى يفعّلها مسؤول. |