
وجدت ثغرة يوم-الصفر في langchain — إليك كيف سارت الأمور
كيف كشفت واجهة برمجية قديمة منسية في أحد أشهر أطر العمل في مجال الذكاء الاصطناعي ملايين التطبيقات لقراءة ملفات عشوائية.
هناك شعور معيّن ينتابك عندما يعمل إثبات المفهوم من المحاولة الأولى. ليس إثارةً بالضبط — بل إدراكٌ بطيء ومزعج بأن شيئًا حقيقيًا قد حدث للتو. هذا ما شعرت به عندما شغّلت إعدادات الاختبار الخاصة بي ضد load_prompt_from_config() وشاهدت محتويات ملفٍ لم يكن من حقّه قراءته تُطبع مباشرةً على طرفيتي.
هذه هي قصة CVE-2026-34070: ثغرة Path Traversal في langchain-core، تم إصلاحها الآن في الإصدار 1.2.22.
إذا كنت قد بنيت أي شيء باستخدام الذكاء الاصطناعي في العامين الماضيين، فمن شبه المؤكد أنك تعاملت مع LangChain. إنه النسيج الضام لمكدس الذكاء الاصطناعي الحديث — الإطار الذي يربط نماذج اللغة، ومخازن المتجهات، والأدوات، وإدارة المطالبات في تطبيقات متماسكة. مع أكثر من 130 ألف نجمة على GitHub واعتماد واسع من مشاريع نهاية الأسبوع الصغيرة إلى النشر المؤسسي، فإن ثغرة هنا لا تبقى محصورة.
كنت أجري مراجعة كود لنظام المطالبات عندما لفت انتباهي شيء ما: وحدة باسم langchain_core/prompts/loading.py. كانت تحمّل ملفات من القرص بناءً على قيم تُسحب مباشرةً من قواميس إعدادات غير مُسلسلة. لا تحقق من الصلاحيات. لا تنظيف للمسارات. مجرد open(path).
واصلت القراءة.
كانت ثلاث دوال داخلية في مركز هذا الأمر:
_load_template() — تقرأ الملفات المشار إليها بواسطة template_path وsuffix_path وprefix_path_load_examples() — تقرأ الملفات المشار إليها بواسطة مفتاح examples عندما يكون نصًا_load_few_shot_prompt() — تقرأ الملفات المشار إليها بواسطة example_prompt_pathكانت فحوصات امتداد الملف موجودة. .txt للمطالبات. .json و.yaml و.yml للأمثلة. لكن لم يكن هناك ما يمنع تلك المسارات من أن تكون مطلقة (/etc/passwd) أو قائمة على الاجتياز (../../../../home/user/.ssh/). مرشّح الامتداد أعطى إحساسًا زائفًا بالأمان — كان يعني فقط أن على المهاجم اختيار الامتداد الصحيح، وليس أن الهجوم محظور.
هذه الدوال كلها قابلة للوصول عبر واجهتين عامتين: load_prompt() وload_prompt_from_config().
أبسط نسخة بدت هكذا:
from langchain_core.prompts.loading import load_prompt_from_config
config = {
"_type": "prompt",
"template_path": "/tmp/secret.txt",
"input_variables": [],
}
prompt = load_prompt_from_config(config)
print(prompt.template) # محتويات /tmp/secret.txt، تُطبع بوضوح
هذا كل شيء. مرّر مسارًا مطلقًا، واحصل على محتويات الملف مغلّفة في PromptTemplate. لا مصادقة. لا صلاحيات خاصة. إذا كان بإمكانك التأثير على قاموس الإعدادات، يمكنك قراءة الملفات.
اجتياز الدلائل عمل بنفس السلاسة:
config = {
"_type": "prompt",
"template_path": "../../etc/secret.txt",
"input_variables": [],
}
كان تنويع JSON/YAML أكثر خطورةً من الناحية العملية بسبب أنواع الملفات التي يمكنه الوصول إليها:
config = {
"_type": "few_shot",
"examples": "../../../../.docker/config.json",
"example_prompt": {
"_type": "prompt",
"input_variables": ["input", "output"],
"template": "{input}: {output}",
},
"prefix": "",
"suffix": "{query}",
"input_variables": ["query"],
}
prompt = load_prompt_from_config(config)
يحتوي .docker/config.json على بيانات اعتماد Docker Hub الخاصة بك. ~/.azure/accessTokens.json يحمل رموز Azure الخاصة بك. ملفات Kubernetes، وإعدادات CI/CD، وإعدادات التطبيقات الداخلية — أي شيء بالامتداد الصحيح موجود في أي مكان على نظام الملفات كان هدفًا مشروعًا.
جاءت درجة CVSS عند 7.5 عالية، مع متجه AV:N/AC:L/PR:N/UI:N — قابل للوصول عبر الشبكة، منخفض التعقيد، بدون صلاحيات، وبدون حاجة لتفاعل المستخدم.
الدرجة محصورة دون 9+ لأن فحص امتداد الملف يحدّ من الملفات التي يمكن قراءتها. لكن 7.5 ما زالت تقلّل من الأثر الحقيقي في أنماط نشر معينة.
فكّر في أين يقع هذا في الإنتاج:
load_prompt_from_config()، فكل مستخدم مهاجم محتمل.في تلك البيئات، يكون قيد "محدود بامتداد الملف" أقل أهمية بكثير. يمكن للمهاجم ببساطة استهداف ملفات يعرف أنها موجودة بالامتداد الصحيح. على مثيل سحابي نموذجي: requirements.txt وconfig.yaml و.env.yaml وملفات أسرار مركّبة بامتدادات .json — القائمة طويلة.
تُوصف الدوال المتأثرة في النشرة الأمنية بأنها "واجهات برمجية قديمة غير موثقة." إنها تسبق نظام التسلسل الحالي langchain_core.load (dumpd/dumps/load/loads)، الذي يعتمد نموذجًا قائمًا على القائمة البيضاء ولا يقوم بقراءات لنظام الملفات.
الواجهات الجديدة موجودة. إنها أفضل. لكن الكود القديم لم يُنظّف أبدًا — بقي هناك، قابلًا للوصول، بدون تحقق، في انتظار.
هذا نمط يستحق الانتباه. في مشاريع المصدر المفتوح سريعة الحركة، خاصةً تلك التي نمت بسرعة مثل LangChain، يتراكم الدين التقني في الزوايا. الكود القديم الذي "لم يكن مقصودًا للمستخدمين حقًا" لا يحظى بنفس التدقيق الذي يحظى به سطح الواجهة الأساسي. لكنه ما زال قابلًا للاستدعاء. ما زال موجودًا في الحزمة. وإذا كان يقرأ ملفات من القرص، فهو ثغرة محتملة.
وصل التصحيح في langchain-core 1.2.22. يضيف الإصلاح تحققًا من المسارات يرفض كلًا من المسارات المطلقة وتسلسلات الاجتياز .. قبل فتح أي ملف. يتوفر مخرج طوارئ — allow_dangerous_paths=True — للتطبيقات التي تحتاج فعلًا للقراءة من مسارات موثوقة، مع الإقرار الصريح بأن المستدعي يختار المخاطرة.
كما تم إيقاف الواجهات القديمة رسميًا مع هذا الإصدار. ستُزال بالكامل في 2.0.0. إذا كنت تستخدم load_prompt() أو load_prompt_from_config() في أي مكان، انتقل إلى مكافئات langchain_core.load الآن بدلًا من انتظار التغيير الكاسر.
حدّث فورًا:
pip install --upgrade langchain-core
تحقق من أنك على الإصدار 1.2.22 أو أحدث:
python -c "import langchain_core; print(langchain_core.__version__)"
إذا كنت تبني على LangChain، فمراجعة سريعة تستحق التنفيذ:
load_prompt وload_prompt_from_config في قاعدة الكود الخاصة بك. إذا ظهرت، تحقق مما يُمرَّر إليها..txt أو .json أو .yaml الموجودة على مثيلاتك وما تحتويه.ثغرات الأمان في البنية التحتية للذكاء الاصطناعي ستزداد أهميةً، وليس أقل، مع تعامل هذه الأنظمة مع أعباء عمل أكثر حساسية. استجاب فريق LangChain جيدًا — الإصلاح نظيف، ومسار الإيقاف واضح، وتوثيق النشرة شامل.
لكنه تذكير جيد بأن سطح الهجوم لتطبيق الذكاء الاصطناعي ليس مجرد النموذج. إنه كل مكتبة في المكدس، وكل دالة قديمة لم تُنظّف أبدًا، وكل مكان تحوّل فيه "المستخدم لن يمرّر على الأرجح مدخلات غير موثوقة هنا" إلى افتراض بدلًا من ضمان.
اقرأ الكود. خاصةً الأجزاء القديمة.
تم تخصيص CVE-2026-34070 لهذه الثغرة. النشرة الكاملة متاحة في النشرة الأمنية على GitHub لـ LangChain.