Hub Python Library documentation

Песочницы под капотом

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v2.4.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Песочницы под капотом

В этом руководстве объясняется, как внутренне устроены Sandboxes, почему они построены именно так и в чём заключаются их ограничения. Если вам нужно только использовать песочницы, достаточно руководства по Sandboxes; продолжайте чтение, если хотите понять механизм, оценить модель доверия или отладить что-то.

API Sandbox является экспериментальным. Его API и поведение могут измениться без предварительного уведомления.

Службы «песочницы» не существует

Первое, что нужно понять: отдельного бэкенда для песочниц не существует. Песочница — это просто HF Job (виртуальная машина), на которой запущен один небольшой статический бинарный файл sbx-server, работающий по HTTP. Клиент общается с этим сервером через прокси Jobs (URL вида *.hf.jobs, который предоставляет Job). Всё остальное — аутентификация, обнаружение, упаковка множества песочниц в один Job — построено на существующих примитивах Jobs: метках, переменных окружения и секретах.

Именно благодаря этому принципу «без новой инфраструктуры» песочница работает в любом Docker-образе и автоматически наследует тарификацию, типы аппаратного обеспечения и разрешения пространства имён Jobs.

Запуск сервера

При запуске задания команда задания представляет собой небольшой скрипт /bin/sh -c, который загружает бинарный файл sbx-server, делает его исполняемым и запускает его через exec. Бинарный файл — это статическая сборка musl размером около 640 КБ без каких-либо зависимостей во время выполнения, поэтому он работает в любом образе Linux для x86_64.

Несколько решений, на которые стоит обратить внимание:

  • Загрузка с резервным вариантом через монтирование. Быстрый путь скачивает бинарный файл с CDN HF с помощью wget или curl (каждый распространённый базовый образ содержит один из них), что быстро и бесплатно. В качестве страховки репозиторий Hub на сервере также монтируется в каждый job в качестве тома: если в образе нет ни wget, ни curl, скрипт копирует бинарный файл с этого монтирования. Монтирование прозрачно — оно ничего не стоит, пока не происходит фактического чтения, — но чтение добавляет ~2-3 секунды к холодному запуску, поэтому это не вариант по умолчанию. Единственное жёсткое требование к образу — наличие /bin/sh.
  • HTTP/1.1-сервер, написанный вручную, без фреймворка. Для потоковой передачи вывода в реальном времени необходимо сбрасывать (flush) каждый фрагмент сразу после его формирования. Типичные минималистичные HTTP-серверы на Rust (например, tiny_http) буферизуют ответы с chunked-кодированием до завершения всего ответа, что нарушает потоковую передачу. Поэтому сервер реализует HTTP/1.1 вручную: потоки событий в формате NDJSON для exec, сырые тела для файлов и явный сброс (flush) для каждого фрагмента.
  • Порт 49983. Сервер слушает на специально выбранном нестандартном порту, чтобы обычные порты для разработки (3000, 8000, 8080, …) оставались свободными для вашего кода.

Сервер является открытым исходным кодом и доступен по адресу github.com/huggingface/sandbox-server.

Аутентификация является безсостоятельной

Два независимых уровня защищают песочницу:

  1. Прокси-шлюз. Прокси для задач пересылает только те запросы, в которых передан токен HF с правами на чтение в пространстве имён задачи. Случайный пользователь из интернета не может получить доступ к URL.
  2. Ворота на уровне приложения. 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, поэтому любой компьютер, на котором хранится этот токен, может пересчитать токен песочницы — именно это делает переподключение без состояния. Это также означает, что токен представляет собой capability, производную от ваших учётных данных, а не проверку личности вызывающей стороны: сервер проверяет, что вызывающая сторона знает токен, а не то, кто она такая.

Каждая задача в песочнице также имеет две стабильные метки для поиска — hf-sandbox=1 (у всех) и hf-sandbox-mode=dedicated или hf-sandbox-mode=pool — что позволяет получать список или фильтровать их на стороне сервера, например, с помощью hf jobs ps --label hf-sandbox=1.

Токен передаётся на сервер через секрет задания. Клиент по запросу повторно выводит его из публичного nonce в метке. Это имеет несколько приятных последствий:

  • Бессохранное переподключение. Sandbox.connect(id) работает с любого компьютера, на котором хранится тот же HF-токен — считайте nonce из метки, пересчитайте токен. Никаких локальных файлов, никакого состояния для копирования.
  • Токен HF не передаётся в песочницу в качестве переменной окружения или секрета задания (если только вы явно не включите это с помощью forward_hf_token=True). Это не жёсткая гарантия того, что ваши учётные данные останутся недоступными: процесс, слушающий порт песочницы, — это тот, который первым запускает образ, поэтому недоверенный образ может иметь возможность наблюдать запросы, отправляемые клиентом, — включая их заголовок Authorization. Рассматривайте учётные данные, доступные из песочницы, как потенциально раскрытые для неё.

Область действия токена

Для каждого Job выпускается один nonce, поэтому производный токен является учётными данными job. В режиме dedicated job и sandbox совпадают. В режиме pool job — это хост, а на одном хосте может находиться множество sandbox, поэтому sandbox в пуле получает собственные, более узкие учётные данные:

credentialкак выведеноавторизует
выделенный токен песочницыHMAC(hf_token, nonce) из метки заданияэтот песочный контейнер (который и есть вся задача)
токен пула хостовHMAC(hf_token, nonce) из метки хост-заданияуправление пулом: создание, просмотр списка и удаление песочниц на этом хосте, а также восстановление их токенов
объединённый токен песочницы256 случайных бит, сгенерированных сервером-хостом для каждого песочного окружениятот самый песочный бокс — не соседний, не пул

Клиент автоматически использует узкий токен: pool.create() получает его в ответе на создание, Sandbox.connect("<host>.<id>") восстанавливает его с помощью токена хоста, а proxy_headers выдаёт токен песочницы, а не хоста (HF bearer разрешается в момент чтения, поэтому долгоживущий обработчик не выдаст устаревший токен) — эти заголовки обычно попадают в браузер или WebSocket-клиент, поэтому они должны предоставлять доступ только к одной песочнице и не более.

Что это значит для утечки: компрометация пул-токена песочницы ставит под угрозу именно эту песочницу. Компрометация токена хоста ставит под угрозу сам хост — все песочницы на нём, текущие и будущие, а также маршруты управления, — поэтому рассматривайте его как учётные данные администратора пула. Участники вашего пространства имён имеют другой токен HF и не могут вывести ваш, но см. раздел Известные ограничения, чтобы узнать, как токен может быть передан не туда.

Клиенту для пулированных операций требуется непустой токен, ограниченный песочницей, и он отклоняет хосты, которые его не предоставляют. Он никогда не заменяет учётные данные управления хостом. Обновите клиент и перезапустите более старые хосты пула перед использованием этого API.

Выделенные песочницы ( Sandbox.create )

Простая модель: один Job на песочницу. Job представляет собой реальную виртуальную машину, поэтому обеспечивает максимальную изоляцию (на уровне ВМ), поддерживает любые типы оборудования, включая GPU, и является правильным выбором для взаимно недоверенного кода. Маршруты его API находятся по адресу /v1/*, а пути к файлам являются абсолютными в файловой системе контейнера. kill() просто отменяет Job.

Граница, которую обеспечивает выделенная песочница, проходит между Job и всем, что находится за её пределами. Внутри ВМ ваш код и sbx-server выполняются с правами root в одном и том же контейнере: sbx-server не изолирует команду, которую запускает для вас, а лишь предоставляет её по HTTP. Поэтому, как только недоверенный код был выполнен в выделенной песочнице, рассматривайте эту ВМ — включая её контрольную плоскость — как принадлежащую этому коду: он может завершить работу сервера или заменить его, и всё, что вы делаете в этой песочнице далее, выполняется на его условиях. Используйте одну выделенную песочницу на каждую недоверенную рабочую нагрузку и kill() её по завершении, а не переиспользуйте одну песочницу между разными доверенными зонами.

Стоимость прямо видна на схеме: каждый песочный контейнер оплачивает полный холодный запуск ВМ (~6 с) и тарифицируется как целая машина. Для одного песочного контейнера или GPU-нагрузки это именно то, что нужно. Для 100–1000 коротких CPU-задач это расточительно — для этого и существуют пулы.

Пул: множество песочниц в одном Job ( SandboxPool )

Пул предназначен для рабочих нагрузок внутри одной границы доверия. Пул-песочница — это uid и набор правил Landlock внутри общей ВМ, а не отдельная ВМ, и её хост запускает привилегированную управляющую плоскость, с которой взаимодействуют все её песочницы. Используйте пулы для дешёвого масштабирования вашего собственного кода. Для взаимно недоверяющих рабочих нагрузок — всего, что вы не хотели бы давать читать файлы других ваших песочниц, — используйте Sandbox.create(), который выделяет каждой рабочей нагрузке собственную ВМ. См. Известные ограничения для конкретных пробелов.

Типичный RL-роллаут или песочница для выполнения инструментов требует нескольких МБ ОЗУ и одного ядра на несколько секунд. Платить за 2-vCPU ВМ и 6-секундный холодный старт для каждой из них — и при этом вызывать всплеск планирования на 1000 ВМ — это неверный компромисс. Поэтому SandboxPool запускает один Job как хост и мультиплексирует множество песочниц внутри него.

Пул-песочница — это не вложенная виртуальная машина или контейнер. Это классический многопользовательский примитив Unix:

  • a выделенный uid (≥ 20000),
  • частный дом с правами 0700, принадлежащий этому uid,
  • команды exec‘d с этим uid в очищенной среде (env_clear, чтобы секреты хоста не утекали), NO_NEW_PRIVS, rlimits для каждого процесса и набор правил Landlock для каждого песочного окружения.

Создание песочницы, таким образом, сводится к mkdir + chown + build ruleset ≈ 1 мс на стороне сервера — без повторной загрузки ВМ. Единственная задержка, видимая клиентом, — это время на проксирование запроса.

Публичный идентификатор пулового песочного окружения имеет вид <host_job_id>.<local_id>, поэтому connect/exec/kill работают без сохранения состояния, как и в случае с выделенными окружениями. Вызов kill() для пулового песочного окружения отправляет запрос DELETE на его хост (освобождая слот); при этом хост продолжает работу.

Изоляция в пуле: uid + Landlock

Именно в этом заключается суть проектирования пула, поэтому важно точно определить, что изолируется, а что — нет.

Стандартный Job выполняется от имени root внутри пользовательского пространства имён, в котором отображаются только uid 0..65535, с включённым фильтром seccomp и без CAP_SYS_ADMIN / CAP_NET_ADMIN / CAP_NET_RAW. Это исключает использование обычных инструментов тяжёлой изоляции: вложенные пространства имён, новые точки монтирования и делегирование cgroup недоступны (unshare, mount, запись в /sys/fs/cgroup/... завершаются ошибкой). Ядро предоставляет Landlock (ABI 6) — модуль безопасности Linux, который позволяет любому непривилегированному процессу ограничивать себя и свои дочерние процессы, что как раз и является нужной нам границей для каждого песочного окружения. Для каждой песочницы сервер создаёт набор правил; дочерний процесс exec переходит на uid/gid песочницы и применяет NO_NEW_PRIVS, landlock_restrict_self и rlimits перед запуском команды.

Сочетание различных uid (дискреционное управление доступом) с Landlock разработано и протестировано для обеспечения:

  • ✅ Процесс A не может прочитать environ другого процесса, что предотвращает прямой доступ к токенам HF и песочниц между песочницами.
  • ✅ A не может SIGKILL / ptrace / читать память процессов B, setuid в B или читать домашний каталог B с правами 0700.
  • ✅ Доступ к /tmp и /dev/shm запрещён — каждая песочница ограничена Landlock в рамках собственного домашнего каталога (её TMPDIR указывает внутрь $HOME).
  • ✅ A не может bind TCP-порт, поэтому внутри песочницы нет локального сервиса (исходящие connect остаются разрешёнными, так что интернет работает).
  • ✅ Абстрактные сокеты unix между песочницами блокируются (LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET; изоляция по uid сама по себе не блокирует их).

Эти параметры ограничивают нагрузку. Корневой контрольный план остаётся отдельной границей доверия: его файловые операции в режиме хоста отклоняют символьные ссылки, его прокси сокета фиксирует inode сокета и проверяет uid удалённой стороны, а выделенные маршруты недоступны в режиме хоста. Остальные общие каналы описаны в разделе Известные ограничения.

Почему это не замена виртуальной машине. Изоляция Landlock и uid предназначена для рабочих нагрузок в пределах одной границы доверия. Поскольку пул песочниц разделяет ядро, виртуальную машину и контрольную плоскость, защита от всех атак между песочницами не гарантируется; ресурсы и некоторые метаданные списка процессов также остаются общими. Для взаимно недоверенного кода или для GPU используйте Sandbox.create(), который выделяет каждой песочнице собственную виртуальную машину.

Файл модели в пуле

Поскольку единственной записываемой областью пул-песочницы является её домашний каталог, ограниченный Landlock (который также является каталогом работы по умолчанию), файловый API привязывает все пути к этому каталогу: files.write("data/in.txt", ...) записывает данные в $HOME/data/in.txt, начальный / интерпретируется как относительный от домашнего каталога, а .. не позволяет выйти за его пределы. Файлы, записанные через API, получают владельца chown с uid песочницы, чтобы собственный код песочницы мог их читать. Это обеспечивает чистую модель «файловая система, корнем которой является песочница», которая точно соответствует тому, к чему может обратиться код внутри песочницы, — и отличается от выделенных песочниц, где пути являются абсолютными в файловой системе контейнера.

Пути нормализуются лексически, а затем разрешаются относительно открытого дескриптора домашнего каталога с отказом от символьных ссылок на каждом компоненте. Файловый API не следует по символьным ссылкам, включая ссылки внутри того же домашнего каталога; код песочницы всё ещё может использовать их в рамках своих правил Landlock.

Пул не имеет авторитетного локального состояния

Пул намеренно не является локальным файлом конфигурации. Пул представляет собой набор запущенных хостовых Jobs, все из которых имеют метку hf-sandbox-pool=<id>. Это обеспечивает согласованность пулов с остальной частью API песочницы (всё обнаруживается по меткам и может быть повторно подключено с любого компьютера), а также означает, что пул просто перестаёт существовать, как только исчезает его последний хост.

  • Хост хранит конфигурацию пула (образ, flavor, sandboxes_per_host, тайм-аут простоя) в переменных окружения своей задачи — метки используются только для фильтрации. Когда клиенту необходимо запустить дублирующий хост, он считывает эту конфигурацию из работающего хоста (inspect_job), благодаря чему все хосты в пуле остаются согласованными без центрального реестра.
  • Переменные окружения и секреты задаются для каждого песочного окружения (sandbox) отдельно и передаются при его создании — на уровне пула они не используются. Переменные окружения пулового sandbox хранятся в памяти хост-сервера на протяжении всего времени жизни sandbox (они повторно применяются при каждом выполняемом вами командой); они никогда не записываются на диск и не отображаются в метаданных любого Job. Канал для шифрованных секретов для пуловых sandbox отсутствует — используйте env и рассматривайте эти значения как «не хранящиеся на диске, но и не зашифрованные». Для значений, требующих шифрованного хранилища, используйте secrets выделенного sandbox.
  • Вместимость определяется на стороне сервера. Хост отклоняет создание, превышающее sandboxes_per_host (отвечая {"rejected": N}); клиент переносит избыток на другой хост или запускает дубликат. Это обеспечивает точную упаковку даже при одновременном создании несколькими процессами в один и тот же пул.
  • Idle eviction выполняется в два уровня. Каждый sandbox удаляется после истечения собственного idle_timeout бездействия (если в нем не выполняется процесс); когда на хосте не осталось sandboxes в течение host 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 устраняет эту проблему. После любого создания/прогрева процесс записывает конфигурацию пула, а также для каждого хоста — URL прокси, аутентификационный nonce и последние известные свободные слоты. Следующий процесс восстанавливает транспорт хоста непосредственно из файла (без HTTP) и сразу переходит к POST.

<context> — это дайджест конечной точки, учётных данных и пространства имён, для которых была записана запись: учётные данные представлены в виде отпечатка; сам токен никогда не записывается в кэш. И имя каталога, и файл содержат этот дайджест, а чтение, не совпадающее по всем трём параметрам, является обычным промахом кэша, поэтому другой пользователь машины, другая конечная точка или connect(pool, namespace="other-org") получают холодный путь вместо хостов, закэшированных для чего-то другого. Файлы создаются с правами 0600, каталоги — с правами 0700.

Для чего кэш предназначен и для чего нет:

  • Не является авторитетным источником по вместимости. Сервер внутри задания остаётся авторитетным, поэтому устаревшее значение live приводит лишь к бесполезному запросу, но никогда не к некорректной упаковке хоста.
  • Не доверяется для определения расположения хоста. Кэшированный URL используется только в том случае, если он является URL прокси HTTPS для задач того же задания, которое указано в этой записи, поэтому файл кэша не может выбирать адрес, на который отправляются ваш токен HF и токен песочницы; записи, указывающие что-либо иное, удаляются из файла. По истечении 15 минут запись также не принимается на основании только её данных: inspect_job должен подтвердить, что задание всё ещё выполняется, всё ещё помечено для этого пула и всё ещё находится по этому URL, прежде чем будет создан транспорт.
  • Форма не подтверждена. Типы и диапазоны проверяются при чтении, поэтому вручную отредактированный, усечённый или строковый файл приводит к промаху кэша, а не к ошибке в середине create().
  • Самовосстановление. Кэшированный хост, который исчез, отбрасывается при первом неудачном запросе и удаляется из файла; при создании прозрачно используется обнаружение по меткам.
  • Безопасность при параллельном доступе. Записи объединяются под файловым замком (с ключом job_id) и фиксируются атомарно, поэтому параллельные процессы create не перезаписывают друг друга, а читатели никогда не видят частично записанный файл.
  • Одноразовый. Удалите его, и вы получите промах кэша: сработает холодный путь, и всё продолжит работать. Он никогда не разделяется между машинами.

Обратите внимание на два момента. Относитесь к $HF_HOME как к конфиденциальным данным: запись по-прежнему представляет собой переиспользуемый URL хоста и публичный nonce, из которого выводится токен. При этом файл, записанный с другими учетными данными, игнорируется, тогда как файл, записанный с вашими, учитывается в течение 15-минутного окна, описанного выше. Кроме того, поскольку пространство имен является частью ключа, пул, закэшированный с явным указанием namespace=, будет найден повторно только при передаче того же namespace= — команде hf sandbox create --pool <id> требуется тот же --namespace, с которым был создан пул, иначе будет использован «холодный» путь, и поиск будет выполнен в вашем собственном пространстве имен.

Известные ограничения

Пул предназначен для рабочих нагрузок в пределах одной границы доверия. Для недоверенного кода используйте выделенные песочницы. Перечисленные ниже проверки снижают конкретные риски, но не превращают пул песочниц в отдельные виртуальные машины.

Модель угроз вкратце

АкторВыделенныйПул
Анонимный пользователь интернетаПрокси Jobs требует токен HF с правами на чтение пространства имён.Аналогично.
Член пространства имён с доступом на чтениеМожно получить доступ к прокси и публичной проверке работоспособности, но для операций с API требуется токен песочницы.Аналогично.
Участник пространства имён, который может создавать задачиНевозможно вывести токен другого пользователя из его nonce.Можно копировать метки пула, но по умолчанию проверка при принятии обращается к инициатору в API Jobs. adopt_hosts="namespace" позволяет доверять другим создателям.
Код, выполняемый в песочницеОбщий корень с сервером внутри ВМ; не переиспользуйте эту ВМ между доверенными границами.Изолирован по uid и Landlock, но разделяет ядро, виртуальную машину и привилегированную плоскость управления.
Ваш клиентСодержит учётные данные HF и выделенной песочницы.Хранит учётные данные для управления хостом и отдельные возможности для каждого песочного окружения.

Изоляция и дефицит ресурсов

  • Обнаружение хостов не является аттестацией. Значение по умолчанию adopt_hosts="own" проверяет инициатора, образ, flavor, команду и открытый URL. Оно не аттестует выполняемый бинарный файл и не делает изменяемый образ доверенным. Используйте adopt_hosts="namespace" только с участниками пространства имён, которым вы доверяете.
  • Учётные данные хоста сохраняют высокую степень привилегий. Они управляют пулом и восстанавливают токен каждой песочницы. Этот клиент никогда не подставляет их вместо отсутствующих токенов с ограниченной областью действия. Хосты, запущенные этой версией, используют sbx-server 0.7.0, который отклоняет их на маршрутах с ограниченной областью действия; хосты, запущенные более старыми клиентами, сохраняют свой сервер (и его совместимость с токенами хоста) до тех пор, пока они не будут пересозданы.
  • Общие каналы остаются. Исходящие TCP-соединения, доступ к серверу управления через loopback, UDP, межпроцессное взаимодействие ядра и метаданные списка процессов, доступные для чтения, не изолируются. Системные каталоги и выбранные узлы устройств остаются доступными. Изоляция пула GPU не протестирована; для GPU используйте выделенный режим.
  • Ограничения не являются совокупными квотами. Rlimits для каждого процесса и ограниченные очереди вывода снижают усиление ресурсов. Они не распределяют доли CPU, общий объём памяти, место на диске, inode или сетевую пропускную способность между песочницами. Пагинация каталогов ограничивает ответы, но всё равно материализует каталог на сервере. Запись файлов через API не является квотой диска, а записи в специальные файлы в режиме выделенного использования могут блокироваться.
  • Ограничения можно ослабить явно. Режим хоста по умолчанию требует Landlock ABI 6 и отклоняет неудачно созданный набор правил. Снижение SBX_MIN_LANDLOCK_ABI или использование флага разработки сервера --allow-unconfined приводит к принятию меньшего числа гарантий; проверьте аутентифицированный /health, чтобы узнать эффективную версию ABI и поддерживаемые функции.
  • Аутентификация прокси-соединений выполняется только один раз. После первого запроса прокси копирует байты в этот бэкенд до EOF. Последующие HTTP-запросы по этому соединению не аутентифицируются и не маршрутизируются независимо. Совместимость с WebSocket/SSE не гарантирует безопасность при повторном использовании соединений с вышестоящим уровнем.

Жизненный цикл и учётные данные

  • Отсоединённые потомки могут пережить завершение процесса или истечение тайм-аута. Оба сигнала отправляются группе процессов; потомок, вызывающий setsid(), покидает эту группу. Чтобы завершить всю рабочую нагрузку, удалите пул песочниц или завершите выделенный Job.
  • max_hosts — это параметр, работающий в режиме best-effort. Различные клиенты могут учитывать одни и те же хосты, и оба могут развернуть больше ресурсов. Для жёсткого общего ограничения требуется приёмка на стороне бэкенда или идемпотентность.
  • Очистка может завершиться с ошибкой. Сбой отмены на хосте приводит к исключению и сохранению записей кэша. close() ограничивает время ожидания активных процессов создания; если создание длится дольше этого времени, может остаться платная задача. Используйте API или CLI Jobs для согласования состояния запущенных ресурсов.
  • Ротация Bearer не вращает возможности песочницы. Аутентификация прокси разрешает текущий токен HF для каждого запроса, но stateless-переподключение выводит хостовый токен из точного исходного значения Bearer. Ротация этого значения может помешать переподключению к существующим хостам.
  • Локальный кэш остаётся доверенным вводом в течение 15 минут. Его URL и контекст безопасности проверяются, но у него нет MAC целостности; защищайте $HF_HOME от записи недоверенным кодом.
  • Пин по дайджесту не является подписанным подтверждением происхождения. Клиент проверяет один конкретный бинарный файл сервера. Публикация и обновление этого пина остаются этапами выпуска; манифест рабочего процесса не является криптографическим подтверждением. Явный параметр SBX_ALLOW_UNVERIFIED_SERVER=1 пропускает проверку только в том случае, если отсутствуют оба инструмента для работы с хэшами.

Неаутентифицированный /health предоставляет только информацию о работоспособности и протоколе. Для получения подробных метаданных требуются учётные данные хоста. Идентификаторы песочниц и токены требуют случайного источника ядра; повторное использование UID выполняется после проверки процесса и очистки домашнего каталога.

Производительность

Все значения измерены на реальных HF Jobs с использованием cpu-basic, при этом клиент находился на ноутбуке, а весь трафик проходил через прокси Jobs.

Выделенная песочница:

метрикаvalue
холодный запуск (create() возвращает, сервер отвечает)медиана ~5,8 с
run() round-tripp50 ~110 мс (нижний порог RTT прокси ~105 мс; накладные расходы клиента ≈ 0)
передача файлов (параллельная, с диапазонами, >8 МиБ)~340 МБ/с вниз, ~441 МБ/с вверх

Пул (общий/хостовый режим):

N песочницхосты (50/хост)подготовить + создать всеexec во всехkill allвсего
10026.1s1.5s0.6s8.2s
1000207.4s4.2s4.2s15.8s

Создание, выполнение и удаление 1000 песочниц заняли ~16 с и обошлись примерно в стоимость одного холодного запуска хоста (~6 с), распределённую на все песочницы, — итого около $0.0009 (20 × cpu-basic), в отличие от ~$0.06 и всплеска планирования 1000 ВМ при использовании одного Job на песочницу. Серверные операции create/exec/delete занимают по ~1 мс; весь бюджет расходуется на сетевой round-trip.

Ключевые решения, краткое резюме

решениепочему
Построено на Jobs, без нового сервисанаследует тарификацию, оборудование, разрешения; работает в любом образе
Статический бинарный файл Rust, загружаемый при запускебез Python/pip; холодный запуск ~6 с против 30–90 с для бутстрапа на основе pip
Собственный HTTP/1.1минимальные фреймворки буферизуют ответы по чанкам и ломают потоковую передачу (проверено)
Бессостоятельная аутентификация HMACпереподключение из любой точки; производный токен вместо самого токена HF (с областью действия для каждой задачи — см. Область действия токена)
run() вызывает исключение при ненулевом коде завершения (check=False отключает это поведение)лучший DX для циклов «запустить код, увидеть ошибку» (в стиле E2B)
idle_timeout watchdog вместо очистки на стороне клиентапостоянные песочницы — это функция; утекающие всё равно завершаются
Pools = uid + Landlock, серверная авторитетная ёмкость, отсутствие локального состояниябыстрая рассылка одному и тому же пользователю; корректность при конкурентном доступе; возможность повторного подключения в любом месте
Update on GitHub