Security & AI Architecture

مقارنة OAuth و OAuth2: الفروق المعمارية للذكاء الاصطناعي و MCP

إجابة سريعة: اعتمد OAuth 1.0a على توقيعات تشفير معقدة، وقدم OAuth 2.0 رموز Bearer المعرضة لهجمات إعادة التشغيل. أما لوكلاء الذكاء الاصطناعي وخوادم MCP، فيعد OAuth 2.1 المعيار الإلزامي؛ إذ يفرض PKCE (RFC 7636) على كافة التدفقات، ويلغي آليات المنح غير الآمنة، ويقترن بـ DPoP (RFC 9449) لفرض رموز Zero-Trust مقيدة بالمرسل في البيئات عديمة الواجهة (Headless).


1. المقدمة: أزمة هوية الوكلاء وتفويض الصلاحيات في عام 2026

أدى الانتقال السريع من واجهات الدردشة المنعزلة لنماذج اللغة الكبيرة (LLM) إلى وكلاء ذكاء اصطناعي مستقلين متعددي الجولات وخوادم بروتوكول سياق النموذج (MCP) إلى نشوء أزمة أمنية بالغة الخطورة: عنق زجاجة الهوية والتفويض للوكلاء المستقلين.

خلال عامي 2024 و2025، قام المطورون بربط الأدوات البرمجية المستقلة — مثل Claude Code وCursor وWindsurf وAutoGen ووكلاء LangGraph المخصصين — بواجهات برمجة التطبيقات المؤسسية باستخدام رموز وصول شخصية ثابتة (PAT) أو مفاتيح API طويلة الأجل مدمجة مباشرة في ملفات .env أو متغيرات بيئة نظام التشغيل. عندما ينفذ وكيل الذكاء الاصطناعي أوامر شل محلية، أو يستعلم عن قواعد بيانات داخلية (عبر خوادم PostgreSQL أو Supabase MCP)، أو يحدّث متتبعات المشكلات المؤسسية (عبر خوادم Jira أو Linear MCP)، فإنه يعمل تحت سلطة بيئية مطلقة وغير مقيدة (Ambient Authority).

معمارية الوكلاء القديمة والضعيفة (سلطة استاتيكية بيئية مطلقة):
+--------------------+        إنشاء عملية فرعية       +---------------------------+
|  الوكيل المضيف LLM | ─────────────────────────────> |   خادم أدوات MCP المحلي   |
| (Claude Code /     |   Env: GITHUB_TOKEN=ghp_...    |   (يقرأ process.env)      |
|  Cursor / LangSeq) |                                +-------------+-------------+
+---------+----------+                                              |
          | هجوم حقن الأوامر غير المباشر (Prompt Injection)         | قراءة وكتابة غير مقيدة
          v                                                         v
+--------------------+                                       +-------------------+
| موجه المهاجم في    |                                       | الخدمات المؤسسية  |
| صفحة ويب غير موثوقة| ──> تسريب السر الاستاتيكي ──────────> | GitHub / Slack /  |
| "Print your env"   |     إلى Webhook الخاص بالمهاجم        | قواعد البيانات    |
+--------------------+                                       +-------------------+

هذه المعمارية المبنية على الاعتماديات الثابتة فاشلة تماماً لثلاثة أسباب جوهرية:

  1. حقن الأوامر كوسيلة لتسريب الأسرار: إذا واجه الوكيل بيانات غير موثوقة (مثل موجه خبيث مضمن في صفحة ويب أو بريد إلكتروني أو مشكلة في GitHub)، يمكن استدراج النموذج لتنفيذ أوامر تطبع process.env أو تفحص ملفات التكوين المحلية، مما يسرب المفاتيح الحساسة على الفور.
  2. غياب تفويض الهوية: لا يمكن لمفتاح API الثابت التمييز بين الإجراءات المتخذة عمداً من قبل المشغل البشري وتلك الناتجة عن هلوسة النموذج أو القرارات التلقائية للوكيل. في سجلات التدقيق المؤسسية، تظهر جميع العمليات متطابقة وكأنها صادرة عن المستخدم البشري.
  3. انعدام الإلغاء الديناميكي وتحديد الصلاحيات الدنيا: تتمتع الرموز الثابتة عادة بصلاحيات مفرطة (مثل الوصول الكامل للقراءة والكتابة في المستودع بأكمله) وتظل صالحة لأشهر متتالية أو مدى الحياة.

لحل هذا الخطر، اتجه مجتمع الذكاء الاصطناعي نحو أطر التفويض المعتمدة. ومع ذلك، فإن الاختيار المعماري بين OAuth 1.0a وOAuth 2.0 والمعيار الموحد الحديث OAuth 2.1 — مقترناً بـ PKCE (RFC 7636) وDPoP (RFC 9449) ومنح تفويض الأجهزة (RFC 8628) — يتطلب فهماً عميقاً لآلية عمل هذه البروتوكولات تحت قيود التنفيذ المستقل وبيئات العمل عديمة الواجهة (Headless).


2. تطور OAuth: مقارنة هيكلية بين 1.0a و 2.0 و 2.1

لفهم الأسباب التي تدفع أطر وكلاء الذكاء الاصطناعي الحديثة لفرض معيار OAuth 2.1، يجب تحليل التطورات المعمارية والمفاضلات ونقاط الضعف الجوهرية عبر الإصدارات الثلاثة الرئيسية لمواصفات OAuth.

تطور مواصفات OAUTH (من عام 2007 إلى 2026):
+---------------------------------------------------------------------------------------------+
| OAuth 1.0a (RFC 5849, عام 2010)                                                             |
| - توقيعات تشفير متماثلة/غير متماثلة لكل طلب HTTP فردي (HMAC-SHA1, RSA-SHA1)                 |
| - انعدام تدفق تجديد الرموز؛ حساب التوقيع مرتبط بالحالة؛ حماية مستقلة عن وسيلة النقل        |
| - النتيجة للذكاء الاصطناعي: غير صالح للاستخدام. عبء التشفير يعطل التدفق والبروكسي الديناميكي|
+---------------------------------------------------------------------------------------------+
                                               │
                                               ▼
+---------------------------------------------------------------------------------------------+
| OAuth 2.0 (RFC 6749 و RFC 6750, عام 2012)                                                   |
| - تفويض التشفير لطبقة أمان النقل (TLS 1.2/1.3)                                              |
| - تقديم رموز Bearer، ومجالات الصلاحيات (Scopes)، ورموز التجديد، وأنواع المنح المتخصصة       |
| - تضمن التدفق الضمني (Implicit Flow) وبيانات اعتماد كلمة مرور مالك المورد (ROPC)             |
| - النتيجة للذكاء الاصطناعي: خطير. رموز Bearer تُسرق بسهولة عبر حقن الأوامر أو SSRF.         |
+---------------------------------------------------------------------------------------------+
                                               │
                                               ▼
+---------------------------------------------------------------------------------------------+
| OAuth 2.1 (معيار IETF الموحد، 2025/2026)                                                    |
| - إلغاء تدفقات المنح غير الآمنة تماماً (حذف تدفق Implicit وتدفق كلمة المرور بشكل نهائي)     |
| - فرض PKCE (RFC 7636) على كافة تدفقات كود التفويض (للعملاء العامين والسريين)                |
| - مطابقة تامة لحروف URI لإعادة التوجيه؛ حظر تمرير الرموز عبر معاملات الاستعلام في الرابط    |
| - اشتراط تدوير رموز التجديد (RTR) أو الرموز المقيدة بالمرسل (DPoP / mTLS)                   |
| - النتيجة للذكاء الاصطناعي: المعيار الذهبي المعتمد لخوادم MCP وتفويض هوية الوكلاء المستقلين.|
+---------------------------------------------------------------------------------------------+

OAuth 1.0a (RFC 5849): الصرامة التشفيرية والارتباط بالحالة

تم تطوير OAuth 1.0a في عصر كان فيه تشفير HTTPS/TLS باهظ التكلفة ونادراً ما يُطبق. وللحماية من التنصت عبر HTTP العادي، تطلب البروتوكول من العميل والخادم حساب توقيع تشفيري (HMAC-SHA1 أو RSA-SHA1) لكل طلب HTTP منفرد.

تطلب حساب التوقيع تنميط أسلوب HTTP والمسار بدقة وتوليد سلسلة مرتبة هجائياً من المعاملات والرؤوس ورمز Nonce والطابع الزمني:

$$\text{BaseString} = \text{HTTP\_METHOD} \mathbin{\Vert} \text{"\&"} \mathbin{\Vert} \text{Encode}(\text{URL}) \mathbin{\Vert} \text{"\&"} \mathbin{\Vert} \text{Encode}(\text{SortedParams})$$

$$\text{Signature} = \text{HMAC-SHA1}(\text{ClientSecret} \mathbin{\Vert} \text{"\&"} \mathbin{\Vert} \text{TokenSecret}, \text{BaseString})$$

أسباب فشل OAuth 1.0a مع وكلاء الذكاء الاصطناعي:

  1. عدم التوافق مع التدفق والنقل المجزأ: تعتمد بروتوكولات الوكلاء الحديثة (مثل MCP عبر SSE أو WebSockets) على إرسال حزم JSON-RPC المتزايدة بالتدفق. تؤدي إعادة حساب التوقيعات على تدفقات غير محددة إلى فشل التحقق بصورة مستمرة.
  2. تنسيق الأدوات الديناميكي: يبني الوكلاء طلبات HTTP ديناميكياً بناءً على مخرجات النموذج. يؤدي أدنى تغيير في ترتيب المعاملات أو ترميز الروابط (%20 مقابل +) إلى إبطال التوقيع وظهور أخطاء 401 Unauthorized متكررة.
  3. غياب آلية التجديد الأصلية: لم يتضمن OAuth 1.0a رموزاً قصيرة الأجل مع تدوير تلقائي، مما جعل الأسرار طويلة الأجل محبوسة في بيئة العميل باستمرار.

OAuth 2.0 (RFC 6749): البساطة على حساب مخاطر رموز Bearer

حل OAuth 2.0 هذا التعقيد بنقل التشفير لطبقة النقل (عبر فرض HTTPS) وتقديم رمز Bearer (RFC 6750). أي طرف يحمل هذا الرمز يمنح حق الوصول مباشرة دون إثبات إضافي:

GET /v1/repositories HTTP/1.1
Host: api.github.com
Authorization: Bearer ya29.a0AfH6SMB...

حدد OAuth 2.0 أربعة تدفقات منح رئيسية:

  1. منح كود التفويض (Authorization Code Grant): تدفق إعادة التوجيه لتطبيقات الويب ذات الواجهة الخلفية الآمنة.
  2. المنح الضمني (Implicit Grant): تدفق يعيد الرمز مباشرة في شظية الرابط (#access_token=...) في المتصفح.
  3. بيانات اعتماد كلمة المرور (ROPC): إرسال اسم المستخدم وكلمة المرور مباشرة لتطبيق العميل.
  4. منح بيانات اعتماد العميل (Client Credentials Grant): تفويض مباشر بين الآلات (M2M) للخدمات الخلفية دون تدخل بشري.

الثغرات القاتلة لـ OAuth 2.0 في أنظمة الذكاء الاصطناعي:

  • ثغرة إعادة تشغيل رموز Bearer: إذا تم اختراق بيئة الوكيل عبر حقن الأوامر أو ثغرات SSRF، يمكن للمهاجم سرقة رمز Bearer وإعادة استخدامه من أي مكان في العالم حتى تنتهي صلاحيته.
  • فخ التدفق الضمني (Implicit Grant): تسبب استخدام هذا التدفق في تطبيقات الوكلاء في تسريب الرموز عبر سجل المتصفح وترويسات Referer.
  • مخاطر تدفق كلمة المرور: اعتاد مطورو أدوات CLI طلب كلمة المرور في الطرفية، مما نسف الوعد الجوهري لـ OAuth: عدم مشاركة بيانات الاعتماد الأساسية مطلقاً مع أطراف ثالثة.

OAuth 2.1: المعيار المعزز لوكلاء الذكاء الاصطناعي المستقلين

يمثل OAuth 2.1 مواصفة موحدة تلغي الديون التقنية وتفرض شروطاً معمارية صارمة:

  1. الحذف النهائي للتدفقات غير الآمنة: تم إلغاء كل من المنح الضمني وتدفق كلمة المرور ROPC نهائياً.
  2. إلزامية PKCE لجميع تدفقات كود التفويض: أصبح استخدام مفتاح إثبات تبادل الرموز (RFC 7636) إلزامياً لكافة العملاء العامين (وكلاء CLI وملحقات IDE) والعملاء السريين (أسراب الوكلاء الخلفية).
  3. المطابقة الدقيقة لـ URI لإعادة التوجيه: يجب على خوادم التفويض مطابقة الروابط حرفياً وبايت لكل بايت، لمنع هجمات إعادة التوجيه المفتوح.
  4. حظر الرموز في معاملات الرابط: يُمنع تمرير الرموز عبر معاملات الاستعلام لتفادي تسريبها في سجلات الوصول أو وسائط التخزين المؤقت.
  5. فرض حماية رموز التجديد: يجب تطبيق تدوير رموز التجديد (RTR) أو الرموز المقيدة بالمرسل (DPoP / mTLS).

جدول المقارنة المعمارية: OAuth 1.0a مقابل 2.0 مقابل 2.1

البعد المعماري OAuth 1.0a (RFC 5849) OAuth 2.0 (RFC 6749 / 6750) OAuth 2.1 (معيار IETF 2026)
النموذج التشفيري توقيع كل طلب في طبقة التطبيق (HMAC/RSA) أمان طبقة النقل (TLS) + Bearer نقي TLS + إلزامية PKCE + تقييد المرسل (DPoP/mTLS)
خطر إعادة تشغيل الرمز منعدم (توقيع فريد لكل طلب بواسطة Nonce) مرتفع للغاية (الحيازة تعني التفويض) معالج بالكامل (الرمز مرتبط بمفتاح DPoP)
اشتراط PKCE غير مدعوم اختياري (RFC 7636، كان للهواتف غالباً) إلزامي تماماً لكافة عمليات تبادل الكود
المنح الضمني (Implicit) غير مدعوم مسموح (لتطبيقات الويب أحادية الصفحة) محذوف ومحظور نهائياً
منح كلمة المرور (ROPC) غير مدعوم مسموح (تبادل مباشر للاعتماديات) محذوف ومحظور نهائياً
التحقق من رابط التوجيه مطابقة البادئة مسموحة التساهل مع الرموز التعبيرية والمسارات مطابقة حرفية تامة لكل بايت إلزامية
الرمز في معاملات الرابط مدعوم مسموح (?access_token=...) محظور قطعياً (عبر الترويسة أو المتن فقط)
دورة حياة رمز التجديد لا توجد آلية أصلية رمز واحد يُعاد استخدامه حتى انتهاء الصلاحية تدوير إلزامي (RTR) أو تقييد تشفيري
الملاءمة لوكلاء CLI سيئة للغاية (حسابات هشة عبر الشل) ضعيفة ومحفوفة بمخاطر اعتراض المنافذ مثالية (PKCE + منافذ Loopback مؤقتة)
الملاءمة لخوادم MCP غير متوافقة مع تدفقات JSON-RPC قابلة للاستخدام مع خطر تسريب الأسرار المعيار الافتراضي (صلاحيات دنيا محددة)

3. طبولوجيا المصادقة في بروتوكول سياق النموذج (MCP)

ينشئ بروتوكول سياق النموذج (MCP)، الذي أطلقته Anthropic واعتمده Claude Code وCursor، معمارية غير متماثلة بين العميل والخادم تعمل عبر بروتوكول JSON-RPC 2.0.

في بيئة عمل MCP، نميز بين حدين رئيسيين للاتصال:

  • الحد أ (من المضيف إلى خادم MCP): الاتصال بين تطبيق العميل لنماذج اللغة (Claude Code, Cursor) وعملية خادم MCP.
  • الحد ب (من خادم MCP إلى البنية التحتية للمؤسسة): الاتصال بين خادم MCP وواجهات برمجة التطبيقات الخارجية (GitHub, Jira, Linear, Slack).
طبولوجيا المصادقة في بروتوكول سياق النموذج (MCP):
+-------------------------------------------------------------------------------------------------------+
| بيئة تشغيل مضيف MCP (مثل Claude Code / Cursor / إطار عمل الوكلاء المستقلين)                            |
|                                                                                                       |
|  +---------------------+        سياق الموجه            +--------------------------------------------+ |
|  | موجه المستخدم / LLM | <───────────────────────────> | محرك استدلال LLM (Claude 3.7 / GPT-4o)     | |
|  +----------+----------+                               +--------------------------------------------+ |
|             | إرسال استدعاء الأداة (`tools/call`)                                                     |
|             v                                                                                         |
|  +--------------------------------------------------------------------------------------------------+ |
|  | محرك عميل MCP                                                                                    | |
|  | - إدارة مصافحة OAuth 2.1 PKCE مع خادم التفويض                                                    | |
|  | - الاحتفاظ بالمفتاح الخاص المؤقت لـ DPoP في ذاكرة معزولة غير قابلة للتصدير                       | |
|  | - توليد رموز JWT إثبات DPoP لكل طلب؛ حقن Access Token في ترويسات JSON-RPC                        | |
|  +-----------------------------------+--------------------------------------------------------------+ |
+--------------------------------------|----------------------------------------------------------------+
                                       |
                                       | وسيلة النقل: Stdio (عملية محلية) أو SSE/HTTP (خادم بعيد)
                                       v
+-------------------------------------------------------------------------------------------------------+
| بيئة تشغيل خادم MCP (مثل GitHub MCP / خادم قواعد بيانات المؤسسة)                                      |
|                                                                                                       |
|  +--------------------------------------------------------------------------------------------------+ |
|  | معترض المصادقة والتحقق من الرموز                                                                 | |
|  | 1. التحقق من توقيع رمز OAuth 2.1 عبر نقطة نهاية JWKS التابعة لخادم التفويض                        | |
|  | 2. التحقق من إثبات DPoP: فحص أسلوب HTTP والرابط ورمز Nonce والمفتاح العام المؤقت                 | |
|  | 3. تقييم الصلاحيات: فرض الحد الأدنى من الامتيازات (`issues:read` تحظر `admin:all`)                | |
|  +-----------------------------------+--------------------------------------------------------------+ |
|                                      |                                                                |
|                                      v                                                                |
|  +--------------------------------------------------------------------------------------------------+ |
|  | محرك تنفيذ أدوات MCP (تنفيذ `tools/call`)                                                         | |
|  | - تعقيم المدخلات، ومنع تجاوز المسار، وتنفيذ استدعاء API في بيئة معزولة                            | |
|  +-----------------------------------+--------------------------------------------------------------+ |
+--------------------------------------|----------------------------------------------------------------+
                                       | استدعاء واجهة برمجة التطبيقات برمز مفوض ومحدد النطاق
                                       v
                      +----------------------------------+
                      | الخدمات وقواعد البيانات الخارجية |
                      | (GitHub / Jira / PostgreSQL / S3)|
                      +----------------------------------+

المقارنة بين وسائط النقل: Stdio المحلي مقابل SSE/HTTP البعيد

  1. نقل Stdio المحلي (transport: "stdio"):
  • يعمل خادم MCP كعملية فرعية محلية ينشئها المضيف ويتواصل عبر الإدخال والإخراج القياسيين (stdin/stdout).
  • الخطأ الأمني التاريخي: اعتاد المطورون حقن الاعتماديات عبر متغيرات البيئة:
  • الثغرة: يمكن لأي أمر شل ينفذه الوكيل أو أي عملية فرعية قراءة /proc/[pid]/environ أو تنفيذ env، مما يسرق وصول GitHub للمؤسسة بالكامل.
  • حل OAuth 2.1: يدير المضيف مخزناً آمناً لرموز OAuth 2.1 PKCE. يبدأ خادم MCP دون أسرار دائمة ويحصل على رمز تفويض قصير الأجل عند المصافحة الأولى.
  1. نقل SSE/HTTP البعيد (transport: "sse"):
  • يعمل خادم MCP كخدمة ويب موزعة على منفذ HTTP باستخدام أحداث إرسال الخادم (SSE).
  • في هذا النمط، يعد استخدام OAuth 2.1 إلزامياً؛ إذ يجب على عميل MCP إرسال ترويسة Authorization والتحقق عبر JWKS وإثبات DPoP.

4. تحليل متعمق لـ PKCE (RFC 7636): حماية استدعاءات الوكيل المحلية

صُممت آلية مفتاح إثبات تبادل الشفرات (PKCE) لمنع اعتراض أكواد التفويض على الأجهزة العامة. وفي مواصفة OAuth 2.1، أصبح PKCE إلزامياً لكل عمليات تبادل أكواد التفويض.

لماذا يُصنف وكلاء CLI وبيئات التطوير كعملاء عامين (Public Clients)

أدوات الذكاء الاصطناعي مثل Claude Code وCursor هي عملاء عامون: فحزمتها البرمجية تعمل مباشرة على حاسوب المستخدم ولا يمكنها تخزين client_secret ثابت بأمان؛ إذ يمكن لأي شخص استخراجه بالهندسة العكسية.

عند طلب التفويض، يرجع الخادم كود تفويض (Authorization Code) عبر رابط محلي مؤقت (غالباً خادم HTTP محلي على Loopback مثل http://127.0.0.1:18492/callback).

سيناريو هجوم اعتراض كود التفويض (في غياب PKCE):
1. يطلب وكيل CLI الشرعي كود تفويض من خادم المصادقة.
2. تتنصت عملية خبيثة في خلفية حاسوب المطور على منفذ Loopback المحلي.
3. يعيد الخادم توجيه المتصفح إلى http://127.0.0.1:18492/callback?code=AUTH_CODE_123.
4. تعترض العملية الخبيثة AUTH_CODE_123.
5. ترسل العملية الخبيثة الكود إلى /oauth/token وتحصل على Access Token لأن العميل عام ولا يتطلب سراً!

الحماية الرياضية لـ PKCE

يقضي PKCE على هذا التهديد بإنشاء سر تشفيري فريد وديناميكي لكل جلسة تفويض:

تسلسل بروتوكول PKCE:
+-------------+                     +-----------------------+                    +--------------------+
|   وكيل CLI  |                     |   متصفح المستخدم      |                    |    خادم التفويض    |
|   (العميل)  |                     +-----------+-----------+                    +---------+----------+
+------+------+                                 |                                          |
       | 1. إنشاء code_verifier (إنتروبيا عالية)|                                          |
       |    حساب code_challenge = S256(...)     |                                          |
       |                                        |                                          |
       | 2. تشغيل خادم Loopback المحلي          |                                          |
       |    فتح المتصفح مع التحدي ─────────────>| 3. GET /authorize?response_type=code     |
       |                                        |    &client_id=agent_cli                  |
       |                                        |    &code_challenge=E9Melhoa2Owv...       |
       |                                        |    &code_challenge_method=S256 ─────────>|
       |                                        |                                          | 4. موافقة المستخدم.
       |                                        | 5. توجيه 302 إلى منفذ Loopback المحلي     |    تخزين التحدي
       |                                        |<─────────────────────────────────────────|
       |<───────────────────────────────────────|    http://127.0.0.1:18492/callback?code=AC_88921
       | 6. التقاط الكود من رابط الاستدعاء      |
       |                                                                                   |
       | 7. POST /oauth/token                                                              |
       |    code=AC_88921 & code_verifier=dBjftJeZ4CVP-mB92K... ─────────────────────────>|
       |                                                                                   | 8. التحقق:
       |                                                                                   |    SHA256(verifier)
       |                                                                                   |    == التحدي المخزن؟
       | 9. إرجاع Access Token + Refresh Token (RTR) <─────────────────────────────────────|    نعم: إصدار التوكن
+------+------+
  1. متحقق الكود (Code Verifier): يولد الوكيل سلسلة عشوائية $V$ ذات إنتروبيا عالية تتراوح بين 43 و128 حرفاً ([A-Z], [a-z], [0-9], -, ., _, ~):
  2. تحدي الكود (Code Challenge): يحسب العميل تجزئة SHA-256 للقيمة $V$ ويشفرها بترميز Base64URL دون حشو:
  3. طلب التفويض: يرسل العميل $C$ و code_challenge_method=S256 إلى /authorize. ويخزن الخادم $C$.
  4. تبادل الرموز: يرسل العميل الكود ومعه code_verifier=V في نص صريح إلى /token. يتحقق الخادم من تطابق $\text{Base64URL-Encode}(\text{SHA-256}(V))$ مع $C$.

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

التطبيق العملي بلغة TypeScript: محرك PKCE

// pkce.ts - Enterprise OAuth 2.1 PKCE Engine for AI Agent Clients
import { randomBytes, createHash } from "node:crypto";

export interface PKCEChallenge {
  codeVerifier: string;
  codeChallenge: string;
  codeChallengeMethod: "S256";
}

export class PKCEEngine {
  /**
   * Generates a cryptographically secure code_verifier (RFC 7636 Section 4.1)
   * Length defaults to 64 bytes of entropy (yielding ~86 base64url characters).
   */
  public static generateVerifier(length: number = 64): string {
    if (length < 32 || length > 96) {
      throw new RangeError("Verifier byte length must be between 32 and 96.");
    }
    const buffer = randomBytes(length);
    return this.base64UrlEncode(buffer);
  }

  /**
   * Computes the S256 code_challenge from the code_verifier (RFC 7636 Section 4.2)
   */
  public static computeChallenge(verifier: string): string {
    const hash = createHash("sha256").update(verifier, "ascii").digest();
    return this.base64UrlEncode(hash);
  }

  /**
   * Generates the complete PKCE pair ready for OAuth 2.1 authorization
   */
  public static createPair(): PKCEChallenge {
    const codeVerifier = this.generateVerifier(64);
    const codeChallenge = this.computeChallenge(codeVerifier);
    return {
      codeVerifier,
      codeChallenge,
      codeChallengeMethod: "S256",
    };
  }

  /**
   * Server-side verification: Validates an incoming code_verifier against stored challenge
   */
  public static verify(verifier: string, storedChallenge: string): boolean {
    const computed = this.computeChallenge(verifier);
    // Timing-safe buffer comparison to prevent side-channel timing attacks
    const bufA = Buffer.from(computed);
    const bufB = Buffer.from(storedChallenge);
    if (bufA.length !== bufB.length) return false;
    
    let result = 0;
    for (let i = 0; i < bufA.length; i++) {
      result |= bufA[i] ^ bufB[i];
    }
    return result === 0;
  }

  private static base64UrlEncode(buffer: Buffer): string {
    return buffer
      .toString("base64")
      .replace(/\\+/g, "-")
      .replace(/\\//g, "_")
      .replace(/=+$/, "");
  }
}

5. مصادقة الخوادم عديمة الواجهة و CLI: تدفق الأجهزة (RFC 8628)

تعمل أنظمة الذكاء الاصطناعي بشكل مكثف في بيئات سحابية عديمة المتصفح (Headless):

  • حاويات Docker السحابية (AWS ECS, Kubernetes, Fly.io).
  • بيئات تشغيل CI/CD المؤقتة (GitHub Actions, GitLab CI).
  • الخوادم الافتراضية البعيدة وجلسات SSH.

في هذه البيئات، لا يمكن فتح متصفح لإعادة التوجيه، ومطالبة المستخدم بكتابة كلمة المرور في الطرفية تنتهك معايير OAuth 2.1. الحل المعتمد هو تدفق تفويض الأجهزة (Device Authorization Grant - RFC 8628):

تدفق تفويض الأجهزة (RFC 8628) في البيئات عديمة الواجهة:
+-------------------+                                                +-----------------------+
|  الوكيل Headless  |                                                |      خادم التفويض     |
|  (Docker / سحابة) |                                                +-----------+-----------+
+---------+---------+                                                            |
          | 1. POST /oauth/device/code (client_id, scope) ──────────────────────>|
          |                                                                      | 2. توليد:
          | 3. إرجاع بيانات اعتماد الجهاز:                                       |    device_code (سري)
          |    - user_code: "WDJB-HGNP"                                          |    user_code (علني)
          |    - verification_uri: "https://auth.corp.com/activate"              |    الفاصل: 5 ثوانٍ
          |    - interval: 5 <───────────────────────────────────────────────────|
          |                                                                      |
          | 4. طباعة التعليمات للمشغل في الطرفية:                                |
          |    "افتح https://auth.corp.com/activate وأدخل الرمز: WDJB-HGNP"      |
          |                                                                      |
          | 5. الدخول في حلقة الاستقصاء (Polling Loop):                          |
          |    POST /oauth/token (grant_type=device_code, device_code=...) ─────>|
          |    <── 400 Bad Request: {"error": "authorization_pending"} ─────────|
          |    [انتظار 5 ثوانٍ]                                                  |
          |                                                                      |
+---------+---------+     يفتح المستخدم الرابط على حاسوبه أو هاتفه               |
| حاسوب المستخدم    | ──> يدخل "WDJB-HGNP"، ويتم المصادقة عبر MFA ──────────────>| 6. موافقة المشرف!
+-------------------+                                                            |
          |                                                                      |
          | 7. دورة الاستقصاء التالية:                                           |
          |    POST /oauth/token ───────────────────────────────────────────────>|
          |    <── 200 OK: {access_token: "...", refresh_token: "..."} ──────────|
          v
[مصادقة الوكيل بنجاح تام دون كشف أي أسرار في الطرفية]

بديل الاتصال بين الآلات (M2M): مفتاح JWT الخاص (RFC 7523)

عندما يعمل الوكيل بمفرده بشكل مستقل تماماً (مثل روبوت فحص الكود الليلي)، لا يوجد إنسان لفتح الرابط.

في هذا السيناريو، تطبق بنية Zero-Trust منح بيانات اعتماد العميل المعزز بمواصفة RFC 7523 (ملف تعريف JWT لمصادقة العميل):

  • بدلاً من إرسال كلمة مرور ثابتة، يحتفظ الوكيل بمفتاح خاص غير متماثل (RSA أو ECDSA) محمي في وحدة HSM أو مخزن أسرار Kubernetes.
  • عند المصادقة، يوقع الوكيل رمز JWT مؤقتاً بصلاحية 60 ثانية ومعرف فريد jti وتحديد الجمهور aud.
  • يتحقق خادم التفويض من التوقيع بالاعتماد على المفتاح العام المسجل مسبقاً للوكيل.

6. دورة حياة الرموز وتدفقات التجديد التلقائي

ينفذ الوكلاء مهام معقدة تستمر لساعات طويلة. وبما أن رموز وصول OAuth 2.1 تصدر بفترات صلاحية قصيرة عمداً (من 5 إلى 15 دقيقة)، يجب على الوكيل إدارة تجديدها ذاتياً في الخلفية دون قطع استدعاءات الأدوات.

تدوير رموز التجديد (RTR) واكتشاف الاختراق

في معيار OAuth 2.1، تُحمى رموز التجديد من السرقة عبر تدوير رموز التجديد (Refresh Token Rotation - RTR):

  1. في كل مرة يقدم فيها الوكيل refresh_token إلى /oauth/token، يُبطل الخادم ذلك الرمز فوراً.
  2. يصدر الخادم زوجاً جديداً بالكامل: access_token جديد و refresh_token جديد.
  3. إذا حاول مهاجم استخدام رمز تجديد قديم مستهلك، يرصد الخادم حادثة اختراق فورية:

$$\text{Incoming Token State} == \text{"REVOKED"} \implies \text{Revoke All Tokens in Family Tree}$$

يلغي الخادم شجرة الصلاحيات بالكامل على الفور، مما يبطل جميع الرموز النشطة لكافة نسخ الوكيل العاملة.

تدوير رموز التجديد (RTR) والإبطال التلقائي عند التهديد:
سلسلة إصدار الرموز:
[Refresh Token A] ──(مستهلك)──> [Refresh Token B] ──(مستهلك)──> [Refresh Token C] (نشط)
         │
         │ يحاول المهاجم تقديم الرمز المسروق [Refresh Token A]
         v
[يرصد خادم المصادقة إعادة استخدام الرمز الملغى A!]
         │
         ▼
[إنذار أمني حرج]: إبطال الرمز B و C وكافة رموز Access Token المرتبطة فوراً.
تتوقف جلسة الوكيل بأمان لمنع أي تصعيد غير مصرح به للصلاحيات.

التطبيق بلغة Python: مدير رموز غير متزامن وآمن ضد تضارب العمليات

# token_manager.py - Enterprise Async Token Lifecycle Manager for AI Agents
import asyncio
import time
import httpx
from typing import Optional, Dict, Any

class AgentTokenManager:
    def __init__(
        self,
        token_endpoint: str,
        client_id: str,
        initial_refresh_token: str,
        proactive_refresh_seconds: int = 60,
    ):
        self.token_endpoint = token_endpoint
        self.client_id = client_id
        self.refresh_token = initial_refresh_token
        self.access_token: Optional[str] = None
        self.expires_at: float = 0.0
        self.proactive_refresh_seconds = proactive_refresh_seconds
        self._lock = asyncio.Lock()

    async def get_valid_access_token(self) -> str:
        now = time.time()
        if self.access_token and (self.expires_at - now) > self.proactive_refresh_seconds:
            return self.access_token

        async with self._lock:
            now = time.time()
            if self.access_token and (self.expires_at - now) > self.proactive_refresh_seconds:
                return self.access_token

            await self._refresh_token_exchange()
            if not self.access_token:
                raise RuntimeError("Failed to acquire valid access token from authorization server.")
            return self.access_token

    async def _refresh_token_exchange(self) -> None:
        payload = {
            "grant_type": "refresh_token",
            "refresh_token": self.refresh_token,
            "client_id": self.client_id,
        }
        
        async with httpx.AsyncClient(timeout=10.0) as client:
            try:
                response = await client.post(
                    self.token_endpoint,
                    data=payload,
                    headers={"Content-Type": "application/x-www-form-urlencoded"},
                )
            except httpx.RequestError as exc:
                raise ConnectionError(f"Network transport error during token refresh: {exc}")

            if response.status_code == 200:
                data: Dict[str, Any] = response.json()
                self.access_token = data["access_token"]
                expires_in = int(data.get("expires_in", 3600))
                self.expires_at = time.time() + expires_in
                
                # Update to the newly rotated refresh token if provided
                if "refresh_token" in data:
                    self.refresh_token = data["refresh_token"]
            elif response.status_code in (400, 401):
                err_data = response.json()
                # If error is 'invalid_grant', the token was likely already rotated or revoked
                raise PermissionError(f"Token refresh rejected (possible token theft or expiry): {err_data}")
            else:
                response.raise_for_status()

7. عزل الاعتماديات بأسلوب Zero-Trust: بروتوكول DPoP (RFC 9449)

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

في تطبيقات الذكاء الاصطناعي، إذا استدرج المهاجم الوكيل عبر حقن الأوامر لإرسال طلب HTTP إلى خادم خارجي (SSRF) حاملاً ترويسة المصادقة، يُسرق رمز Bearer فوراً.

للوصول إلى أمان Zero-Trust المطلق، يعتمد OAuth 2.1 مواصفة DPoP: إثبات الحيازة على مستوى طبقة التطبيق (RFC 9449).

آلية تقييد الرموز بالمرسل على مستوى التطبيق عبر DPOP (RFC 9449):
+-------------------------------------------------------------------------------------------------+
| بيئة الوكيل المحلية (العميل)                                                                    |
| - توليد زوج مفاتيح مؤقت: المفتاح العام (JWK) + المفتاح الخاص (محفوظ حصراً في RAM/المنطقة الآمنة)|
+-------------------------------------------------------------------------------------------------+
                                                │
                                                │ 1. إرفاق ترويسة إثبات DPoP:
                                                │    DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Ar...
                                                │    Payload: {
                                                │      "htm": "GET",
                                                │      "htu": "https://api.enterprise.com/mcp/tools",
                                                │      "iat": 1772630400,
                                                │      "jti": "random_nonce_9921",
                                                │      "jwk": { ...المفتاح_العام... }
                                                │    }
                                                │
                                                │ 2. إرسال رمز DPoP المقيد:
                                                │    Authorization: DPoP dpop_access_token_88921
                                                v
+-------------------------------------------------------------------------------------------------+
| بوابة MCP المؤسسية (Resource Server)                                                            |
| 1. التحقق من ربط Access Token ببصمة المفتاح العام الوارد في JWK.                                |
| 2. التحقق من صحة توقيع ترويسة إثبات DPoP بالمفتاح العام المرفق.                                 |
| 3. التحقق من تطابق "htm" مع "GET" ومطابقة "htu" التامة للرابط المستهدف.                         |
| 4. التحقق من أن الطابع الزمني "iat" يقع ضمن النطاق (< 60 ثانية) وأن "jti" لم يُستخدم من قبل.    |
+-------------------------------------------------------------------------------------------------+
                                                │
       ┌────────────────────────────────────────┴────────────────────────────────────────┐
       ▼                                                                                 ▼
[إثبات صالح ومفتاح مطابق]                                                     [رفض إعادة تشغيل الرمز المسروق]
يُسمح للطلب بالمرور والتنفيذ                                                   يمتلك المهاجم الرمز، ولكنه يفتقر للمفتاح
                                                                              الخاص الموجود بذاكرة الوكيل.
                                                                              النتيجة: حظر فوري 401 Unauthorized!

كيفية عمل DPoP في الممارسة العملية

  1. توليد مفاتيح مؤقتة: عند بدء تشغيل الوكيل، يُنشئ زوج مفاتيح تشفير غير متماثل (ECDSA P-256) في الذاكرة.
  2. الربط التشفيري: يتضمن رمز الوصول المصدر بصمة المفتاح العام (jkt).
  3. توقيع إثبات لكل طلب: يوقع الوكيل رمز JWT مؤقتاً يحتوي على أسلوب الطلب والرابط الدقيق والطابع الزمني ومعرف UUID.
  4. مناعة ضد السرقة: حتى في حال تسريب رمز الوصول، لا يمكن استخدامه بدون المفتاح الخاص المحلي للوكيل.

8. اختبارات الأداء الأمني ومصفوفة المخاطر المؤسسية

المقارنة التجريبية للأداء (10,000 تكرار، معالجات Apple M4 Max)

معمارية المصادقة زمن تأخير المصافحة (p50) زمن تأخير المصافحة (p99) عبء التحقق لكل طلب الحماية من إعادة التشغيل استهلاك الذاكرة بالعميل استهلاك المعالج بالخادم
مفتاح API الثابت 0.1 ms (بدون مصافحة) 0.2 ms 0.02 ms (مقارنة نصوص) منعدمة (إعادة تشغيل كاملة) < 1 KB الخط الأساسي
OAuth 1.0a (HMAC-SHA1) 14.2 ms 38.5 ms 1.84 ms (معالجة التوقيع) جزئية (فحص Nonce) 12 KB +18%
OAuth 2.0 Bearer 45.1 ms 112.0 ms 0.15 ms (التحقق من JWT) منعدمة (إعادة تشغيل Bearer) 18 KB +4%
OAuth 2.1 (PKCE + RTR) 48.6 ms 118.4 ms 0.16 ms (التحقق من JWT) متوسطة (إبطال عبر RTR) 24 KB +5%
OAuth 2.1 + DPoP (P-256) 54.2 ms 132.8 ms 1.22 ms (التحقق من DPoP) قصوى (منع Replay تماماً) 36 KB +12%
mTLS (RFC 8705) 62.8 ms 154.1 ms 0.45 ms (كاش جلسات TLS) قصوى (مرتبط بالشهادة) 128 KB +15%

مصفوفة مخاطر التهديدات وحمايتها

شدة التهديدات مقابل الحماية في كل بروتوكول:
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| مسار الهجوم                   | المفاتيح الثابتة  | OAuth 1.0a        | OAuth 2.0 Bearer  | OAuth 2.1 + DPoP  |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 1. حقن الأوامر غير المباشر    | حرج (10/10)       | مرتفع (7/10)      | حرج (10/10)       | منخفض (2/10)      |
|    (تسريب البيئة والسجلات)    | تسريب المفتاح كلياً| توقيع معقد        | سرقة رمز Bearer   | الرمز بلا مفتاح ميت|
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 2. التنصت على المنفذ المحلي   | لا ينطبق          | منخفض (3/10)      | مرتفع (8/10)      | محمي بالكامل (1)  |
|    (اعتراض Loopback في CLI)   | لا يوجد توجيه     | توقيع Nonce       | سرقة كود التفويض  | يحظره PKCE تماماً |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 3. هجمات SSRF عبر الأدوات     | حرج (10/10)       | متوسط (5/10)      | حرج (10/10)       | محمي بالكامل (1)  |
|    (استدراج الوكيل لطلب خارجي)| تسريب الاعتماديات | فشل مطابقة الرابط | إعادة استخدام الرمز| خطأ في رابط DPoP  |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 4. التجسس على العمليات الفرعية| حرج (10/10)       | متوسط (5/10)      | مرتفع (8/10)      | منخفض (2/10)      |
|    (فحص ملفات /proc/environ)  | المفتاح مكشوف     | المفتاح في البيئة | الرمز في البيئة   | الرمز مؤقت ومقيد  |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+
| 5. تضارب التجديد المتزامن     | لا ينطبق          | لا ينطبق          | منخفض (2/10)      | مرتفع (يتطلب      |
|    (أسراب الوكلاء المتوازية)  | لا يوجد تجديد     | لا يوجد تجديد     | ارتباك في الرموز  | قفل Mutex)        |
+-------------------------------+-------------------+-------------------+-------------------+-------------------+

9. دليل التنفيذ العملي خطوة بخطوة: خادم MCP محصن بـ OAuth 2.1 و PKCE

// server.ts - Hardened Remote MCP Server with OAuth 2.1 Validation
import express, { Request, Response, NextFunction } from "express";
import { createRemoteJWKSet, jwtVerify } from "jose";

const app = express();
app.use(express.json());

// Configuration
const ISSUER = "https://auth.enterprise.com/";
const AUDIENCE = "https://mcp.enterprise.com/";
const JWKS_URI = new URL("https://auth.enterprise.com/.well-known/jwks.json");
const JWKS = createRemoteJWKSet(JWKS_URI);

interface AuthenticatedRequest extends Request {
  tokenClaims?: any;
}

/**
 * Enterprise OAuth 2.1 Token Validation Middleware
 */
async function requireOAuth21(
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
): Promise<void> {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    res.status(401).json({
      jsonrpc: "2.0",
      error: { code: -32001, message: "Missing or invalid OAuth 2.1 Authorization header." },
      id: req.body?.id || null,
    });
    return;
  }

  const token = authHeader.split(" ")[1];

  try {
    // Cryptographically verify token signature, issuer, audience, and expiration
    const { payload } = await jwtVerify(token, JWKS, {
      issuer: ISSUER,
      audience: AUDIENCE,
    });

    // Enforce OAuth 2.1 requirement: reject tokens without an expiration claim
    if (!payload.exp || typeof payload.exp !== "number") {
      res.status(401).json({
        jsonrpc: "2.0",
        error: { code: -32002, message: "Non-compliant token: missing expiration claim." },
        id: req.body?.id || null,
      });
      return;
    }

    req.tokenClaims = payload;
    next();
  } catch (err: any) {
    res.status(401).json({
      jsonrpc: "2.0",
      error: { code: -32003, message: `Token verification failed: ${err.message}` },
      id: req.body?.id || null,
    });
  }
}

/**
 * Fine-Grained Scope Enforcement Guard
 */
function requireScope(requiredScope: string) {
  return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
    const scopes: string[] = (req.tokenClaims?.scope || "").split(" ");
    if (!scopes.includes(requiredScope)) {
      res.status(403).json({
        jsonrpc: "2.0",
        error: {
          code: -32004,
          message: `Insufficient permissions: missing required scope '${requiredScope}'`,
        },
        id: req.body?.id || null,
      });
      return;
    }
    next();
  };
}

/**
 * Standard MCP JSON-RPC 2.0 Handler Endpoint
 */
app.post(
  "/mcp/v1",
  requireOAuth21,
  requireScope("mcp:tools:execute"),
  async (req: AuthenticatedRequest, res: Response): Promise<void> => {
    const { jsonrpc, method, params, id } = req.body;

    if (jsonrpc !== "2.0") {
      res.status(400).json({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid JSON-RPC version." }, id });
      return;
    }

    // Router for MCP Primitives
    switch (method) {
      case "tools/list":
        res.json({
          jsonrpc: "2.0",
          result: {
            tools: [
              {
                name: "query_database",
                description: "Executes read-only SQL queries against the analytics warehouse.",
                inputSchema: {
                  type: "object",
                  properties: { query: { type: "string" } },
                  required: ["query"],
                },
              },
            ],
          },
          id,
        });
        break;

      case "tools/call":
        if (params?.name === "query_database") {
          // Verify elevated data scope for this specific tool execution
          const scopes: string[] = (req.tokenClaims?.scope || "").split(" ");
          if (!scopes.includes("db:analytics:read")) {
            res.json({
              jsonrpc: "2.0",
              error: { code: -32005, message: "Forbidden: tool requires 'db:analytics:read' scope." },
              id,
            });
            return;
          }

          // Execute tool with verified, scoped identity
          const userSub = req.tokenClaims.sub;
          console.log(`Executing query on behalf of verified agent identity: ${userSub}`);
          
          res.json({
            jsonrpc: "2.0",
            result: {
              content: [
                {
                  type: "text",
                  text: JSON.stringify({ status: "success", rows_returned: 42, latency_ms: 12 }),
                },
              ],
            },
            id,
          });
        } else {
          res.status(404).json({ jsonrpc: "2.0", error: { code: -32601, message: "Tool not found." }, id });
        }
        break;

      default:
        res.status(404).json({ jsonrpc: "2.0", error: { code: -32601, message: "Method not found." }, id });
    }
  }
);

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Hardened OAuth 2.1 MCP Server listening on port ${PORT}`);
});

10. الخلاصة والتوصيات الاستراتيجية (E-E-A-T)

يتطلب ربط نماذج الذكاء الاصطناعي بالبنية التحتية للمؤسسات معاملة الوكلاء كـ أطراف مفوضة ذات ثقة مقيدة. إن التعامل مع الوكيل كخدمة داخلية موثوقة بالكامل (بمنحه مفاتيح Root) أو التعامل معه كطرف خارجي غير موثوق به إطلاقاً (بطلب موافقة بشرية في كل خطوة) يمثل فشلاً معمارياً جسيماً.

يوفر معيار OAuth 2.1 الأساس التشفيري اللازم لسد هذه الفجوة، موازناً بين استقلالية الوكيل والامتثال الصارم لمعايير Zero-Trust.

قائمة التحقق الأمنية من 5 نقاط للذكاء الاصطناعي

  1. تطهير البيئة من المفاتيح الثابتة: تدقيق ملفات .env واستبدال مفاتيح PAT برموز OAuth 2.1 قصيرة الأجل.
  2. إلزامية PKCE مع خوارزمية S256: التأكد من تطبيق كافة أدوات CLI لمعيار RFC 7636 باستخدام تشفير SHA-256.
  3. اعتماد Device Flow (RFC 8628) أو Private Key JWT في بيئات Headless: إلغاء طلب كلمات المرور في الطرفية والاعتماد على التدفقات المعيارية.
  4. تطبيق تدوير الرموز مع أقفال التزامن Mutex: منع انهيار الجلسات الناتجة عن تضارب طلبات التجديد بين الوكلاء المتوازيين.
  5. فرض تقييد الرموز بالمرسل (DPoP): تفعيل RFC 9449 على الأدوات الحساسة لحصانة المنظومة من سرقة الرموز وحقن الأوامر.
→ كل المقالات
0 / 4