Hub Python Library documentation
الصناديق الرملية تحت الغطاء
الصناديق الرملية تحت الغطاء
يشرح هذا الدليل كيفية عمل Sandboxes داخليًا، ولماذا بُنيت بالطريقة التي هي عليها، وما هي قيودها. إذا كنت ترغب فقط في استخدام sandboxes، فإن دليل Sandboxes كافٍ؛ تابع القراءة إذا كنت ترغب في فهم الآلية، أو تقييم نموذج الثقة، أو تصحيح خطأ ما.
واجهة برمجة التطبيقات الخاصة بـ Sandbox تجريبية. قد تتغير واجهتها وسلوكها دون إشعار مسبق.
لا يوجد “خدمة بيئة معزولة”
أول ما يجب فهمه هو أنه لا يوجد خادم خلفي مخصص للبيئة المعزولة. البيئة المعزولة ليست سوى HF Job (آلة افتراضية) تشغّل ملفًا ثنائيًا صغيرًا ثابتًا واحدًا هو sbx-server ويتحدث بلغة HTTP. يتواصل العميل مع ذلك الخادم عبر وكيل المهام (عنوان *.hf.jobs الذي تعرضه المهمة). أما كل ما عدا ذلك — المصادقة، والاكتشاف، وتجميع العديد من البيئات المعزولة في مهمة واحدة — فهو مبني على عناصر المهام الموجودة مسبقًا: التسميات، ومتغيرات البيئة، والأسرار.
إن تصميم “عدم الحاجة إلى بنية تحتية جديدة” هو السبب في أن بيئة العزل تعمل في أي صورة Docker وتورث فوترة مهام Jobs وأنواع الأجهزة وأذونات مساحة الاسم مجانًا.
تهيئة الخادم
عند بدء تشغيل المهمة، تكون أمر المهمة عبارة عن سكربت صغير /bin/sh -c يجلب الثنائي sbx-server، ويجعله قابلاً للتنفيذ، ثم execه. الثنائي هو بناء ثابت بحجم ~640KB باستخدام musl دون أي تبعيات وقت تشغيل، لذا يعمل في أي صورة لينكس x86_64.
بعض القرارات التي تستحق الإشارة إليها:
- التنزيل، مع خيار احتياطي عبر نقطة التثبيت. المسار السريع ينزّل الملف الثنائي من شبكة توصيل المحتوى الخاصة بـ HF باستخدام
wgetأوcurl(كل صورة أساسية شائعة تتضمن أحد هذين الأداة)، وهو سريع ومجاني. وكإجراء احترازي، يتم تثبيت مستودع الخادم في Hub كحجم على كل مهمة: إذا لم تتضمن الصورة أيًا منwgetأوcurl، ينسخ السكربت الملف الثنائي من تلك نقطة التثبيت بدلاً من ذلك. التثبيت شفاف — لا يتكبد أي تكلفة ما لم يُقرأ فعليًا — لكن قراءته يضيف حوالي 2-3 ثوانٍ إلى بدء التشغيل البارد، ولهذا السبب لا يُعد الخيار الافتراضي. المتطلب الصارم الوحيد على الصورة هو/bin/sh. - خادم HTTP/1.1 مكتوب يدويًا، بدون إطار عمل. يتطلب بث المخرجات الحية تفريغ كل جزء فور إنتاجه. تُخزّن خوادم HTTP البسيطة الشائعة بلغة Rust (مثل
tiny_http) الاستجابات المقسّمة في ذاكرة مؤقتة حتى اكتمال الاستجابة، مما يُعطل البث. لذا، ينفّذ الخادم بروتوكول HTTP/1.1 يدويًا: تدفقات أحداث NDJSON لـexec، وأجسام خام للملفات، مع تفريغ صريح لكل جزء. - المنفذ 49983. يستمع الخادم إلى منفذ غير شائع عمدًا حتى تبقى المنافذ الشائعة للتطوير (3000، 8000، 8080، …) متاحة لأكوادك الخاصة.
الخادم مفتوح المصدر في github.com/huggingface/sandbox-server.
المصادقة غير ذات حالة
تحمي طبقتان مستقلتان بيئة العزل:
- بوابة الوكيل. يقوم وكيل Jobs بإعادة توجيه الطلبات التي تحمل رمز HF مع صلاحيات القراءة إلى مساحة اسم المهمة فقط. لا يمكن لأي عضو عشوائي من الإنترنت الوصول إلى عنوان URL.
- بوابة التطبيق. كما يتحقق
sbx-serverمن ترويسةX-Sandbox-Tokenفي كل طلب باستثناء/health. وهذا يمثل طبقات دفاع متعددة: فعضو مساحة الأسماء للقراءة فقط الذي يستطيع الوصول إلى الوكيل لا يزال غير قادر على تنفيذ الأوامر.
يُشتق رمز بيئة العزل ولا يُخزَّن:
nonce = random 128-bit hex # stored in the job label "hf-sandbox-nonce" token = HMAC-SHA256(key=your_hf_token, msg="hf-sandbox:" + nonce)
القيمة العشوائية (nonce) عامة ومفتاح HMAC هو رمز HF الخاص بك، لذا يمكن لأي جهاز يحمل ذلك الرمز إعادة حساب رمز بيئة العزل — وهذا بالضبط ما يجعل إعادة الاتصال بلا حالة. كما يعني ذلك أن الرمز هو صلاحية مشتقة من بيانات الاعتماد الخاصة بك، وليس فحصًا لهوية المستدعي: فالخادم يتحقق من أن المستدعي يعرف الرمز، وليس من هويته.
كل مهمة بيئة اختبار تحمل أيضًا وسمين ثابتين للاكتشاف — hf-sandbox=1 (على جميعها) وhf-sandbox-mode=dedicated أو hf-sandbox-mode=pool — مما يتيح لك إحصاءها أو تصفيتها على جانب الخادم، مثل hf jobs ps --label hf-sandbox=1.
يتم تسليم الرمز إلى الخادم عبر سر مهمة. يعيد العميل اشتقاقه عند الطلب من الرقم العشوائي العام في التسمية. لهذا بعض العواقب الإيجابية:
- إعادة اتصال بلا حالة. يعمل
Sandbox.connect(id)من أي جهاز يحمل نفس رمز HF — اقرأ الرقم غير المتكرر من التسمية، ثم أعد حساب الرمز. لا ملفات محلية، ولا حالة يجب نسخها. - لا يتم تمرير رمز HF إلى بيئة العزل كمتغير بيئة أو سر مهمة (إلا إذا اخترت ذلك باستخدام
forward_hf_token=True). هذا ليس ضمانًا صارمًا بأن بيانات الاعتماد الخاصة بك تبقى بعيدة عن المتناول: العملية التي تستمع على منفذ بيئة العزل هي ما يبدأه النظام الأول، لذا قد تكون صورة غير موثوقة قادرة على مراقبة الطلبات التي يرسلها العميل — بما في ذلك ترويسةAuthorizationالخاصة بها. تعامل مع بيانات الاعتماد القابلة للوصول من بيئة العزل على أنها معرّضة محتملًا لها.
نطاق الرمز
يُصدر رمز غير قابل لإعادة الاستخدام (nonce) واحد لكل Job، لذا فإن الرمز المشتق هو اعتماد خاص بـ job. في الوضع المخصص، يكون الـ job هو بيئة العزل (sandbox)، وبالتالي يتطابق الاثنان. في وضع المسبح (pool)، يكون الـ job هو المضيف (host)، ويحتوي المضيف على العديد من بيئات العزل — لذا تحصل بيئة العزل في المسبح على اعتماد ثانٍ أضيق نطاقًا خاص بها:
| بيانات الاعتماد | كيف تم اشتقاق | يُصرّح |
|---|---|---|
| رمز عربة اختبار مخصص | HMAC(hf_token, nonce) من تسمية المهمة | ذلك الصندوق الرملي (وهو المهمة بأكملها) |
| رمز مضيف الحوض | HMAC(hf_token, nonce) من تسمية مهمة المضيف | إدارة المجموعات: إنشاء علب الرمل على ذلك المضيف، وعرض قوائمها، وحذفها، واستعادة رموزها |
| رمز صندوق الرموز المجمّع | 256 بت عشوائية، تُنشأ من قِبل الخادم المضيف لكل بيئة معزولة | ذلك العزل الواحد — وليس العزل المتجاور، وليس مجموعة العزل |
يستخدم العميل الضيق تلقائيًا: يستلمه pool.create() في استجابة الإنشاء، ويستعيده Sandbox.connect("<host>.<id>") باستخدام رمز المضيف، بينما يسلّم proxy_headers رمز بيئة العزل بدلاً من رمز المضيف (مع حلّ رمز HF المحمول في اللحظة التي تقرأها، حتى لا يسلّم مقبض طويل العمر رمزًا قديمًا) — وغالبًا ما تنتهي هذه الترويسات في متصفح أو عميل WebSocket، لذا يجب أن تمنح الوصول إلى بيئة عزل واحدة فقط ولا شيء آخر.
ما يعنيه ذلك في حالة تسريب: يُعرّض رمز العزل المُجمَّع ذلك العزل للخطر. أما رمز المضيف فيُعرّض المضيف للخطر — أي كل العزل الموجود عليه، الحالي والمستقبلي، بالإضافة إلى مسارات إدارته — لذا تعامل معه باعتباره بيانات اعتماد المسؤول الخاص بالمجموعة. أعضاء مساحة الأسماء الخاصة بك يحملون رمز HF مختلف ولا يمكنهم اشتقاق رمزك، لكن راجع القيود المعروفة لمعرفة كيف يمكن أن يُسلَّم الرمز إلى المكان الخطأ.
يتطلب العميل رمز نطاق رمل غير فارغ للعمليات المجمّعة ويرفض المستضيفين الذين لا يوفرونه. ولا يستبدل أبدًا بيانات اعتماد إدارة المستضيف. قم بترقية العميل وإعادة تدوير مستضيفي المجموعة الأقدمين قبل استخدام هذا الواجهة البرمجية.
صناديق رملية مخصصة (Sandbox.create)
النموذج المباشر: مهمة واحدة لكل بيئة معزولة. المهمة هي جهاز افتراضي حقيقي، لذا يمنح هذا النموذج أقوى مستوى من العزل (على مستوى الجهاز الافتراضي)، ويدعم أي نوع من العتاد بما في ذلك وحدات معالجة الرسومات، وهو الخيار الأنسب للأكواد غير المتبادلة الثقة. مسارات واجهة برمجية التطبيقات الخاصة به موجودة في /v1/* ومسارات الملفات مطلقة على نظام ملفات الحاوية. kill() يلغي المهمة ببساطة.
الحدود التي يمنحكها صندوق العزل المخصص هي بين المهمة وكل ما هو خارجها. داخل آلة الافتراض، يعمل كودك وsbx-server كلاهما بصلاحيات root في نفس الحاوية: sbx-server لا يعزل الأمر الذي ينفذه نيابةً عنك، بل يعرضه فقط عبر HTTP. لذا، بمجرد تنفيذ كود لا تثق به داخل صندوق عزل مخصص، تعامل مع تلك الآلة الافتراضية — بما في ذلك مستوى التحكم فيها — على أنها تابعة لذلك الكود: إذ يمكنه إنهاء عمل الخادم أو استبداله، وأي شيء تقوم به في ذلك الصندوق العزل بعد ذلك يتم تحت سيطرته. استخدم صندوق عزل مخصص واحدًا لكل مهمة غير موثوقة، ثم kill() عند الانتهاء، بدلاً من إعادة استخدام صندوق واحد عبر حدود الثقة.
تكلفة الأمر واضحة في المخطط: كل بيئة اختبار تدفع ثمن بدء تشغيل كامل لآلة افتراضية يستغرق حوالي 6 ثوانٍ، وتُحتسب عليها فاترة كاملة لجهاز. بالنسبة لبيئة اختبار واحدة أو حمولة عمل تعتمد على وحدة معالجة رسومية، فهذا بالضبط ما تريده. أما بالنسبة لمهام CPU قصيرة الأمد بعدد يتراوح بين 100 و1000 مهمة، فهو هدر — وهذا هو الغرض من استخدام المجموعات.
المجموعات: العديد من بيئات العزل في مهمة واحدة ( SandboxPool )
تُستخدم المجموعات (Pools) للأحمال التشغيلية داخل حدّ ثقة واحد. صندوق الرملي المُجمَّع هو معرّف مستخدم (uid) + مجموعة قواعد Landlock داخل آلة افتراضية مشتركة، وليس آلة افتراضية مستقلة، ويعمل مضيفه بمستوى تحكم مميّز تتواصل معه جميع صناديقه الرمليّة. استخدم المجموعات لنشر كودك الخاص بتكلفة منخفضة. بالنسبة للأحمال التشغيلية التي لا تثق ببعضها — أي شيء لن تسمح له بقراءة ملفات صناديقك الرمليّة الأخرى — استخدم Sandbox.create()، والذي يمنح كل حمل تشغيلي آلة افتراضية خاصة به. راجع القيود المعروفة لمعرفة الفجوات المحددة.
تحتاج بيئة تنفيذ نموذجية لتجربة التعزيز أو صندوق أدوات التنفيذ إلى بضع ميغابايتات من ذاكرة الوصول العشوائي ونواة واحدة لبضع ثوانٍ. إن دفع تكلفة جهاز افتراضي بمعالجين مركزيين اثنين ووقت بدء تشغيل بارد مدته 6 ثوانٍ لكل حالة — مع إحداث دفعة جدولة لألف جهاز افتراضي — هو خيار غير مناسب. لذلك، يعمل SandboxPool على تشغيل مهمة واحدة كعقدة مضيف وتعدد استخدامات العديد من صناديق الأدوات داخلها.
لا تُعدّ بيئة العزل المجمّعة (pooled sandbox) آلة افتراضية متداخلة أو حاوية. بل هي الأصل الأساسي متعدد المستخدمين في نظام يونكس الكلاسيكي:
- a dedicated uid (≥ 20000),
- منزل خاص
0700مملوك لذلك المعرّف، - الأوامر
execالمنفذة بتلك المعرّف (uid) مع بيئة مُنظّفة (env_clear، حتى لا تسرّب أسرار المضيف)، وNO_NEW_PRIVS، وrlimits لكل عملية، ومجموعة قواعد Landlock لكل صندوق رمل.
لذلك، إنشاء بيئة معزولة هو mkdir + chown + build ruleset ≈ 1ms على جانب الخادم — دون إعادة تشغيل ثانية للآلة الافتراضية. التأخير الوحيد الذي يراه العميل هو زمن ذهاب وإياب الوكيل.
المعرّف العام لبيئة العزل المشتركة هو <host_job_id>.<local_id>، لذا تعمل أوامر connect/exec/kill بشكل غير معتمد على الحالة تمامًا مثل البيئات المخصصة. عند استدعاء kill() على بيئة عزل مشتركة، يُرسل طلب DELETE إلى مضيفها (لتحرير خانة)؛ بينما يستمر المضيف في العمل.
العزل في مجمّع: uid + Landlock
هذا هو جوهر تصميم المسبح، لذا يستحق الأمر الدقة في توضيح ما هو معزول وما ليس كذلك.
تعمل مهمة Job قياسية بصلاحيات root داخل مساحة أسماء مستخدمين (user namespace) تُخاطب معرفات المستخدمين من 0 إلى 65535 فقط، مع تفعيل مرشح seccomp وبدون CAP_SYS_ADMIN / CAP_NET_ADMIN / CAP_NET_RAW. وهذا يستبعد أدوات العزل الثقيلة المعتادة: لا مساحات أسماء متداخلة، لا تركيبات جديدة، ولا تفويض لمجموعات التحكم (cgroups) (فكل من unshare وmount والكتابة إلى /sys/fs/cgroup/... تفشل). ما يقدمه نظام التشغيل هو Landlock (ABI 6)، وهو وحدة أمان لينكس (Linux Security Module) تتيح لأي عملية غير مخصصة تقييد نفسها وأبنائها — وهو بالضبط حد العزل لكل رملية (sandbox) الذي نحتاجه. لكل رملية، يبني الخادم مجموعة قواعد؛ ثم تنزل العملية الفرعية المنفذة (exec child) إلى معرفي المستخدم/المجموعة (uid/gid) الخاصين بالرملية وتطبق NO_NEW_PRIVS وlandlock_restrict_self وحدود الموارد (rlimits) قبل تنفيذ الأمر.
يُصمَّم ويُختبَر دمج المعرّفات الفريدة (uids) المختلفة (التحكم في الوصول التقديري) مع Landlock لتوفير:
- ✅ لا يمكن للعملية A قراءة
environالخاص بعملية أخرى، مما يمنع الوصول المباشر إلى رموز HF ورموز العزل بين بيئات العزل. - ✅ لا يمكن لـ A تنفيذ
SIGKILL/ptrace/ قراءة ذاكرة عمليات B، أو استخدامsetuidللدخول إلى حساب B، أو قراءة مجلد B الشخصي ذي الصلاحيات0700. - ✅ يُمنع الوصول إلى
/tmpو/dev/shm— كل بيئة معزولة محصورة بواسطة Landlock في مجلد المستخدم الخاص بها (يشيرTMPDIRالخاص بها إلى داخل$HOME). - ✅ لا يمكن لـ A
bindمنفذ TCP، لذا لا يوجد خدمة localhost بين بيئات العزل (يبقى الاتصال الخارجيconnectمسموحًا به، لذا تعمل الإنترنت). - ✅ يتم حظر مقابس يونكس المجردة عبر الصناديق الرملية (
LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET؛ فالتعزل المعتمد على المعرّف uid وحده لا يحظر هذه المقابس).
تقيّد هذه الضوابط حجم العمل. يبقى مستوى التحكم الجذري حدّ ثقة منفصلاً: فتُرفض عمليات الملفات في وضع المضيف التي تستخدم الروابط الرمزية، ويثبّت وكيل المقبس (socket) عقدة inode للمقبس ويتحقق من معرّف uid الطرف الآخر، كما أن المسارات المخصصة غير متاحة في وضع المضيف. تُوصف القنوات المشتركة المتبقية في القيود المعروفة.
لماذا لا يُعدّ هذا بديلاً عن آلة افتراضية. صُمّمت عزلات Landlock وعزلات uid للأحمال العاملة ضمن حدّ الثقة نفسه. وبما أن الصناديق الرملية المجمّعة تتشارك نواةً واحدةً، وآلةً افتراضيةً، ومستوىَ تحكم، فإن الحماية من كل هجوم بين الصناديق الرملية ليست مضمونة؛ كما تبقى بعض الموارد وبيانات الوصف الخاصة بقائمة العمليات مشتركة. أما بالنسبة إلى الأكواد غير المتبادل الثقة، أو لاستخدام GPU، فاستخدم Sandbox.create()، إذ يمنح كل صندوق رملي آلةً افتراضيةً خاصةً به.
ملف النموذج في مجموعة
بما أن المنطقة القابلة للكتابة الوحيدة في صندوق الرملي المجمّع هي منزله المقيّد بواسطة Landlock (والذي يُمثّل أيضًا مجلده التشغيلي الافتراضي)، فإن واجهة برمجة الملفات تُجذّر كل مسار في ذلك المنزل: files.write("data/in.txt", ...) يكتب إلى $HOME/data/in.txt، ويُعتبر / في البداية نسبيًا بالنسبة للمنزل، ولا يمكن لـ .. الخروج منه. تُمكَّن الملفات المكتوبة عبر الواجهة من chown إلى معرّف المستخدم الخاص بصندوق الرملي بحيث يمكن لأكواد الصندوق نفسه قراءتها. وهذا يمنح نموذجًا نظيفًا لنظام ملفات «مُجذّر في صندوق الرملي» يتطابق تمامًا مع ما يمكن للأكواد داخل الصندوق الوصول إليه — وهو يختلف عن صناديق الرملي المخصصة، حيث تكون المسارات مطلقة على نظام ملفات الحاوية.
تُطَبَّع المسارات لفظيًا، ثم تُحَلّ نسبةً إلى مُصِفِّ مجلد المنزل المفتوح مع رفض الروابط الرمزية في كل مكوّن. لا يتتبّع واجهة برمجة الملفات الروابط الرمزية، بما في ذلك الروابط داخل نفس مجلد المنزل؛ غير أن أكواد العزل يمكنها ما زال استخدامها ضمن قواعد Landlock الخاصة بها.
لا تمتلك المجموعات حالة محلية مرجعية
المجموعة (Pool) ليست ملف إعدادات محليًا بشكل مقصود. المجموعة هي مجموعة من مهام (Jobs) المضيفات العاملة، جميعها تتشارك وسم hf-sandbox-pool=<id>. وهذا يحافظ على اتساق المجموعات مع بقية واجهة برمجة تطبيقات (API) بيئة العزل (sandbox) (حيث يمكن اكتشاف كل شيء من الأوسمة وإعادة ربطه من أي جهاز)، ويعني أن المجموعة تتوقف عن الوجود ببساطة بمجرد اختفاء مضيفها الأخير.
- يحمل المضيف إعدادات المجموعة (الصورة، النوع،
sandboxes_per_host، مهلة الخمول) في متغيرات البيئة الخاصة بمهمته — وتُستخدم التسميات فقط للتصفية. عندما يجب على العميل تشغيل مضيف مكرر، فإنه يقرأ ذلك الإعداد من مضيف قيد التشغيل (inspect_job)، مما يضمن بقاء جميع المضيفين في المجموعة متسقة دون الحاجة إلى سجل مركزي. - تُعد المتغيرات البيئية والأسرار خاصة بكل بيئة معزولة (sandbox) وتُمرَّر عند الإنشاء — وليس على مستوى مجموعة الموارد (pool). تُخزَّن البيئة الخاصة بالبيئة المعزولة ضمن مجموعة الموارد في ذاكرة الخادم المضيف طوال عمر تلك البيئة المعزولة (حيث تُعاد تطبيقها على كل أمر تنفذه)؛ ولا تُكتب إلى القرص أبدًا ولا تظهر في بيانات الوصف (metadata) لأي مهمة (Job). لا يوجد قناة أسرار مشفّرة للبيئات المعزولة ضمن مجموعة الموارد — استخدم
envواعتبر تلك القيم «غير مخزنة على القرص، لكنها غير مشفّرة أيضًا». أما للقيم التي تتطلب مخزنًا مشفّرًا، فاستخدمsecretsالخاص ببيئة معزولة مخصصة. - السعة مُعتمدة من الخادم. يرفض المضيف عمليات الإنشاء التي تتجاوز
sandboxes_per_host(مع الرد{"rejected": N})؛ ويجمع العميل الفائض على مضيف آخر أو يُشغّل نسخة مكررة. وهذا يحافظ على دقة التجميع حتى عندما تنشئ عدة عمليات في نفس الحوض بشكل متزامن. - تتم الإخلاء عند الخمول على مستويين. يُخلى كل صندوق رمل بعد فترة خمول خاصة به محددة بـ
idle_timeout(ما لم يكن لديه عملية قيد التشغيل بعد)؛ وبمجرد أن لا يحتوي المضيف على أي صناديق رمل لفترة خمول المضيف، يُغلق نفسه — وهي آلية احتياطية للفوترة حتى في حال اختفاء جميع العملاء.
يحافظ كاش يُبذل فيه جهدٌ أقصى على سرعة أمر create --pool
إن عدم وجود حالة محلية مرجعية أمر ممتاز من حيث الصوابية لكنه يأتي على حساب زمن الاستجابة. فعملية hf sandbox create --pool <id> الباردة (عملية CLI جديدة) ستضطر، في حال غياب هذه الحالة، إلى إعادة اكتشاف كل شيء عبر الشبكة قبل أن تتمكن من إنشاء بيئة العزل: list_jobs لمسح مساحة الأسماء ← inspect_job لكل مضيف لإعادة بناء عنوان URL ورمز nonce الخاص به ← GET /v1/sandboxes لمعرفة مدى امتلاء كل بيئة ← وأخيرًا POST لإنشاء البيئة. عدة رحلات ذهابًا وإيابًا من النفقات البحتة، في كل استدعاء.
تزيل ذاكرة تخزين مؤقتة تُدار بأقصى جهد موجودة في $HF_HOME/sandbox/pools/<context>/<pool-id>.json ذلك. بعد أي عملية إنشاء أو تسخين، تسجّل عملية الإعدادات الخاصة بالمجموعة بالإضافة إلى، لكل مضيف، عنوان البروكسي الخاص به، ورمز المصادقة العشوائي، وآخر عدد من الفتحات المتاحة التي شوهدت. تعيد العملية التالية بناء نقل المضيف مباشرةً من الملف (بدون HTTP) وتنتقل مباشرةً إلى POST.
<context> هو ملخص لنقطة النهاية والاعتمادات والمساحة الاسم التي كُتبت لها الإدخالة — الاعتمادات بصيغة بصمة؛ حيث لا يُكتب الرمز نفسه أبدًا إلى ذاكرة التخزين المؤقت. يحمل كل من اسم المجلد والملف هذا الملخص، والقراءة التي لا تتطابق مع جميع العناصر الثلاثة تُعدّ فشلًا عاديًا في ذاكرة التخزين المؤقت، فيحصل مستخدم آخر للجهاز، أو نقطة نهاية أخرى، أو connect(pool, namespace="other-org") على المسار البارد بدلًا من العُقد التي خُزّنت مؤقتًا لشيء آخر. تُنشأ الملفات بصلاحية 0600 والمجلدات بصلاحية 0700.
ما يُعتمد على الكاش وما لا يُعتمد عليه:
- غير معتمد فيما يتعلق بالسعة. يبقى خادم المهمة الداخلية هو المرجع المعتمد، لذا فإن عدد
liveقديم لا يتسبب إلا في طلب ضائع، وليس في استضافة غير مجهزة بشكل صحيح. - لا يُعتمد عليه لتحديد موقع استضافة العُقدة. لا يُستخدم عنوان URL المخزَّن مؤقتًا إلا إذا كان عنوان HTTPS الخاص بوسيط مهام (jobs-proxy) للمهمة التي تحمل الاسم نفسه في ذلك السجل، فلا يمكن لملف الكاش أن يختار الوجهة التي تُرسل إليها رموز HF bearer ورمز بيئة العزل (sandbox token) الخاصة بك؛ أما أي سجل يشير إلى أي شيء آخر فيُستبعد من الملف. وبعد مرور 15 دقيقة، لم يعد يُعتمد على السجل بمفرده أيضًا: يجب على
inspect_jobتأكيد أن المهمة ما زالت قيد التشغيل، وما زالت مُصنَّفة لهذا المسبح (pool)، وما زالت على ذلك العنوان، قبل بناء طبقة النقل. - غير موثوق به من حيث شكله. يتم فحص الأنواع والنطاقات عند القراءة، لذا فإن الملف الذي تم تحريره يدويًا أو تم اقتطاعه أو يحتوي على أنواع نصية هو فشل في التخزين المؤقت وليس خطأً في منتصف عملية
create(). - التعافي الذاتي. يُستبعد المضيف المخزَّن مؤقتًا الذي لم يعد موجودًا عند أول طلب فاشل ويُحذف من الملف؛ ويعود create بشكل شفاف إلى اكتشاف التسميات.
- آمن من حيث التزامن. يتم دمج عمليات الكتابة تحت قفل ملف (مفهرس بـ
job_id) وتُثبَّت بشكل ذرّي، لذا لا تتداخل عملياتcreateالمتوازية مع بعضها البعض ولا يرى القارئون ملفًا غير مكتمل الكتابة. - مُتقنٍ. احذفه وستحصل على فشل في التخزين المؤقت: يُنفَّذ المسار البارد وتعمل كل الأشياء بشكل طبيعي. لا يتم مشاركته بين الأجهزة أبدًا.
شيئان يجب وضعهما في الاعتبار. تعامل مع $HF_HOME كمعلومة حساسة: فالبند لا يزال عبارة عن عنوان مضيف قابل لإعادة الاستخدام بالإضافة إلى الرقم غير السري العام الذي يُشتق منه الرمز، ورغم أن الملف المكتوب تحت بيانات اعتماد مختلفة يُتجاهل، فإن الملف المكتوب تحت بياناتك يُحتسب ضمن نافذة الخمس عشرة دقيقة المذكورة أعلاه. وبما أن مساحة الأسماء جزء من المفتاح، فإن المجموعة المخزنة مؤقتًا تحت namespace= صريحة لن تُعثر عليها مرة أخرى إلا عند تمرير namespace= نفسه — حيث يتطلب الأمر hf sandbox create --pool <id> نفس --namespace الذي أُنشئت به المجموعة، وإلا سلك المسار البارد والبحث في مساحتك الخاصة.
القيود المعروفة
تُستخدم المجموعات (Pools) للأحمال التشغيلية ضمن حدّ ثقة واحد. استخدم صناديق رمل مخصصة للكود الذي لا تثق به. تقلّل الفحوصات أدناه مخاطر محددة؛ لكنها لا تحوّل صناديق الرمل المشتركة إلى آلات افتراضية منفصلة.
نظرة عامة على نموذج التهديد
| المُنفِّذ | مخصص | المجموعة |
|---|---|---|
| مستخدم إنترنت مجهول | يتطلب وكيل Jobs رمز HF مع صلاحية قراءة مساحة الأسماء. | نفس الشيء. |
| عضو في مساحة الأسماء مع صلاحية القراءة | يمكن الوصول إلى الوكيل وفحص الصحة العام، لكن يتطلب رمز العزل (Sandbox) لعمليات واجهة برمجة التطبيقات (API). | نفس الشيء. |
| عضو مساحة الأسماء الذي يمكنه إنشاء مهام | لا يمكن اشتقاق رمز مستخدم آخر من رقمه غير المتكرر (nonce). | يمكن نسخ تسميات مجموعة النسخ، لكن التحقق الافتراضي من التبني يفحص مُبادِر واجهة برمجة تطبيقات المهام. adopt_hosts="namespace" يختار الثقة في منشئين آخرين. |
| كود يعمل داخل بيئة العزل | يتشارك المسار الجذري مع الخادم داخل آلة افتراضية؛ لا تعِد استخدام تلك الآلة الافتراضية عبر حدود الثقة. | مقيّد بواسطة uid و Landlock، لكنه يشارك النواة، ووحدة إدارة الذاكرة الافتراضية، ومستوى التحكم المميز. |
| عميلك | يحتوي على بيانات اعتماد HF وبيانات اعتماد بيئة العزل المخصصة. | يحتوي على بيانات اعتماد إدارة المضيف والصلاحيات المنفصلة لكل بيئة معزولة. |
الفجوات في العزل والموارد
- اكتشاف العُقد لا يُعَدّ شهادةً. القيمة الافتراضية
adopt_hosts="own"تتحقق من المُبادِر، والصورة، والنوع، والأمر، وعنوان URL المُعرَّض. وهي لا تُشهِد على الملف التنفيذي قيد التشغيل ولا تجعل الصورة القابلة للتعديل موثوقة. استخدمadopt_hosts="namespace"فقط مع أعضاء مساحة الأسماء التي تثق بهم. - تبقى بيانات اعتماد المضيف قوية. فهي تُدير المجموعة وتستعيد رمز كل بيئة معزولة. ولا تُستبدل هذه البيانات أبدًا بالرموز النطاقية المفقودة من قِبل هذا العميل. تعمل المضيفات المُشغَّلة بواسطة هذه الإصدار على sbx-server 0.7.0، الذي يرفضها على المسارات النطاقية؛ في حين تحتفظ المضيفات المُشغَّلة بواسطة عملاء أقدم بخادمها (وتوافق رمز المضيف الخاص به) حتى يتم إعادة تدويرها.
- تبقى القنوات المشتركة. لا يتم عزل الوصول عبر TCP الصادر، ولا الوصول عبر حلقة الرجوع إلى خادم التحكم، ولا UDP، ولا IPC على مستوى النواة، ولا بيانات الوصف القابلة للقراءة لقائمة العمليات. تبقى المجلدات النظامية وعقد الأجهزة المحددة قابلة للوصول. لم يتم اختبار عزل مجمّع وحدات معالجة الرسوميات (GPU)؛ استخدم الوضع المخصص لوحدات معالجة الرسوميات.
- الحدود ليست حصصًا مجمّعة. تُقلّل حدود rlimits لكل عملية والطوابير المحدودة المخرجات من تضخيم الموارد. لكنها لا تُقسّم حصص المعالج، أو الذاكرة الإجمالية، أو مساحة القرص، أو عقد inode، أو سعة الشبكة بين البيئات المعزولة. تحدد ترقيم صفحات المجلدات حدودًا للاستجابات لكنها لا تزال تُنشئ المجلد على الخادم. عمليات الكتابة إلى الملفات عبر واجهة برمجة التطبيقات ليست حصة قرص، وقد تؤدي عمليات الكتابة في وضع مخصص إلى الملفات الخاصة إلى الحجب.
- يمكن إضعاف العزل صراحةً. يتطلب وضع المضيف Landlock ABI 6 افتراضيًا ويرفض مجموعة قواعد فاشلة. يؤدي خفض
SBX_MIN_LANDLOCK_ABIأو استخدام علم التطوير--allow-unconfinedالخاص بالخادم إلى قبول ضمانات أقل؛ افحص/healthالموثق لمعرفة ABI والميزات الفعالة. - يتم المصادقة على اتصالات الوكيل مرة واحدة فقط. بعد الطلب الأول، ينسخ الوكيل البايتات إلى الخادم الخلفي المعني حتى نهاية الملف (EOF). الطلبات HTTP اللاحقة على تلك الاتصال لا تخضع للمصادقة أو التوجيه بشكل مستقل. لا يضمن التوافق مع WebSocket/SSE الأمان من إعادة استخدام الاتصال مع الخادم العلوي.
دورة الحياة والمعلومات المؤسسية
- يمكن للأحفاد المنفصلين أن يستمروا في العمل بعد انتهاء العملية أو انتهاء المهلة الزمنية. تُرسل الإشارة إلى مجموعة العمليات؛ أي حفيد يستدعي
setsid()ينجو من تلك المجموعة. احذف صندوق الرمل المُجمَّع أو أنهِ المهمة المخصصة لإنهاء كامل عبء العمل. max_hostsيُعدّ جهدًا حثيثًا. يمكن للعملاء المنفصلين أن يحسبوا نفس العُقد ويوفّرا كلاهما موارد أكثر. يلزم قبول الخلفية أو خاصية التكرار لضمان حدّ مشترك صارم.- قد يفشل التفكيك. ترفع عمليات الإلغاء الفاشلة على المضيف وتحتفظ بسجلات الكاش. يحدد
close()حد انتظاره للعمليات الجارية؛ قد تترك عملية إنشاء تتجاوز ذلك الانتظار مهمة (Job) قابلة للفوترة. استخدم واجهة برمجة تطبيقات Jobs أو سطر الأوامر CLI لمواءمة الموارد قيد التشغيل. - تدوير حامل التوكن لا يقوم بتدوير قدرات بيئة الاختبار. يعمل مصادقة الوكيل على تحديد توكن HF الحالي لكل طلب، لكن إعادة الاتصال غير الحالة تستنتج توكن المضيف من قيمة الحامل الأصلية الدقيقة. قد يؤدي تدوير تلك القيمة إلى منع إعادة الاتصال بالمضيفين الحاليين.
- تبقى ذاكرة التخزين المؤقت المحلية مدخلاً موثوقًا لمدة 15 دقيقة. يتم فحص عنوان URL وسياق الأمان الخاص بها، لكنها لا تحتوي على رمز تحقق MAC؛ احمِ
$HF_HOMEمن عمليات الكتابة بواسطة أكواد غير موثوقة. - تثبيت البصمة (Digest pin) ليس إثباتًا موقّعًا للمصدر. يتحقق العميل من ثنائي خادم محدد واحد. تبقى عملية نشر هذا التثبيت وتحديثه خطوات ضمن دورة الإصدار؛ فبيان عمل سير العمل (manifest) ليس شهادة تشفيرية. يتجاوز مسار الهروب الصريح
SBX_ALLOW_UNVERIFIED_SERVER=1عملية التحقق فقط عندما يكون كلا أداتي التجزئة مفقودين.
يُظهر /health غير الموثّق حالة البقاء على قيد الحياة والبروتوكول فقط. تتطلب البيانات الوصفية التفصيلية بيانات اعتماد المضيف. تتطلب معرّفات العزل الرمزي والرموز المصدر العشوائي للنواة؛ ويتبع إعادة استخدام معرّف المستخدم (UID) تنظيف العملية والمنزل المُتحقَّق منهما.
الأداء
تُقاس جميع الأرقام مقابل مهام HF الفعلية على cpu-basic، مع وجود العميل على حاسوب محمول وجميع حركة المرور تتدفق عبر وكيل المهام.
بيئة معزولة مخصصة:
| مقياس | value |
|---|---|
البدء البارد (تُعيد create()، والخادم يستجيب) | ~5.8 ثانية (الوسيط) |
رحلة ذهاب وعودة لـrun() | p50 ~110ms (الحد الأدنى لـ RTT عبر الوكيل ~105ms؛ الحمل على العميل ≈ 0) |
| نقل الملفات (متوازي، نطاقات، >8 ميغابايت) | ~340 ميغابايت/ثانية نزولاً، ~441 ميغابايت/ثانية صعوداً |
المجمّع (وضع المشاركة/المضيف):
| N بيئات معزولة | المضيفات (50/مضيف) | التخصيص + إنشاء الكل | تنفيذ في الكل | إنهاء الكل | الإجمالي |
|---|---|---|---|---|---|
| 100 | 2 | 6.1s | 1.5s | 0.6s | 8.2s |
| 1000 | 20 | 7.4s | 4.2s | 4.2s | 15.8s |
إنشاء 1000 بيئة اختبار وتنفيذها ثم إيقافها يستغرق حوالي 16 ثانية، وتكلفته الإجمالية تعادل تقريبًا تكلفة بدء تشغيل مضيف واحد من الصفر (حوالي 6 ثوانٍ) موزعة على جميعها — أي ما يعادل 0.0009 دولار إجمالًا (20 × cpu-basic)، مقارنةً بحوالي 0.06 دولار واندفاع جدولة لـ 1000 آلة افتراضية عند استخدام مهمة واحدة لكل بيئة اختبار. عمليات الإنشاء والتنفيذ والحذف على جانب الخادم تستغرق حوالي 1 مللي ثانية لكل منها؛ والميزانية الزمنية بالكامل هي زمن ذهاب وإياب الشبكة.
قرارات التصميم، ملخص
| قرار | لماذا |
|---|---|
| البناء على الوظائف، بدون خدمة جديدة | يرث الفوترة والأجهزة الصلبة والصلاحيات؛ يعمل في أي صورة |
| ملف Rust ثابت، يُحمَّل عند بدء التشغيل | لا يتطلب Python أو pip؛ بدء تشغيل بارد يستغرق ~6 ثوانٍ مقابل 30–90 ثانية لتهيئة تعتمد على pip |
| HTTP/1.1 مبنية يدوياً | تخزّن الإطارات الدنيا الاستجابات المقسّمة إلى أجزاء وتُعطّل البث المباشر (تم التحقق) |
| مصادقة HMAC بدون حالة | إعادة الاتصال من أي مكان؛ رمز مشتق بدلاً من رمز HF نفسه (محدّد النطاق لكل مهمة — راجع نطاق الرمز) |
يرفع run() استثناءً عند انتهاء غير صفري (يتم تجاوز ذلك عند check=False) | أفضل تجربة مطوّر لدورات «تشغيل الكود، رؤية الخطأ» (على غرار E2B) |
idle_timeout بدلاً من التنظيف على جانب العميل | الرمال المستمرة ميزة؛ أما الرمال المتسربة فتتوقف عن العمل على أي حال |
| المجموعات = uid + Landlock، سعة خاضعة للتحكم من الخادم، لا توجد حالة محلية | نشر سريع للمستخدم نفسه؛ صحيح تحت التزامن؛ قابل لإعادة التثبيت في أي مكان |