هندسة · 24 سبتمبر 2026

بيانات شخصية مقسومة بين جزأين: لماذا يمرّرها الفحص الجاري على كل جزء وحده

الحارس الذي يفحص كل جزء على حدة يمرّر رقم بطاقة إن وصل في قطعتين. أربعة مشاريع لا تتشارك كودًا وقعت في هذا العطل باستقلال، ووثّقه خامس، وصار للصنف اسم ورقم DOI. وفيما يلي إعادة إنتاج العطل، والشرط الذي يمنعه، والاختبار الذي يكشفه.

جزآن من البثّ جنبًا إلى جنب، وقيمة حسّاسة مقطوعة تمامًا عند الحدّ بينهما — نصفها في كل جزء
العطل في صورة واحدة: لا أحد من الجزأين يحوي القيمة، فلا يُفعَّل أي فحص، ثم يعيد متصفّح القارئ لصق النصفين.

إعادة الإنتاج: «user@exa» ثم «mple.com»

يبثّ النموذج إجابته دفعاتٍ مقسومة على حدود الرموز لا على المعنى. ولذلك يصل عنوان البريد الإلكتروني أو رقم البطاقة أو مفتاح API مقسومًا في العادة:

chunk 1:  "...you can reach me at user@exa"
chunk 2:  "mple.com whenever you like."

طبّق نمط البريد الإلكتروني على الجزء الأول: لا تطابق. وعلى الثاني: لا تطابق. يمرّ الجزآن، ويُكتبان إلى الشبكة، ثم يلصقهما المتصفّح عنوانًا صالحًا على الشاشة. وهذا المثال نفسه ذو الشطرين يتكرّر في البلاغات المرفوعة ضدّ عدّة مشاريع، وهو أول دليل على أن الأمر ليس خطأ فريق واحد.

لماذا لا يرى الفحصُ الجاري على كل جزء هذا العطل

الفحص يجري على نصّ لم يقصده النموذج وحدةً واحدة. فأحداث SSE تحمل ما أنتجه المُرمِّز، والقطع يقع في موضع اعتباطي: داخل كلمة، أو داخل رقم، أو داخل مفتاح. ومعاملة كل دفعة كأنها مستند كامل هي طرح السؤال الخطأ على البيانات الصحيحة.

وأول ما يلجأ إليه الناس هو حفظ جزء من الدفعة السابقة. وهذا يفيد في الاكتشاف ولا يغيّر شيئًا في التسريب: فحين يصل النصف الثاني وتتحقّق المطابقة أخيرًا، يكون النصف الأول قد خرج. وحجبه بعد ذلك يغيّر سجلّاتك لا شاشة القارئ.

لا يجوز إخراج نصّ قبل أن يثبت أن لا مطابقة يمكن أن تمتدّ إليه. الاكتشاف ليس منعًا.

أربعة مشاريع وقعت فيه باستقلال، وخامس وثّقه

قواعد كود منفصلة، ولغات منفصلة، وفرق منفصلة؛ ولا بلاغ منها يستشهد بالآخر. والحالة كما تحقّقنا منها في 24 سبتمبر 2026:

المشروعالبلاغالحالة
LiteLLM (Python)#41611 — قيمة مقسومة بين جزأي SSE تجتاز الفحصمفتوح، رُفع في 17 سبتمبر 2026
Mastra (TypeScript)#23783 — PIIDetector يُخرج البيانات مكشوفة عند انقسام المطابقةأُصلح في أربعة أيام (PR #24189)
LangChain (Python)#35011 — البثّ يتجاوز الحُرّاس والوسيطأُصلح في 10 يونيو 2026 (PR #37616)
NVIDIA NeMo Guardrails (Python)#2375 — مسار حجب المخرجات غير قابل للاستعمال في 0.24.0مفتوح، رُفع في 10 سبتمبر 2026
Vercel AI SDK (TypeScript)#21209 — مثال الحماية ترك wrapStream دون تنفيذنقص في التوثيق، أُغلق في 22 سبتمبر 2026

والسطر الأخير من نوع مختلف ويستحقّ الفصل: لم تشحن الحزمة مرشّحًا معطوبًا أصلًا. إنما عرضت صفحة الوسيط مثالًا بقي شطره الخاص بالبثّ تعليقًا، فكان من يتبع المثال يكتب تنفيذه بنفسه — وهكذا أعادت أربعة فرق اختراع التصميمين الخاطئين نفسيهما.

صار للصنف اسم: «split-boundary leaks»

في 22 سبتمبر 2026 نشر Ninad Phalak تقرير Split-Boundary Leaks in Streaming Guardrails على Zenodo برخصة CC BY 4.0 — doi.org/10.5281/zenodo.22909585. يجمع التقرير الحالات الأربع أعلاه، ويعدّ حالة Vercel نقصًا معترفًا به لا إخفاقًا خامسًا، ويسمّي الخاصية التي تنقص الأربعة.

وتوضيحان لازمان: التقرير صادر عن منتج منافس، وهو لا يفحص أي مكتبة ولا يزكّي أيًّا منها، بما فيها مكتبتنا. والاسم اسمه: فإن كتبت عن هذا الصنف فاستشهد بالـDOI بدل إعادة تسميته، لأن صنفًا باسم واحد أسهل على الفريق التالي أن يجده من الصنف نفسه بأربعة أسماء.

الشرط الثابت: ناتج مطابق بايت ببايت لتصفية النص كاملًا

لأي طريقة لتقسيم المُدخل نفسه، يجب أن يكون الناتج المبثوث مجمّعًا مطابقًا بايت ببايت لتصفية النص كاملًا دفعة واحدة.

هذه الجملة وحدها تستبعد كل الحالات أعلاه، وهي قابلة للاختبار بتأكيد واحد بدل أن تُناقَش في المراجعة. وهي الخاصية التي ينبغي أن تطلبها من أي مكتبة — ويلاحظ التقرير أن لا أحد تقريبًا ينشرها عقدًا معلنًا، مع أنها ما يتيح لك تمييز تنفيذ صحيح من تنفيذ يبدو معقولًا.

هل تفحص مكتبتك كل جزء وحده أم تحتفظ بحالة بينها؟

ثلاثة فحوص تجيب دون قراءة كود أحد:

  • اقطع قيمة معروفة عند كل موضع. مرّر المُدخل نفسه مقسومًا بكل الطرق الممكنة، وقارن الناتج المجمّع بتصفية النص كاملًا. وأي اختلاف هو العطل.
  • راقب متى يخرج الجزء الأول. إن ظهر الناتج قبل أن يتمكّن الفاحص من رؤية نهاية أي مطابقة، فالتنفيذ يُخرج على أمل لا على يقين.
  • ابحث عن حجم في الإعدادات. وجود حجم مخزن أو نافذة قابل للضبط يعني نافذة ثابتة، وللنافذة الثابتة حدّ — انظر ما يلي.

وجود مخزن مؤقّت ليس احتجازًا للنص

هذا هو الفارق الذي يوقع مهندسين جيّدين. فحالة الاكتشاف وسياسة الإخراج قراران منفصلان، والثاني وحده هو ما يوقف التسريب. وقد جاءت حزمة Umbraco للذكاء الاصطناعي بهذا الشكل تمامًا: نافذة منزلقة تكتشف بشكل صحيح بينما يخرج كل جزء فور وصوله. والإصلاح المدموج في 22 سبتمبر 2026 عنوانه «make streaming post-generate guardrails actually block/redact»، والذي تغيّر فيه هو سياسة الإخراج لا الكاشف.

احتجاز ثابت بمقدار N محرفًا تسريبٌ قابل للضبط

احتجِز آخر 20 أو 50 أو 64 محرفًا، وألحقها بالجزء التالي، ثم افحص وأخرِج: تنجح الحيلة حتى تأتي مطابقة أطول من N — وحينها تفشل صامتة، لأن الناتج يظلّ يبدو محجوبًا. ويحمل LiteLLM إعدادًا اسمه stream_holdback_chars عبر ثمانية ملفات مصدرية، وفي 24 سبتمبر 2026 عبر صفر ملفات توثيق؛ أي أن الرقم الذي يقرّر إن كنت تسرّب لا يراه معظم المشغّلين.

وتذكر وثائق Vercel AI SDK الآن الصيغة العامة لذلك في ملاحظة تحت مثالها: على أي تنفيذ تدريجي أن يحتفظ بكل مطابقة ناقصة محتملة، والمخزن ذو الحجم الثابت وحده غير آمن مع الأنماط متغيّرة الطول بلا حدّ. وكتلة مفتاح PEM أو رمز JWT طويل مثالان على ذلك.

وللإصلاح نفسه عطله

عطلان يأتيان مع الإصلاح:

  • الإفساد. الحجب يغيّر طول النص، فإن قطعت النص المحجوب بمواضع حُسبت على النص الخام وقعت داخل علامة الحجب وأكلت محارف صحيحة. وبلاغ Mastra يذكر هذه الحالة إلى جانب التسريب.
  • إنذارات كاذبة عند إعادة اللصق. يصف NVIDIA NeMo Guardrails #1197 مسافةً تُدرَج عند سحب الرموز من المخزن: فمع مُرمِّز دون الكلمة تصل «assisting» على هيئة «ass» و«isting» فتبلغ مسار المخرجات «ass isting»، مُطلِقةً مخالفة على كلمة لم توجد أصلًا.

«خزّن كل شيء أو ابثّ وسرّب» مفاضلة زائفة

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

وفيه قرار واحد لا بدّ أن يُتّخذ صراحةً، وعنده يعود الصنف: حين يبلغ الذيل المحتجز سقفه ونمطٌ متغيّر الطول ما زال مفتوحًا، إمّا أن يُطلق التنفيذ نصًّا قد يكون النصف الأول من سرّ، وإمّا أن يرفض. والذيل المحدود الذي يُطلق عند التجاوز هو النافذة الثابتة نفسها باسم أفضل. والسلوك الآمن هو الفشل المغلق.

الاختبار الذي يكشف الصنف كلّه

ليس إطارًا جديدًا، بل تأكيد واحد يُشغَّل على كل تقسيم لكل حالة:

for (const text of fixtures) {
  const whole = filterWholeString(text);
  for (let size = 1; size <= text.length; size++) {
    let streamed = '';
    const f = createFilter();
    for (let i = 0; i < text.length; i += size) streamed += f.push(text.slice(i, i + size));
    streamed += f.flush();
    assert.equal(streamed, whole);        // chunking invariance
  }
}

وضَع في الحالات نمطًا غير محدود الطول — كتلة PEM أو JWT طويلًا — وإلا نجحت المجموعة على تنفيذ يُطلق عند التجاوز. وأضِف قيمة ملتصقة بنصّ غير ASCII، ولغةً تُكتب بلا مسافات، لأن ذيلًا محدودًا بفواصل الكلمات ينمو فيها بلا حدّ.

من أين جاء هذا النص

ننشر llm-stream-guardrails، وهي مكتبة برخصة MIT تنفّذ تصميم نقطة الحسم وتعلن الشرط الثابت عقدًا لها، ومشرفها هو من أبلغ Vercel AI SDK في 20 سبتمبر 2026 بأن المثال تُرك دون تنفيذ؛ ثم دُمجت بعد يومين ثلاثة طلبات توثيق تشير إلى ذلك البلاغ، فتحتها أتمتة المشروع نفسه. هذه هي المصلحة التي يجب الإفصاح عنها قبل أن تقرأ ما سبق باعتباره محايدًا.

وكل واقعة هنا جرى التحقّق منها من مصدرها في 24 سبتمبر 2026 لا نقلًا عن التقرير: حالات المسائل وطلبات الدمج عبر واجهة GitHub البرمجية، وسجلّ Zenodo وملفه، وتوثيق الوسيط على الفرع الأساسي للحزمة. وما تعذّر التحقّق منه ليس في هذه الصفحة.

الأسئلة الشائعة

أسئلة يطرحها المهندسون

هل هذا عطل في إطاري وحده أم عند الجميع؟

أربعة مشاريع لا رابط بينها وقعت فيه باستقلال — LiteLLM وMastra وLangChain وNVIDIA NeMo Guardrails — واثنان من تلك البلاغات كانا ما زالا مفتوحين في 24 سبتمبر 2026. افترض أن منظومتك مصابة حتى تُشغّل اختبار الثبات على التقسيم.

هل يفحص حارسي كل جزء وحده أم يحتفظ بحالة؟

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

ما حجم الاحتجاز المناسب: 20 أم 50 أم 64 محرفًا؟

لا أحدها آمن بذاته. فأي N ثابت يسقط أمام مطابقة طولها N+1، ويسقط صامتًا لأن الناتج يبدو محجوبًا. المقدار المحتجز يجب أن يُشتقّ من الأنماط التي تبحث عنها، وعلى التنفيذ أن يفشل مغلقًا عند بلوغ سقفه.

هل يكفي أن أخزّن الردّ كاملًا ثم أُرشّحه؟

نعم، وهو صحيح، وهو ما توصي به وثائق Vercel AI SDK كخيار افتراضي آمن. لكن الثمن أنك تتوقّف عن البثّ: يصير زمن أول رمز هو زمن التوليد الكامل، وتنمو الذاكرة مع حجم الكتلة. مقبول في الردود القصيرة، ومؤلم في الطويلة.

ماذا يفعل مثال wrapStream في AI SDK اليوم؟

منذ تصحيح التوثيق في سبتمبر 2026 صار يحتفظ بمخزن لكل كتلة نصّية، ويضيف إليه كل دفعة دون إخراج، ثم عند text-end يحجب النص كاملًا ويُخرجه مرّة واحدة. هذا يسدّ ثغرة المطابقة المقسومة، وتذكر الوثائق أنه يؤخّر الإخراج ويستهلك ذاكرة تتناسب مع حجم الكتلة.

هل أُصلحت مسألة LiteLLM؟

ليس حتى 24 سبتمبر 2026. كانت المسألة #41611 مفتوحة وعليها خمسة تعليقات، وقد رُفعت في 17 سبتمبر. تحقّق بنفسك قبل الاعتماد على أي من الجوابين.

هل يعاني NVIDIA NeMo Guardrails من هذا؟

المسألة #2375 بعنوان «مسار حجب المخرجات غير قابل للاستعمال في 0.24.0» كانت مفتوحة في 24 سبتمبر 2026. وثمّة مسألة أخرى، #1197، توثّق خطرًا قريبًا: إعادة لصق الرموز المسحوبة تُدرج مسافة، فقد تبلغ «assisting» مسارَ المخرجات على هيئة «ass isting».

كيف أكتب اختبارًا يكشفه؟

تأكيد واحد: لكل حالة ولكل حجم جزء، يجب أن يساوي تجميع الناتج المبثوث تصفيةَ النص كاملًا. وأدرِج نمطًا غير محدود الطول مثل كتلة PEM، وإلا نجح الاختبار على تنفيذ يُطلق عند التجاوز.

الحجب حذف نصًّا سليمًا. لماذا؟

لأن الحجب يغيّر طول النص. فإن حسبت المواضع على النص الخام ثم قطعت بها النص المحجوب، وقعت داخل علامة الحجب وفقدت محارف حقيقية. أبقِ المخزن الخام سليمًا ولا تحجب إلا عند الإخراج.

من سمّى هذا الصنف، وكيف أستشهد به؟

Ninad Phalak، في «Split-Boundary Leaks in Streaming Guardrails»، Zenodo، 22 سبتمبر 2026، رخصة CC BY 4.0، doi.org/10.5281/zenodo.22909585. استشهد بالـDOI بدل إعادة تسمية الصنف: اسم واحد يجعله أسهل على الفريق التالي.

هل شيء من هذا أداة امتثال قانوني؟

لا. المرشّح أثناء البثّ دفاع في العمق: يطابق الأشكال لا المعاني، ولا مكتبة تجعل نظامًا ممتثلًا لأي إطار. والامتثال خاصية نظام ومشغّله.

هل يزكّي التقرير مكتبتكم؟

لا. يذكر حسابنا على GitHub مرّة واحدة، لأنه أبلغ عن نقص التوثيق لدى Vercel. ولا يسمّي الحزمة ولا يفحصها ولا يزكّي شيئًا — وهو صادر عن منتج منافس. اقرأه واحكم بنفسك، والـDOI أعلاه.