تخطَّ إلى المحتوى

البيئة المعزولة والأمان

يقرأ الوكيل الملفات، ويشغّل أوامر الصدفة، ويستدعي واجهات خارجية نيابة عنك. الفشل الذي يعنيك قلّما يكون خبثًا: وكيل يخطئ قراءة مسار فيعيد كتابة نسخة عملك، أو يدفع فرعًا لم تفرغ من مراجعته. السؤال الذي على المنتج أن يجيب عنه ليس «هل الوكيل موضع ثقة؟»، بل «ما نطاق الأثر حين يخطئ؟».

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

خمسة أمور تحدّ تشغيل الوكيل على كل مضيف، ببيئة معزولة أو من دونها. ليس أي منها حدًا في النواة، وكلها حقيقية. حيث يقدّم المضيف خلفية تقع بيئة نظام التشغيل المعزولة فوقها، وقسم «ما تضيفه البيئة المعزولة وأين تتوقف» أدناه يبيّن ما يتغيّر.

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

ويجدر التدقيق في مستوى هذا الحد: شجرة العمل لكل محادثة لا لكل وكيل. يحصل الوكيلان في المحادثة الواحدة على دليلي عمل متراكبين منفصلين يشتركان في repos/ الخاص بتلك المحادثة، وهذا هو المقصود، فهما يتعاونان على تغيير واحد، بينما تبقى أدلة كل وكيل الخاصة منفصلة. النسخ عند الكتابة هو الخلفية الوحيدة حيثما يُشحَن rift. إن كان نظام الملفات لا يدعم reflink، أو كان دليل البيانات على وحدة تخزين غير وحدة المستودع، فشل التجهيز ولم يرجع إلى بديل. Windows هو الاستثناء: لا خلفية للنسخ عند الكتابة هناك، فالخلفية git worktree، وليست تدهورًا صامتًا.

حصر المسارات في أدوات الملفات لدى الإطار المضمّن. تحل أدوات الإطار read وwrite وedit وapply_patch وsearch وfind وsearch_files كل مسار مقابل دليل عمل التشغيل والجذور المشتركة، دليل repos/ الخاص بالمحادثة حيث تعيش أشجار العمل، ودليل مرفقات المساحة إن وُجد، وترفض كل ما يقع خارجها. لا تُقبل المسارات المطلقة إلا إن كانت داخلها أصلًا. والوصلة الرمزية داخل شجرة العمل، إن أشارت إلى خارجها، تُحَل وتُرفض أيضًا. لهذا لا يستطيع الوكيل تنفيذ write على ~/.ssh حتى من غير بيئة معزولة.

سطح أدوات الوضع. يقرّر وضع المحادثة أي الأدوات تُبنى للتشغيل أصلًا. في أوضاع القراءة فقط (plan وreview وorchestrate) لا تكون الأدوات المعدِّلة في السجل أصلًا، وترفض سياسة أوامر الصدفة في تلك الأوضاع كل أمر معدِّل رفضًا مباشرًا، مثل git commit وgit push وnpm install وdocker وسواها، بدل أن تطلب الموافقة عليها. الأداة الغائبة لا تُستدعى، وهذا ضمان أقوى من أداة حاضرة وتخضع لبوابة.

ضوابط الإجراء الموحّدة. تصرّح كل أداة معدِّلة بأصناف الأثر التي يمكن أن تنتجها، ويحسم محرك السياسة كل صنف إلى allow أو prompt أو deny قبل أن يجري الاستدعاء. يُحسم prompt من غير معتمِد متصل إلى مرفوض. السؤال الذي لا يجد مجيبًا لا يُعدّ موافقة أبدًا. انظر الضوابط لنموذج الحسم.

بيانات اعتماد عبر الوسيط وبيئة معقّمة. تُصدَر بيانات الاعتماد عند كل إطلاق، وتُسحَب حين ينتهي التشغيل. رمز GitHub الذي يحمله التشغيل يقرأ ولا يدفع، انظر «الدفع يمر عبر Control Center» أدناه. يحصل ACP وbash في الإطار وClaude Code تحت التغليف الأصلي على بيئة مجرّدة من متجهات حقن الرابط الديناميكي والمفسّر (LD_PRELOAD وDYLD_INSERT_LIBRARIES وNODE_OPTIONS وGIT_SSH_COMMAND ونظائرها) قبل أن تبدأ العملية. ويرث Claude Code عبر NoSandboxAdapter بيئة المضيف.

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

أداة bash هي الموضع الآخر الذي يجدر تحديده. تُقيَّم أوامرها وفق سياسة أوامر الوضع، ثم تُطلَق ببيئة معقّمة ويكون دليل عملها شجرة العمل. حيث توجد خلفية يُغلَّف هذا الإطلاق، فالبيئة المعزولة هي التي تمنع أمر الصدفة من الكتابة خارج شجرة العمل. حيث لا توجد، على Windows أو على Linux من غير bwrap وsocat، تبقى سياسة الأوامر الفاصل الوحيد بين الأمر وبقية القرص. في وضع chat ترفض السياسة رفضًا قاطعًا قائمة ثابتة، هي تصعيد الامتيازات وتدمير النظام والقرص والهروب من الحاوية ومن فضاء الأسماء، وتطلب الموافقة على تعديلات الحالة البعيدة، git push وgh pr create ونشر الحزم وتثبيتها. ما لا يقع في أي من القائمتين يُنفَّذ. تُحلَّل الأنابيب والسلاسل والأصداف الفرعية، ويُقيَّم كل أمر فرعي على حدة، ويُعتمد القرار الأشد تقييدًا.

Claude Code، حين يُستخدَم محوّلًا، هو الحالة التي تحمل فيها البيئة المعزولة الوزن الأكبر. يطلقه Control Center مع --dangerously-skip-permissions، لأن claude -p غير التفاعلي سيتوقف إلى الأبد على طلب الموافقة الخاص به. تحدث استدعاءات القراءة والكتابة والتحرير داخل عمليته، ولا يراها Control Center. أما استدعاءات Bash فتصل قبل تنفيذها إلى خطاف على الخادم يصنّف سطر الأمر ويسأل القاعدة المطابقة، وأدوات mcp__* التي يقدّمها Control Center نفسه تمر ببوابة كالمعتاد. حيث توجد خلفية تبقى تلك الاستدعاءات غير المرئية داخل تغليف البيئة المعزولة، فنظام التشغيل هو الحد حولها. حيث لا توجد خلفية، فلا شيء يحدّها. يصرّح كل محوّل بهذا في مصفوفة فرضه، انظر مصفوفة صدق المحوّل.

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

الدفع هو الإجراء الذي يجب أن يصمد أمام وكيل يحاول الالتفاف على القاعدة، لذلك يُفرض بما يحمله الوكيل لا بما يشغّله. حين يُضبط تطبيق GitHub، يحصل التشغيل في شجرة عمل GitHub على رمز تثبيت محصور بمستودع واحد، يقرأ المحتوى ويتعامل مع طلبات السحب لكنه لا يدفع. تعيد تهيئة git لدى التشغيل كتابة كل عنوان دفع إلى GitHub (https://github.com/ وgit@github.com: وssh://git@github.com/) ليتجه إلى بوابة تشغيل الوكيل، وهي نقطة loopback على cc_server. تقرأ البوابة المراجع التي يحدّثها الدفع، وتسأل قاعدة الدفع إلى مستودع بعيد، فتنطبق القواعد المقيّدة بالفروع مثل «ارفض الدفع إلى main»، ثم تمرّر الدفع إلى GitHub برمز كتابة تصدره لهذا الغرض ولا تسلّمه للوكيل أبدًا. يظهر الدفع المرفوض في مخرجات git نفسها:

! [remote rejected] main -> main (Control Center policy: …)

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

بقية أوامر الصدفة تلقى بوابة ألين. أداة bash في الإطار، وأداة Bash في Claude Code عبر الخطاف، تصنّفان كل سطر أمر وتسألان القاعدة التي يندرج تحتها: git commit وmerge وrebase وcherry-pick وrevert وam تندرج تحت إنشاء إيداع؛ وgh pr create تحت فتح طلب سحب؛ وgh pr merge وreview وcomment وclose تحت نشر مراجعة أو إجراء دمج؛ وgh issue create وedit وcomment وclose تحت الكتابة إلى متتبع خارجي؛ وتثبيت الحزم تحت تثبيت حزمة؛ وcurl وwget تحت الوصول إلى الشبكة. يلتقط هذا الصيغ المعتادة للإجراء، والوكيل العازم على إخفائه (eval، أو ملف نصي، أو استدعاء API مباشر) يستطيع ذلك. الدفع لا يعتمد على هذا.

لا يوضع أي مفتاح API للتذاكر في البيئة المعزولة. يكتب الوكلاء التذاكر عبر أدوات MCP في Control Center، وهي تسأل قاعدة الكتابة إلى متتبع خارجي.

تُقرأ قاعدة الوصول إلى الشبكة مرة واحدة عند بدء التشغيل. شبكة البيئة المعزولة إما مشغّلة أو مطفأة، فالرفض يطفئها، والسماح والسؤال أولًا يبقيانها مشغّلة، وتظل أداتا الإطار web_fetch وweb_search تسألان عند كل استدعاء. تُسحَب بيانات الاعتماد حين يُفكَّك التشغيل، ومعها كل رمز كتابة أصدرته البوابة، فلا يعيش المقبض أطول من التشغيل.

ما تضيفه البيئة المعزولة وأين تتوقف

قسم بعنوان “ما تضيفه البيئة المعزولة وأين تتوقف”

للتعداد SandboxBackend ثلاث قيم: native (Seatbelt على macOS، وbubblewrap على Linux وWSL2)، وnone، وmicrovm. تغليف أوامر الوكيل لا يستخدم إلا الأوليين. microvm خلفية جهاز VM في حاوية معزولة، للطرفيّات التفاعلية والمنصات، لا ترقية صامتة لتغليف bash. فهم الشكل الأصلي يفسّر ما تكسبه حيث تُلحَق خلفية، وما لم يكن ليُعطيك أصلًا.

يفحص cc_server المضيف مرة واحدة عند الإقلاع، وحيث تجيب خلفية يغذّي وصلتي مسار الإرسال كلتيهما، لأن قناتي النقل لا تشتركان في وصلة واحدة. الأولى (SandboxPort) تغلّف سطر أوامر Claude Code. والثانية (SandboxManager) تغلّف محوّلات ACP وأداة bash في الإطار المضمّن. الاكتفاء بتغذية واحدة يترك الباقي يُطلَق بلا تغليف، وهذا ما كان يحدث. على مضيف بلا خلفية، أو مع --sandbox off، يُطلَق الجميع بلا تغليف، ويقول الخادم أي الحالتين أنت فيها على سطر إقلاعه. ويعقّم ACP وbash في الإطار البيئة مع ذلك، ويرث Claude Code عبر NoSandboxAdapter بيئة المضيف.

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

على Linux يبني bubblewrap فضاء أسماء للتركيب، فيرى الوكيل فعلًا ما رُبط فيه فقط. على macOS يطبّق sandbox-exec ملف Seatbelt متساهلًا افتراضيًا. الملف الذي يرفض كل شيء افتراضيًا يجعل الصدفة التفاعلية غير صالحة للاستعمال، لأن تعداد استدعاءات dyld وXPC وMach غير عملي. لذلك يبدأ ملف macOS من (allow default) ويقتطع منه الرفض: قراءة المسارات السرية، وإعادة ضبط سياسة الكتابة إلى الرفض مع سماح صريح فوقها، وحظر unlink على كل مسار مرفوض وعلى أسلافه حتى لا تتجاوز إعادة التسمية رفض الكتابة، ورفض التنفيذ للثنائيات الخطرة دائمًا ولكل ما يُنسَخ إلى دليل قابل للكتابة، وحصر الشبكة في منافذ بروكسي loopback إضافة إلى DNS.

النتيجة العملية: على Linux يضيق ما يراه الوكيل من نظام الملفات، وعلى macOS يظل قادرًا على قراءة معظم المضيف، وتُحمى نسخ العمل المسجّلة برفض صريح للكتابة ولإعادة التسمية، لا بإخفائها. كلاهما عزل على مستوى فضاء الأسماء، لا حد أمان في النواة، والخلفيات تقول ذلك في ملاحظات قدراتها.

مجموعة تركيبات الربط هي الموضع الذي يُرسَم فيه الحد بين الوكلاء. يركّب إرسال التراكب (Claude Code وACP) ما يصل إلى أربعة مسارات، تُحَل في كل تشغيل: دليل عمل الوكيل المتراكب للقراءة والكتابة، ودليل إعداد الوكيل للقراءة فقط، ودليل repos/ المشترك للمحادثة للقراءة والكتابة إن وُجد، ودليل مرفقات المساحة للقراءة فقط إن وُجد. لا يُركَّب دليل وكيل شقيق أبدًا. تغليف bash في الإطار أضيق، فلا يركب ربطًا إلا دليل عمل التشغيل. هذا الشكل نفسه الذي يفرضه فحص المسار داخل العملية في البرمجيات، فيتفق الشكلان، لكن فحص المسار وحده يغطي أدوات الملفات الخاصة بالإطار، وهي لا تبلغ فضاء أسماء تركيب أصلًا.

وتنضم نسخ العمل المسجّلة إلى مجموعة رفض الكتابة فوق ذلك، في كل وضع وعلى المسارات المغلَّفة الثلاثة (Claude Code وACP وbash في الإطار)، وتُصدَر كل قاعدة بصيغتين، الحرفية وبعد حل الوصلة الرمزية. الصيغة الثانية ليست تحسينًا شكليًا. تطابق النواة قاعدة الرفض مع المسار المحلول، ويجعل macOS الحل الحالة الشائعة لا النادرة: /tmp هو في الحقيقة /private/tmp، و/var/folders/… هو في الحقيقة /private/var/folders/…. فنسخة العمل التي تُبلَغ عبر أي منهما كانت ستبقى قابلة للكتابة، بينما يبدو ملف التعريف مانعًا لها.

الشبكة، تحت بيئة معزولة، قائمة سماح بالنطاق لكل جلسة، تفرضها بروكسيات loopback داخل العملية. الخروج مرفوض افتراضيًا: مع إطفاء الشبكة تظل البروكسيات تبدأ لكن قائمة سماحها فارغة، فيُرفض كل اتصال صادر. مع تشغيلها يبني المحلّل قائمة السماح من خط أساس منتقى، مزوّدو واجهات نماذج اللغة واستضافة git وسجلات الحزم وفهرس النماذج، إضافة إلى نطاقات GitHub، ويطابق مضيف وجهة كل اتصال مع القائمة. أحرف البدل تعمل (*.example.com يطابق النطاقات الفرعية لا نطاق القمة). قائمة رفض دائمة منفصلة تغلب قائمة السماح، وتغطي نقاط بيانات التعريف السحابية ووجهات القياس عن بُعد. ما هو غير قابل للتهيئة هو القائمة نفسها: تحمل المواصفة حقل egressAllowlist لنطاقات إضافية، ولا شيء في مسار الإرسال يملؤه، فيحصل كل تشغيل عمليًا على خط الأساس مضافًا إليه GitHub، ولا شيء بعد ذلك.

حالات الرفض على macOS يمكن رصدها. يقرأ مراقب سجل النظام بحثًا عن رفض Sandbox، ويصفّي الضوضاء المعروفة، ويزيل تكرار عواصف إعادة المحاولة، ويُظهر كل حالة على هيئة SandboxViolation تسمّي ما جرت محاولته وما الذي حُجب. على Linux يحلّل مسار التغليف stderr بحثًا عن EPERM وOperation not permitted وPermission denied، ويصدر نوع الحدث نفسه، بلا التقاط لسجل النظام. تصل هذه الأحداث إلى مجرى أحداث التشغيل نفسه، فيظهر استدعاء النظام المرفوض في نص المحادثة لا في سجل فحسب. لا يوجد هذا القياس إلا حين يغلّف العزل الأصلي العملية فعلًا. على مضيف بلا خلفية لا يوجد ما يُرصَد. وهو منفصل عن الضوابط: المخالفة تأتي من استدعاء نظام مرفوض، لا من استعلام عن قاعدة.

التأكيدات تأتي من الضوابط لا من البيئة المعزولة

قسم بعنوان “التأكيدات تأتي من الضوابط لا من البيئة المعزولة”

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

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

تشخيص وكيل هو الموضع الوحيد الذي يبيّن ما يمكن فحصه يدويًا.