status/volatile «Стандарт 2026», список технологий (Firecracker, E2B, Docker Sandboxes) и тайминги (~150 мс) быстро устаревают. Ревизия раз в квартал.
Суть
Любая защита на уровне промпта пробиваема, поэтому последний барьер — аппаратная граница. Если агенту дали инструменты execute_code / bash, изоляция исполнения обязательна: даже при успешном Tool Hijacking ущерб ограничен изолированной машиной.
Зачем это нужно
Это «ограничение последствий» — то, на что упирается даже «сорвавшаяся» после Jailbreak Attacks модель. Для computer-use агентов (Computer Use) изоляция — часть базовой защиты (никогда не на хосте, не маунтить docker.sock).
Как работает (спектр изоляции)
| Технология | Изоляция | Старт | Когда |
|---|---|---|---|
| Docker-контейнеры | Слабая — все делят одно ядро ОС хоста | Медленнее VM-микро | Обычный веб-сервис (Next.js) |
| VM | Сильная | Секунды, большой overhead | Когда нужна полная изоляция, скорость неважна |
| E2B / Firecracker microVM | Сильная (своё ядро через KVM) | ~150 мс | Агент с execute_code / bash — золотой стандарт |
Альтернативная линия: gVisor и WASM
Продакшен-факультатив заходит с той же посылки — голого Docker недостаточно, — но предлагает другую замену. Вместо микро-VM там два варианта:
- gVisor — перехватывает системные вызовы приложения в пользовательском пространстве и не пускает их напрямую в ядро хоста. Изоляция без запуска отдельного ядра.
- WASM-песочницы — исполнение Python внутри WebAssembly через Pyodide. Здесь изолируется не машина, а сам рантайм языка: код физически не имеет доступа к файловой системе и сети, если их явно не пробросили.
При этом Docker у него не отменяется совсем: одноразовые контейнеры с жёсткими лимитами CPU и памяти и отключённой сетью остаются рабочим вариантом для менее рискованных сценариев. Формулировка, которую он выносит в правило: никогда не запускать exec() напрямую в хост-ОС приложения.
Как это соотносится с Firecracker. Оба автора сходятся в диагнозе и расходятся в выборе лечения, причём выбор объясняется разным профилем задач:
| Firecracker / microVM (базовый разбор) | gVisor и WASM (продакшен-вариант) | |
|---|---|---|
| Что изолирует | Машину целиком, своё ядро через KVM | Системные вызовы (gVisor) или рантайм языка (WASM) |
| Цена | Старт ~150 мс, нужна аппаратная виртуализация | Легче и быстрее, виртуализация не требуется |
| Слабое место | Оверхед на каждый запуск | Поверхность атаки больше: ядро хоста рядом |
| Естественный домен | Долгие сессии computer-use, произвольный bash | Короткие вычисления, исполнение сгенерированного кода в цикле рефлексии |
Практический ориентир: чем длиннее живёт песочница и чем шире права внутри неё, тем сильнее аргумент за микро-VM. Для паттерна рефлексии, где код исполняется десятки раз подряд короткими итерациями (см. Multi Agent Patterns), оверхед старта становится определяющим, и WASM выигрывает.
Третий слой, который факультатив добавляет поверх изоляции при работе с MCP-серверами: доступ к базе только через read-only учётные записи с тайм-аутом и лимитом вывода (порядка пятидесяти строк), а любые мутирующие операции — через обязательное подтверждение оператором (Human in the Loop).
Firecracker (от AWS, та же технология, что AWS Lambda):
- Каждый microVM получает изолированное ядро через KVM (аппаратная виртуализация) — побег из песочницы требует уязвимости в гипервизоре, а не в разделяемом ядре.
Не «невозможно», а «сильно дороже»
Формулировку «выйти за периметр невозможно» стоит считать рекламной. Замер 2026 года (arXiv:2607.17518) подтверждает градиент изоляции container → gVisor → микро-VM, но снимает абсолют: side-channel через общий page cache не устраняется и микро-VM. Изоляция здесь — вопрос стоимости атаки, а не бинарной гарантии.
- Написан на Rust — исключает целые классы уязвимостей (buffer overflow, memory safety).
- Минималистичная модель устройств; изоляция процесса через cgroups, namespaces, seccomp BPF; сам процесс заперт через утилиту Jailer.
- microVM даёт полноценную среду (включая Docker внутри) без доступа к хосту.
- Scoped permissions — каждый microVM получает только нужные для задачи права.
Тренд 2026: в апреле 2026 Docker выпустил Sandboxes — каждый агент в microVM с приватным Docker внутри (Docker признал, что собственной изоляции контейнеров для агентных нагрузок недостаточно).
Спорный/устаревающий тезис Ранее Docker считался достаточной основой деплоя агента. Для агентов с доступом к коду это устаревает: «докер-контейнеры — это медленно… их изоляции недостаточно для агентных нагрузок». Для обычного веб-сервиса Docker по-прежнему ок.
Самый дешёвый уровень: разбор вместо исполнения
Не всякая задача, выглядящая как «выполни код», требует песочницы. Классический пример — арифметика: агенту нужно посчитать выражение, и соблазн решить это через eval() велик, потому что одна строка вместо тридцати.
eval() здесь недопустим в любом виде, включая «отфильтрованный». Фильтрация строки по запрещённым подстрокам — чёрный список, который обходится вложенностью, кодировками и обращением к атрибутам; за eval("__import__('os').system(...)") стоит вся стандартная библиотека.
Рабочий вариант — не исполнять строку, а разобрать её в дерево и обойти белый список разрешённых узлов:
_ALLOWED_OPS = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
ast.Div: op.truediv, ast.Pow: op.pow, ast.USub: op.neg}
def _eval_expr(node):
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return node.value
if isinstance(node, ast.BinOp) and type(node.op) in _ALLOWED_OPS:
return _ALLOWED_OPS[type(node.op)](_eval_expr(node.left), _eval_expr(node.right))
if isinstance(node, ast.UnaryOp) and type(node.op) in _ALLOWED_OPS:
return _ALLOWED_OPS[type(node.op)](_eval_expr(node.operand))
raise ValueError("Unsupported expression") # всё неперечисленное — отказ
Ключевое — последняя строка. Функция знает конечный список того, что умеет, и на всём остальном отказывает; вызовы функций, обращения к атрибутам и импорты просто не имеют ветки в разборе. Это тот же принцип белого списка, что и в реестре инструментов (Tool Calling), только на уровне выражения.
Правило выбора уровня: если множество допустимых операций конечно и перечислимо — хватает разбора, и песочница не нужна. Песочница начинается там, где агент выполняет произвольный код, который вы заранее не описали.
Альтернативный взгляд: управляемая песочница вместо своей
Разбор выше строит изоляцию самостоятельно — Docker SDK, лимиты CPU/RAM, отключённая сеть, при необходимости gVisor или WASM. Облачный заход предлагает другой ответ: взять готовый managed-рантайм, спроектированный именно под код от LLM.
Разница видна при сравнении обычного serverless-контейнера и специализированной песочницы (пример — Azure Container Apps Sandboxes):
| Критерий | Serverless-контейнер | Managed sandbox |
|---|---|---|
| Назначение | Хостинг сервисов и фоновых задач | Выполнение недоверенного кода в реальном времени |
| Жизненный цикл | Длительный | Эфемерный: создаётся под сессию и уничтожается |
| Скорость запуска | Секунды (холодный старт) | < 100 мс за счёт пула прогретых инстансов |
| Изоляция | Стандартная контейнерная, ядро Linux общее | Аппаратная, Hyper-V microVM на пользователя |
| Рантайм | Собираешь образ сам | Готовый Python REPL с DS-библиотеками |
Два аргумента, которых нет в самосборной схеме. Первый — скорость старта как продуктовое требование: пока песочница поднимается секунды, стриминг ответа стоит, и пользователь видит паузу. Второй — граница изоляции проходит по железу, а не по namespace ядра, что снимает целый класс побегов из контейнера.
Цена — вендор-лок и невозможность тонко настроить рантайм под себя.
Где проходит граница между managed и своей песочницей
Из сравнения выше граница читается по трём признакам, а не по цене за час.
Managed берут, когда песочница нужна эфемерная и часто: холодный старт под 100 мс достижим только пулом прогретых инстансов, а поддерживать такой пул самому — отдельная инфраструктурная работа. Плюс готовый рантайм снимает сборку образа под каждую задачу.
Свою строят, когда критичны две вещи, которых managed не даёт: полный контроль над содержимым образа (свои библиотеки, свои версии, отсутствие чужого агента внутри) и отсутствие выхода данных за периметр — недоверенный код обрабатывает ваши данные внутри чужого рантайма, и для регулируемого контура это стоп-фактор сам по себе, независимо от стоимости (DLP for LLM).
Стоимость становится решающей только на третьем шаге, когда обе стороны технически подходят: managed дороже за вызов, но не требует дежурства и обновления ядра, поэтому переход к своей окупается на постоянной нагрузке и не окупается на редкой.
Три вопроса про песочницу, которые легко перепутать
Заметка отвечает на первый из трёх, и границу стоит держать явно:
| Вопрос | Где |
|---|---|
| чем изолировать — контейнер, микровиртуальная машина, gVisor, WASM | здесь |
| какой контракт между инструментами и средой, чтобы среду можно было подменить | Sandbox Abstraction |
| как эксплуатировать арендованную среду: состояния, стоимость простоя, снимки | Sandbox Lifecycle |
Оси независимы: локальная реализация не изолирует вовсе, но контракту удовлетворяет; облачная изолирует полностью и удовлетворяет тому же контракту. Выбирать технологию изоляции и проектировать контракт — разные решения, и принимаются они в разное время.
Отдельное измерение: как права объявляются
Всё выше — про то, чем изолировать: контейнер, микровиртуальная машина, gVisor, WASM. Есть второй вопрос, независимый от первого: в какой форме права записаны.
Обвязка Codex объявляет политику перечислимым значением — readOnly, workspaceWrite, dangerFullAccess, externalSandbox — и уточняет полями writableRoots, readableRoots, networkAccess (Agent Harness).
Разница с рассыпанными по коду проверками практическая, а не эстетическая. Объявленное значение читается из конфигурации, попадает в лог, сравнивается между запусками и запрещается политикой централизованно; условия в коде ничего из этого не позволяют, и ответ на вопрос «а что этому запуску вообще разрешено» приходится собирать чтением (Agent Control Plane).
Отдельного упоминания стоит externalSandbox — значение, которое честно говорит «изоляцию обеспечивает вызывающая сторона». Явное «здесь не я» лучше умолчания: без него окружение, где изоляции нет вовсе, неотличимо от окружения, где она есть снаружи.
Три уровня, которые называют одним словом «песочница»
Архитектурно песочница — не контейнер, а режим ограничений: она отвечает на вопрос «что произойдёт, если инструмент поведёт себя хуже, чем мы ожидали». Реализаций много, уровней изоляции — три, и команды регулярно считают, что у них есть песочница, имея только первый:
| Уровень | Что это | Чем останавливает |
|---|---|---|
| Логическая | Проверки политик, контракты возможностей, списки разрешений | Логикой приложения |
| Процессная | Отдельный процесс, таймаут, лимиты ресурсов | Операционной системой |
| Средой исполнения | Отдельное окружение, урезанная файловая система, ограниченный исходящий трафик, минимум секретов | Самой средой |
Проверочный вопрос, отделяющий одно от другого: если возможность начнёт вести себя хуже нормы, что именно её остановит — логика, процесс или среда? Для чтения с низким риском иногда хватает первого уровня; для исполнения с высоким риском почти всегда нужен третий.
Ограничивает песочница шесть вещей: доступ к сети, к файловой системе, к секретам, бюджет процессора и памяти, разрешённые системные вызовы, время жизни операции.
Связано с
- Sandbox Abstraction — контракт со средой, позволяющий её подменить
- Sandbox Lifecycle — как эксплуатировать арендованную среду: состояния, стоимость, снимки
- Agent Harness — как права объявляются, в отличие от того, чем изолировать
- Capability Containment — сдерживание возможностей как принцип над этими уровнями
- Tool Catalog — где записан профиль изоляции конкретной возможности
- Capability Containment — зачем изоляция сильнее надзора за поведением
- Tool Hijacking — что именно ограничивает sandbox
- Jailbreak Attacks — «ограничение последствий» сорвавшейся модели
- Guardrails — sandbox как Action-слой защиты
- Computer Use — Firecracker/gVisor для агента, управляющего экраном
- Agent Security — место изоляции в модели угроз
- Tool Calling — реестр с allowlist как тот же принцип уровнем выше