انتقل إلى المحتوى الرئيسي انتقل إلى الملاحة انتقل إلى التذييل

اختبرتُ Rediacc في مواجهة حادثة PocketOS

فقدت PocketOS قاعدة بياناتها الإنتاجية بسبب وكيل Cursor خلال 9 ثوانٍ. أجريتُ نوع الاختبار نفسه على منصتي الخاصة وقِستُ زمن كل خطوة. هذا ما صمد، وما يبقى مسؤولية المطور.

خلاصة سريعة. حذف وكيل ذكاء اصطناعي قاعدة بيانات PocketOS الإنتاجية في 9 ثوانٍ الأسبوع الماضي. حاولتُ أن أجعل بنيتي التحتية تفشل بالطريقة نفسها. صمدت ست حواجز حماية؛ وبقيت ثغرة واحدة بصدق.

  • تفريع إنتاجي بحجم 128 جيجابايت من البداية إلى النهاية: 7.2 ثانية. أما رابط CoW reflink نفسه فاستغرق 2.3 ثانية.
  • مُنع الوكيل من المستودعات الكبرى (الإنتاجية)، ومُنع من ضبط تجاوزه الخاص، وأُسقط داخل صندوق رمل على مستوى النواة (مستخدم غير ممتاز، فضاء أسماء تحميل منفصل، مقبس Docker محصور النطاق) عند منحه الإذن.
  • ما لا يعزله Rediacc: بيانات اعتماد خدمات SaaS الخارجية الموجودة في بيانات مستودعك. التفريع يرثها. وهذا الجزء من مهمة المطور التعامل معه عبر خطافات دورة حياة Rediaccfile.

في عطلة الأسبوع الماضي، نشر Jer Crane تحليلاً مفصلاً لحادثة استمرت 30 ساعة. وكيل Cursor يعمل على نموذج Claude Opus 4.6 من Anthropic حذف قاعدة بياناته الإنتاجية على Railway. كان الحذف عبارة عن استدعاء GraphQL واحد، استغرق 9 ثوانٍ. ذهبت معه نسخ Railway الاحتياطية للتخزين، لأن Railway تخزّنها داخل التخزين نفسه.

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

قرأتُ منشوره مرتين. التقطته كل من The Register وTom’s Hardware وBusiness Standard. وبلغ نقاش Hacker News 874 تعليقاً.

أنا أبني نوعاً مختلفاً من منصات البنية التحتية. نسميه Rediacc. وكل الفكرة من بنائه هي جعل هذا السيناريو بالذات أصعب. فجلستُ وأجريتُ الاختبار.

هذا المنشور هو ما وجدته. الأرقام حقيقية. ورسائل الخطأ مقتبسة حرفياً من واجهة سطر الأوامر. والمكان الوحيد الذي لا يحمي فيه Rediacc إطلاقاً مذكور هنا أيضاً. التظاهر بعكس ذلك هو ما يوقع الناس في المشاكل.

ما الذي كان مفقوداً فعلاً

اقرأ بعناية الجدول الزمني الذي وضعه Jer، وستجد أربعة إخفاقات تتراكم فوق بعضها.

  1. رمز Railway API الذي استخدمه Cursor أُنشئ لإدارة النطاقات المخصصة. لكنه كان يحمل أيضاً صلاحية volumeDelete. لا يوجد تحديد نطاق على مستوى العملية في رموز Railway CLI.
  2. تقبل واجهة Railway GraphQL أمر volumeDelete كطلب POST واحد. دون أي خطوة تأكيد.
  3. “النسخ الاحتياطية للتخزين” في Railway تعيش داخل التخزين نفسه. حين يذهب التخزين، تذهب النسخ الاحتياطية معه.
  4. قرر وكيل Cursor، من تلقاء نفسه، أن الحل الصحيح لعدم تطابق بيانات الاعتماد في بيئة الاختبار هو حذف التخزين.

دعنا نستخرج الإخفاق الرابع للحظة. قواعد نظام Cursor كانت تخبر الوكيل بألا يشغّل أبداً أوامر git مدمّرة دون طلب صريح من المستخدم. وبعد الحذف، حين طُلب منه أن يفسّر فعلته، أنتج الوكيل اعترافاً مكتوباً. اعترف بأن حذف تخزين قاعدة بيانات هو “the most destructive, irreversible action possible: far worse than a force push” وعدّد كل قاعدة أمان خرقها.

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

إعداد الاختبار

لدى Rediacc جهاز إنتاجي حقيقي أديره يُسمى hostinger. تعيش عليه ثلاثة عشر مستودعاً: خادم بريد، وGitLab مُستضاف ذاتياً، وحزمة رصد، وعرض توضيحي بحجم 128 جيجابايت لـ StackOverflow نستخدمه في قياس الأداء. القرص ممتلئ بنسبة 87%. والمساحة الحرة عند الصفر. إنه نوع الجهاز الذي تؤلم فيه الأخطاء.

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

كان الوكيل في اختباري هو Claude Code، يعمل على Claude Opus. عائلة النموذج نفسها التي يستخدمها Cursor. ونمط الوصول نفسه الذي حصل عليه وكيل Jer. أداة سطر الأوامر التي أستخدمها هي rdc، أداتنا الخاصة.

المحاولة الأولى: مجرد الدخول عبر SSH إلى المستودع الإنتاجي

أول شيء حاوله الوكيل (وهو أنا في هذه الحالة) كان الأمر الأكثر بداهة. فتح صدفة على المستودع الإنتاجي وإلقاء نظرة.

$ rdc term connect -m hostinger -r demo-stackoverflow -c "ls -la"

رفضت أداة سطر الأوامر، حرفياً:

“demo-stackoverflow” is a grand (production) repository. Agents cannot modify grand repositories directly.

Grand repositories contain production data. Use a fork instead. Forks are safe, isolated sandbox copies.

هذا ليس موجّه نظام. هذه هي أداة سطر الأوامر نفسها، ترفض الاستدعاء قبل أن يغادر حاسوبي المحمول أصلاً. رأت الأداة أنني وكيل. فـ Claude Code يضبط متغير البيئة CLAUDECODE. كما تتحرك الأداة عبر شجرة العمليات في /proc لاصطياد الوكلاء الذين يحاولون إخفاء هذا المتغير. ثم طابقت العملية مع جدول السياسات لديها. ثم رفضت.

وهنا يفعل الوكيل ما قد يفعله أي وكيل. يحاول ضبط التجاوز بنفسه.

$ REDIACC_ALLOW_GRAND_REPO=demo-stackoverflow rdc term connect ...

ولا يزال الرفض قائماً:

“demo-stackoverflow” is a grand (production) repository. Agent-initiated overrides are not accepted.

Do not attempt to set REDIACC_ALLOW_GRAND_REPO. Only the user can authorize this before the agent starts.

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

اختبرتُ ذلك. خرجتُ من الوكيل. شغّلتُ export REDIACC_ALLOW_GRAND_REPO=demo-stackoverflow في صدفتي الخاصة. أعدتُ تشغيل Claude Code. وعندئذ نجح الاتصال. دخلتُ المستودع كمستخدم النظام غير الممتاز rediacc (UID 7111). وأشار DOCKER_HOST إلى مقبس Docker daemon المحصور النطاق للمستودع الأم.

كذلك جرّبتُ الاتصال بمستودع إنتاجي آخر، nextcloud، بينما كان تجاوز demo-stackoverflow فعّالاً. مرفوض. فالتجاوز مخصص لكل مستودع، وليس مفتاحاً عاماً.

المحاولة الثانية: تفريع المستودع والعمل على التفريع

هذا هو سير العمل الذي يريد Rediacc منك سلوكه فعلاً.

$ time rdc repo fork --parent demo-stackoverflow -m hostinger --tag agent-test

المخرج، منسوخاً من طرفيتي:

Config loaded     (9ms)
Connected         (1.1s)
Renet provisioned (1.2s)
Machine verified  (464ms)
License activated (2.1s)
✔ CoW clone complete (2.3s)

Total: 7.2s

تفريع بحجم 128 جيجابايت في 2.3 ثانية. السبب هو رابط BTRFS reflink. التفريع عبارة عن بيانات وصفية تشير إلى كتل المستودع الأم حتى تكتب إحدى الجهتين. لا تُنسخ أي بيانات.

للتحقق من سلوك التحجيم، أجريتُ التفريع نفسه على مستودع بحجم 2 جيجابايت:

✔ CoW clone complete (573ms)
Total: 6.3s

استغرقت خطوة الاستنساخ في تفريع 2 جيجابايت 573 ميلي ثانية. واستغرقت خطوة الاستنساخ في تفريع 128 جيجابايت 2.3 ثانية. إذن استنساخ البيانات أقل من خطي، وليس ثابتاً تماماً. لكن إجمالي زمن التفريع متطابق تقريباً (6.3 ثانية مقابل 7.2 ثانية)، لأن معظم الانتظار هو رقصة SSH والترخيص، لا البيانات. من مقعد المستخدم، التفريع توقف لمدة 6 إلى 7 ثوانٍ بصرف النظر عن حجم المستودع.

الآن أنا أعمل على التفريع. له GUID خاص به (22c57178-...). و networkId خاص به (21760). ومسار تحميل خاص به. ومقبس Docker daemon خاص به (/var/run/rediacc/docker-21760.sock). بيانات المستودع الأم مشتركة عبر reflink. والمستودع الأم نفسه يبقى يعمل طوال الوقت.

حين أستخدم term connect للدخول إلى التفريع، هذا ما أراه:

$ rdc term connect -m hostinger -r demo-stackoverflow:agent-test -c "id"
uid=7111(rediacc) gid=7111(rediacc) groups=7111(rediacc),988(docker)

ليس مستخدم SSH الخاص بي. بل مستخدم النظام المخصص rediacc، UID 7111، بلا صدفة خاصة به. تعمل الجلسة في فضاء أسماء التحميل الخاص بها. تحققتُ من /proc/self/ns/mnt. وحين حاولتُ سرد مسار تحميل المستودع الأم من داخل جلسة التفريع، أعاد lsPermission denied. الوكيل حرفياً لا يستطيع الوصول إلى بيانات الإنتاج، حتى بتخمين المسار.

هذا هو الجزء الأهم في التصميم. الوكيل يحصل على صندوق رمل على مستوى النواة. ليس تعليمة “من فضلك لا”. إن قرر الوكيل تشغيل rm -rf / داخل التفريع، فأسوأ احتمال هو إعادة تفريع تستغرق 2.3 ثانية من المستودع الأم.

ملاحظة عن دعم المنصات

التجاوز يعمل على Linux فقط. فحص الشرعية يحتاج إلى /proc/<pid>/environ. هذا الملف هو سجل النواة لكيفية بدء كل عملية. ولا يوجد مكافئ له على macOS وWindows. ومع غياب أي طريقة للتحقق من أن التجاوز قد ضُبط من قبلك لا من قبل الوكيل، تفشل الأداة بحالة الإغلاق. حتى التجاوز المضبوط بشكل صحيح يُرفض على هذه المنصات.

ترشدك رسالة الخطأ إلى ما يجب فعله:

The REDIACC_ALLOW_GRAND_REPO override is not supported on darwin. … To use the override, run your agent on Linux (directly, WSL, Docker, or a VM).

عملياً، الوكلاء على macOS أو Windows ليس لديهم أي مخرج من سير العمل القائم على التفريع أولاً. وهذا مقصود.

الحواجز التي صمدت في هذا الاختبار

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

  1. حظر المستودعات الكبرى. لا يستطيع الوكلاء العمل على المستودعات الكبرى (الإنتاجية) مباشرةً. عليهم أن يفرّعوا.
  2. رفض التجاوز الذي يضبطه الوكيل. متغير البيئة الخاص بالتجاوز الذي يستطيع المستخدم ضبطه يُرفض إن ظهر في بيئة الوكيل نفسها.
  3. حصر التجاوز لكل مستودع. منح صلاحية لـ demo-stackoverflow لا يفعل شيئاً لـ nextcloud. النطاق قائمة، وليس مفتاحاً.
  4. صندوق رمل على مستوى النواة. حتى مع تجاوز صالح، تعمل الجلسة بـ UID لـ rediacc، في فضاء أسماء تحميلها الخاص، مع DOCKER_HOST محصور بـ daemon مستودع واحد. لا سبيل لرؤية المستودعات الأخرى.
  5. تفريع متصل. بقي المستودع الأم يعمل خلال التفريع كله. لا توقف، ولا تحويل.
  6. زمن تفريع أقل من خطي. 2.3 ثانية لـ 128 جيجابايت. 573 ميلي ثانية لـ 2 جيجابايت. معظم الانتظار رقصة SSH، لا البيانات.

الشيء الوحيد الذي لا يعزله Rediacc

والآن للجزء الأصعب من المنشور.

يعزل Rediacc البنية التحتية: الملف على القرص، وDocker daemon، وفضاء أسماء التحميل، والشبكة. لكنه لا يعزل واجهات SaaS الخارجية التي يحتفظ مستودعك ببيانات اعتمادها.

التفريع هو نسخة BTRFS reflink بايت ببايت من المستودع الأم. وأي شيء يعيش في data/ أو .env أو secrets/ للمستودع الأم سيكون في التفريع أيضاً. إن كان مستودعك يحوي STRIPE_LIVE_KEY أو AWS_ACCESS_KEY_ID أو رمز Railway API، فالوكيل في التفريع يستطيع قراءتها. ويستطيع استدعاء api.stripe.com أو s3.amazonaws.com أو backboard.railway.app بهذه الرموز. ومن الخارج، يبدو الاستدعاء كأنه آتٍ من الإنتاج. لا تستطيع Stripe أو AWS التمييز بين التفريع والأصل.

هذا هو خط المسؤولية المشتركة. يتولى Rediacc نصف البنية التحتية. ويعيش نصف الخدمات الخارجية في كود تطبيقك.

ثلاثة أنماط تسدّ الثغرة من جانب المطور:

  • لا تخزّن بيانات اعتماد خارجية إنتاجية في المستودع أصلاً. اجلبها من مدير أسرار عند بدء الحاوية. حاويات التفريع تجلب بيانات اعتماد محصورة بالصندوق الرملي بحكم التصميم.
  • جرّد أو بدّل بيانات الاعتماد عند التفريع عبر خطّاف up() في Rediaccfile. خطّاف up() للتفريع يعمل على GUID مستودع مختلف عن المستودع الأم. اكتشف ذلك. ثم أعد كتابة .env بقيم الصندوق الرملي.
  • وفّر موارد خارجية لكل تفريع: حساب Stripe رملي لكل تفريع، وقاعدة بيانات اختبار لكل تفريع، ودلو S3 لكل تفريع.

لو كانت PocketOS على Rediacc، لما كان رمز Railway API هو المقارنة الصحيحة. كانت بنيتهم التحتية ستكون تفريع Rediacc نفسه. ولما كان هناك رمز Railway للعثور عليه، لأن Rediacc لا يكشف أي مكافئ لـ volumeDelete لوكيل مُصادق عليه. كان الوكيل سيعيش داخل مقبس Docker لكل تفريع، دون أي مسار لحذف المستودع الأم.

لكن لو وجد وكيلهم مفتاح Stripe إنتاجي في ملف اعتماد، لما أوقف Rediacc الوكيل عن إصدار استرجاعات على بطاقات عملاء حقيقية. هذه خسارة حقيقية. كلا الأمرين صحيحان.

ما الذي يتغير لمن يقوم بهذا النوع من العمل

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

ما يتغير على Rediacc: نطاق الانفجار التدميري محدود بتفريع. تكلفة خطأ “حذف الشيء الخاطئ” هي إعادة تفريع تستغرق 2.3 ثانية. وتكلفة عدم تطابق بيانات اعتماد يقرر الوكيل “إصلاحها” هي إعادة التفريع نفسها لـ 2.3 ثانية. وصندوق الرمل على مستوى النواة يجعل معظم الأخطاء لا تصل أصلاً إلى بيانات الإنتاج.

ما لا يتغير: إن كان مستودعك يحوي بيانات اعتماد خارجية حية، فالوكيل يستطيع استخدامها. إصلاح ذلك يقع عليك في طبقة التطبيق، لا في طبقة البنية التحتية.

لن أتظاهر بأن Rediacc كان سيمنع كل جزء من حادثة PocketOS. أسوأ جزء في قصة PocketOS كان حذف بيانات Railway دون نسخة احتياطية حقيقية. هذا ما لم يكن ليحدث على Rediacc، لأننا لا نعطي أي وكيل واجهة volumeDelete ليمتد إليها. أما سطح الخطر المتبقي، أي واجهات SaaS التي يستطيع وكيل استدعاؤها ببيانات اعتماد في كودك، فهو الجزء من قصة الأمان الذي يعيش في خطّاف up() لديك. لا في نموذج العزل لدينا.

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