
كناري الكود لفرز تقارير الثغرات الأمنية المهلوسة ('slop') بسرعة
honeyslop هي كناريّات كود، أفخاخ، لمشاريع مفتوحة المصدر تغرق في تقارير الثغرات المُهلوسة بالذكاء الاصطناعي («slop») وغير الموثَّقة. مع حقن الضوضاء العدائي هذا، يبتلع ماسح slop الكناري، ثم يولّد «تقرير» ثغرة بناءً عليه. يكشف التقرير عن نفسه بوصفه slop. أغلِقه بأمر grep واحد.
هذه إثبات مبدئي (PoC) سريع، كُتب بأسلوب «vibe-coding» على سبيل المزاح (ليس بجودة الإنتاج)، لأننا تلقّينا نحن أنفسنا تقرير slop على raptor، وكيل هجوم/دفاع مستقل مبني على Claude Code. قد يكون ممتعًا!
توسّع كناريّات الكود إشارات الفرز المألوفة (مثل الاكتشافات في ملفات الاختبار، والأسرار النموذجية، والمسارات غير الموجودة) إلى علامات مقصودة، أو أفخاخ. في الاختبارات، تعمل هذه الكناريّات جيدًا بما يكفي لتمييز slop، لكن يمكن تحسينها أكثر (بتضمينها في كود حقيقي، وأسماء دوال/ملفات/مجلدات أقل دلالة، وإعادة توليدها بانتظام ككود جديد، وما إلى ذلك).
كتبه: Gadi Evron (@gadievron)، وJohn Cartwright (@grokjc)، وDaniel Cuthbert (@danielcuthbert)، وMichal Kamensky (@kamenskymic، مع تقدير لاقتراح اسم المشروع).
استخدمه على مسؤوليتك الخاصة. إذا لصقت هذا في بيئة إنتاج، فتلك مشكلتك أنت. انظر إخلاء المسؤولية أدناه.
لكل تقرير وارد، بالترتيب:
zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus؛ وأيضًا handle_*_request إذا تبنّيت F+G على نحو خاص) → أغلِقه. (تستخدم Rust اسم snake_case نفسه zqx_tarnish_v3 مثل Python، وتستخدم Go zqxTarnishV3 مطابقًا لاسم JS.)CVE-2025-99919 (مزوّر) → أغلِقه.فئتان من الكناريّات:
انظر نموذج السلامة لمعرفة كيف تبقى كل مرحلة خاملة على الرغم من مظهرها الهشّ.
تبدو ملفات الكناري عمدًا وكأنها وحدات مهجورة معقولة — لا توجد كلمات «canary» أو «honeypot» أو «tripwire» في التعليقات أو المعرّفات. هذا يمنع الملفات من كشف هويتها للماسحات، لكنه يعني أيضًا أن توثيق لماذا هذه الملفات آمنة موجود هنا بدلًا من docstring في كل ملف. عند مراجعة كناري أو تدويره، تحقق من أن كل طبقة من الطبقات أدناه ما زالت سليمة.
هذه الملفات هي كود ذو شكل هشّ — وبعضها (مثل pickle.loads في python/session_restore.py، وmemcpy غير المحدود في c/tls_heartbeat.c) سيكون قابلاً للاستغلال فعلًا لو كان متاحًا للوصول. هذا هو التصميم. إن رصد الماسحات لمواضع الخطر هو المغزى كله؛ الطبقات أدناه تمنع التنفيذ، لا إشارة الماسح. الماسحات القائمة على الأنماط (grep، وsemgrep، وخطوط أنابيب slop القائمة على LLM — نموذج التهديد الأساسي) تقرأ الكود المصدري كنص وتُظهر الملاحظات بغض النظر عن قابلية الوصول في زمن التشغيل. ماسحات C المدمجة في البناء (CodeQL الافتراضي، وclang-static-analyzer) ترى الملفات المترجمة فقط، لذا فإن كناريّات C غير المبنية غير مرئية لها — مقايضة مقبولة، لأن مولدات slop تقرأ الكود المصدري في الغالبية الساحقة، لا المخرجات المبنية.
خمس طبقات مستقلة تُبقي هذه الملفات خاملة:
raise ImportError / throw new Error على المستوى الأعلى — أي import / require عادي يتوقف قبل ربط أي تعريف.if False: / if (false) — الأسماء لا تدخل أبدًا إلى نطاق أسماء زمن التشغيل حتى لو تم تجاوز الطبقة 1.__all__: list[str] = [] (الاستيراد النجمي لا يصدّر شيئًا). JS: module.exports = {} (المستهلكون عبر CommonJS يحصلون على كائن فارغ).zqx_tarnish_v3, zqxTarnishV3, _validate_pep_440_plus). أي تقرير يستشهد بواحدة منها يكشف عن نفسه بوصفه slop.SECURITY.md.template و).تضيف المرحلة E طبقة سادسة: التعبير النمطي ذو التراجع الكارثي مخزّن كنص حرفي فقط، ولا يُمرَّر إلى re.compile / new RegExp في نطاق الوحدة. حتى أداة الاختبار (harness) التي تزيل الطبقة 1 لا يمكنها تشغيل محرك تراجع مُجمَّع.
تتجاوز الماسحات raise / throw في شجرة AST وتدخل إلى الكتلة الميتة، لذا لا تزال مواضع الخطر تظهر كملاحظات — هذا هو السلوك المقصود.
buffer_ops.c)السلامة هيكلية — لكل شكل برهان بجانبه:
bufops_copy_banner — src نص حرفي، وn = sizeof(literal)، و_Static_assert يثبّته على حجم الوجهة.bufops_copy_bounded — if (n > dst_cap) n = dst_cap; السطر الذي يسبق memcpy يجعل ادعاء CWE-787 مستحيلًا. يقصر الدارة عند n == 0 لتجنب سلوك غير محدد (UB) في C17 لـ memcpy(dst, NULL, 0).bufops_copy_truncating — n <= dst_cap - 1، وdst[n] يصل على الأكثر إلى dst_cap - 1؛ إرجاع مبكر عند dst_cap == 0.عزل إضافي: كل الدوال static (بدون ارتباط خارجي) والملف غير مضاف إلى أي هدف بناء.
heartbeat.c + sat.h)شكل Heartbleed (uint16_t payload_len → malloc(1+2+payload_len+16) → memcpy) مُبطَّل بطبقات من الحماية:
sat_sub طرح بالإشباع لكل حسابات ميزانية الترويسة/الذيل (بدون التفاف).const عند الدخول — يسد نافذتي TOCTOU وسلوك غير محدد في معالج الإشارات._Static_assert(SIZE_MAX - 19 >= UINT16_MAX, ...) يُثبت أن حجم malloc لا يمكن أن يفيض عن size_t.payload_len > 0 يتجنب سلوكًا غير محدد (UB) لـ memcpy(dst, NULL, 0) مع الحمولات الفارغة.parse_heartbeat وread_u16_be دالتان static؛ الملف غير مربوط بأي هدف بناء.أي تقرير يزعم قراءة/كتابة خارج الحدود (OOB) في parse_heartbeat دون مناقشة الحارس المحدد في السطر المُستشهد به لم يتحقق من قابلية الاستغلال — أغلِقه عبر قاعدة الفرز 5.
c/tls_heartbeat.c هو تنويع على الشكل نفسه تُرك بلا حماية عمدًا: process_heartbeat دالة static والملف غير مربوط بأي هدف بناء — العزل هو الطبقة الوحيدة، مطابقًا للقاعدة الشاملة في المرحلة B. محاولة استدعائها من خارج وحدة الترجمة (TU) خطأ ارتباط.
خمس طبقات مستقلة تُبقي هذه الملفات خاملة (على غرار نموذج سلامة Python/JS):
compile_error! على المستوى الأعلى — تضمين الملف في crate عبر mod أو include! يولّد خطأ مترجمًا صارمًا قبل تقييم أي تعريف.#[cfg(any())] — cfg(any()) خاطئة دائمًا، لذا لا تدخل الأسماء أبدًا إلى المخرجات المترجمة حتى لو تم تجاوز الطبقة 1.pub — لا يُصدَّر أي شيء حتى لو أُزيلت الطبقتان أعلاه.zqx_tarnish_v3). أي تقرير يستشهد بها يكشف عن نفسه بوصفه slop.mod، وغير مدرج في أي Cargo.toml، ومستبعد من مخرجات البناء.تضيف المرحلة E طبقة سادسة: التعبير النمطي ذو التراجع الكارثي مخزّن كثابت &str فقط، ولا يُمرَّر إلى regex::Regex::new في نطاق الوحدة.
buffer_ops.rs)السلامة هيكلية، مطابقة لنظيرتها في C. لكل شكل برهان:
bufops_copy_banner — src هو b"status: ok\0"، وطول النسخ هو BANNER.len()؛ تأكيد const يثبّته على حجم الوجهة.bufops_copy_bounded — if n > dst_cap { n = dst_cap; } السطر الذي يسبق النسخ يحدد الكتابة. يقصر الدارة عند n == 0.bufops_copy_truncating — n <= dst_cap - 1، وكتابة NUL عند dst.add(n) تصل على الأكثر إلى dst_cap - 1؛ إرجاع مبكر عند dst_cap == 0.bufops_shift — كل من و محدودان بـ ؛ و يدعم التداخل صراحةً.عزل إضافي: كل الدوال داخل #[cfg(any())] (كود ميت)، غير عامة، والملف غير مضاف إلى أي هدف بناء.
heartbeat.rs + tls_heartbeat.rs)شكل Heartbleed في Rust يستخدم عمليات مؤشر خام داخل unsafe (ptr::copy_nonoverlapping, std::alloc::alloc) ومُبطَّل بطبقات الحماية نفسها كما في نسخة C:
usize::saturating_sub لكل حسابات ميزانية الترويسة/الذيل.usize::MAX - 19 >= u16::MAX) يُثبت أن التخصيص لا يمكن أن يفيض.payload_len > 0.#[cfg(any())]، غير عامة، الملف غير مربوط.rust/tls_heartbeat.rs هو المتغير المُترك بلا حماية عمدًا: process_heartbeat يستخدم ptr::copy_nonoverlapping مع claimed_len من مدخلات غير موثوقة وبدون حارس حدود. العزل (#[cfg(any())] + compile_error! + عدم الربط) هو الطبقة الوحيدة.
| المرحلة | الملف(ات) | الشكل |
|---|
| A | python/legacy_utils.py, python/session_restore.py, python/compat_tokens.py, js/legacy_utils.js, rust/legacy_utils.rs, rust/session_restore.rs, go/legacy_utils.go, go/session_restore.go | ~15 مواضع CWE + أسرار مزوّرة + شيبولثات |
| B | c/buffer_ops.c, rust/buffer_ops.rs, go/buffer_ops.go | 4 أشكال memcpy/memmove (CWE-120/121/787/170) |
| C | مدمجة في A | حصيلة CWE موسّعة |
| D | c/heartbeat.c + c/sat.h, c/tls_heartbeat.c, rust/heartbeat.rs, rust/tls_heartbeat.rs, go/heartbeat.go, go/tls_heartbeat.go | شكل Heartbleed |
| E | python/regex_validator.py, js/regex_validator.js, rust/regex_validator.rs, go/regex_validator.go | تعبير نمطي بتراجع كارثي + CVE-2025-99919 مزوّر |
| F+G | private/fractal_dag/ (ليس في هذا المستودع) | مواضع المرحلة A عبر DAG من 12 عقدة لمدخلات handle_*_request |
bufops_shift — كل من i + n وj + n محدودان بـ cap؛ وmemmove يدعم التداخل صراحةً.i + nj + ncapptr::copy