English translation is not available yet. Showing Russian content.
Как делать sandboxing для agent tools (изоляция выполнения)?
Краткий тезис
Sandboxing (песочница) для инструментов AI-агента — это обязательная практика безопасности, позволяющая выполнять код и команды, инициированные агентом, в изолированной среде. Ключевые методы: контейнеризация (Docker, gVisor, Kata Containers), микро-VM (Firecracker) и WebAssembly (WASI). Изоляция включает ограничение файловой системы (read-only), сети (только белый список API), ресурсов (CPU, memory, time) и привилегий. Правильный выбор песочницы — компромисс между безопасностью, производительностью и сложностью эксплуатации.
1. Зачем нужен sandboxing в agentic RAG?
Агент получает способность вызывать tools (инструменты): выполнять код, читать/писать файлы, ходить по сети. Без изоляции злонамеренный или ошибочный запрос может:
- удалить системные файлы,
- запустить DoS-атаку,
- получить доступ к закрытым данным,
- установить вредоносное ПО.
Даже если агент не злонамерен, он может следовать инструкции пользователя, которая приводит к нежелательным действиям (например, execute_python("import shutil; shutil.rmtree('/')")). Sandboxing предотвращает такие инциденты, выполняя код в среде, которая не может влиять на хост.
2. Основные техники изоляции: обзор
| Техника | Уровень изоляции | Производительность | Время запуска | Примеры инструментов |
|---|---|---|---|---|
| Контейнеризация | Процессы (Linux namespaces, cgroups) | Нативная (малый оверхед) | Секунды | Docker, Podman, containerd |
| Виртуализация с микро-VM | Аппаратная виртуализация (KVM) | ~5-10% оверхед | Миллисекунды | Firecracker, Kata Containers |
| WebAssembly (WASI) | Изолированная песочница на уровне Wasm runtime | Очень низкий (компилируемый) | Миллисекунды | Wasmtime, Wasmer, WasmEdge |
| Secure Containers (gVisor) | Пользовательское ядро (syscall interposition) | Средний (10-30% оверхед) | Секунды | gVisor (runsc) |
Каждая техника имеет свои сильные стороны. Контейнеры — самый популярный выбор: просты в использовании, широко поддерживаются, но изоляция несовершенна (общее ядро). Микро-VM дают почти полную изоляцию за счёт отдельного ядра, но сложнее в управлении. WebAssembly идеален для лёгких песочниц (выполнение кода на Python/C/Go в безопасной среде), но ограничен по возможностям (некоторые системные вызовы недоступны).
3. Контейнеризация: Docker, Podman, gVisor
Docker — де-факто стандарт. Каждый вызов инструмента запускается в отдельном контейнере, который удаляется после завершения. Для повышения безопасности:
- Используйте read-only root filesystem (
--read-only). - Монтируйте только необходимые директории (например,
/tmpкак tmpfs, объём ограничен). - Отключайте привилегии:
--security-opt no-new-privileges,--cap-drop ALL. - Ограничивайте ресурсы:
--memory,--cpus,--ulimit nofile=100. - Устанавливайте timeout на выполнение (через Docker SDK или внешний таймаут).
Пример запуска Python-кода с помощью Docker Python SDK:
import docker
import tempfile
import os
client = docker.from_env()
code = "print('hello')"
# Сохраняем в файл
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
script_path = f.name
try:
container = client.containers.run(
'python:3.11-slim',
f'python /tmp/script.py',
volumes={os.path.dirname(script_path): {'bind': '/tmp', 'mode': 'ro'}},
read_only=True,
mem_limit='128m',
cpu_period=100000,
cpu_quota=50000, # 0.5 CPU
network_mode='none', # без сети
timeout=30, # таймаут 30 секунд
remove=True # удалить после выполнения
)
output = container.decode('utf-8')
except docker.errors.ContainerError as e:
output = f"Error: {e.stderr}"
finally:
os.unlink(script_path)
gVisor (runsc) — альтернатива Docker с песочницей на уровне syscall. Каждый системный вызов перехватывается и обрабатывается безопасным ядром (Sentry). Это даёт дополнительную изоляцию, но снижает производительность.
Kata Containers — каждый контейнер работает в лёгкой виртуальной машине (QEMU/Firecracker), что обеспечивает изоляцию на уровне аппаратуры, сохраняя совместимость с Docker CLI.
4. Микро-VM: Firecracker от AWS
Firecracker — микро-виртуальная машина, используемая AWS Lambda и Fargate. Запуск занимает ~125 мс, оверхед минимален. Каждый вызов инструмента может запускать свою микро-VM с отдельным ядром Linux.
- Изоляция: полная, как у VM, но без лишнего «гостевого ПО».
- Ограничения: нужно подготавливать образы (rootfs + kernel), управлять сетью и ресурсами через API (REST на Unix socket).
- Когда использовать: когда требуется максимальная безопасность (агент с высокими привилегиями, multi-tenant) и допустимо 100-200 мс оверхеда на запуск.
Пример архитектуры: агент → оркестратор (Firecracker-containerd) → микро-VM с задачей → возврат stdout/stderr.
5. WebAssembly (WASI) — лёгкая песочница
WebAssembly System Interface (WASI) — платформа для запуска кода (C/C++, Rust, Go, Python через компиляцию или трансляцию) в изолированной среде без полноценной ОС.
- Плюсы: очень быстрый запуск (<5 мс), малый расход памяти (несколько МБ), предсказуемая производительность.
- Минусы: не все языки и библиотеки доступны; ограниченный доступ к файлам (WASI preview 1) и сети (WASI preview 2 — расширяется).
- Инструменты: Wasmtime от Bytecode Alliance, Wasmer, WasmEdge.
Пример выполнения Python-кода через WasmEdge с компиляцией Python в Wasm (используя wasmtime-py):
import wasmtime
import wasmtime.loader
# загрузка модуля с Python runtime в Wasm
store = wasmtime.Store()
module = wasmtime.Module.from_file(store, 'python.wasm')
linker = wasmtime.Linker(store)
linker.define_wasi() # WASI стандартный импорт
instance = linker.instantiate(module)
# запуск кода через передачу строки
result = instance.exports["run"](store, "print('hello')")
Для продакшена удобнее использовать extism — фреймворк для плагинов на Wasm, поддерживающий HTTP-интерфейс.
6. Ограничение файловой системы
Даже внутри контейнера нужно минимизировать доступ к файлам:
- read-only rootfs (монтируется из образа без права записи).
- Временные файлы помещаются в tmpfs (in-memory), которая очищается после завершения.
- Для чтения данных из базы знаний (если инструменту нужно) — монтировать только конкретную директорию как bind mount в режиме ro.
- Запретить монтирование /proc,
/sys,/devв режиме записи (использовать --security-opt seccomp=default.json). - Использовать chroot или pivot_root (Docker делает это автоматически).
7. Ограничение сети
Большинство инструментов не должны иметь доступ в интернет. Варианты:
- network_mode='none — полное отключение сети.
- allowlist: разрешить только определённые API (например, https://api.openai.com). Для контейнеров можно настроить iptables или eBPF-фильтры, но проще использовать прокси (например,
squidс ACL) или DNS-фильтрацию. - DNS: для Docker можно задать
--dns 0.0.0.0или не указывать DNS. - Для микро-VM — сетевая карта подключена только к виртуальному мосту без доступа к внешней сети.
8. Ограничение ресурсов: CPU, Memory, Time
Важно предотвратить случайное или злонамеренное истощение ресурсов:
- CPU: через cgroups (в Docker параметры
--cpus,--cpu-shares). - Memory:
--memory,--memory-swap(swap ограничить или выключить). - Disk: через
--storage-opt size=...(если нужно ограничить размер образа). - Time: абсолютный таймаут на выполнение (через Docker SDK timeout или сигнал SIGKILL после N секунд).
- Number of processes:
--pids-limit(ограничить fork-бомбу).
Рекомендуемые лимиты для типового инструмента execute_python:
- CPU: 1 ядро (100% одного vCPU)
- Memory: 256 MB (может хватить для большинства скриптов)
- Timeout: 30 секунд
- PID limit: 100
9. Пример полной реализации: безопасный execute_python
Спроектируем микросервис на FastAPI, который принимает код и возвращает результат выполнения в песочнице.
Архитектура
- Агент → HTTP POST /run → сервис → Docker SDK → контейнер → возврат stdout/stderr.
- Контейнер: python:3.11-slim с read-only ROOT, network=none, лимиты 256MB/1CPU/30s.
Реализация (схематично):
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import docker
import uuid
import tempfile
app = FastAPI()
client = docker.from_env()
class RunRequest(BaseModel):
code: str
@app.post("/run")
def run_code(req: RunRequest):
run_id = uuid.uuid4().hex[:8]
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(req.code)
host_path = f.name
cont_path = f"/sandbox/{run_id}.py"
try:
container = client.containers.run(
"python:3.11-slim",
f"python {cont_path}",
volumes={os.path.dirname(host_path): {"bind": "/sandbox", "mode": "ro"}},
read_only=True,
mem_limit="256m",
cpu_period=100000,
cpu_quota=100000, # 1 CPU
network_mode="none",
pids_limit=100,
detach=True
)
result = container.wait(timeout=30)
output = container.logs(stdout=True, stderr=True)
container.remove()
except docker.errors.APIError as e:
raise HTTPException(500, str(e))
finally:
os.unlink(host_path)
return {"output": output.decode("utf-8"), "exit_code": result["StatusCode"]}
Дополнительно: добавить логирование, мониторинг (Prometheus метрики по времени выполнения, ошибкам), rate limiting.
10. Мониторинг, аудит и обнаружение аномалий
Даже песочница не гарантирует 100% безопасность. Необходимо:
- Логирование: все вызовы инструментов, входные параметры, выходные данные (с фильтрацией чувствительных данных).
- Метрики: количество вызовов, время выполнения, ошибки, превышение лимитов.
- Алерты: если какой-либо контейнер попытался использовать сеть (несмотря на
none) или выделить много памяти — сигнал о возможной атаке. - Аудит образов: использовать только минимальные образы (например, python:3.11-slim вместо
full), подписанные цифровой подписью. - Аномалии: ML-модель может выявлять подозрительный код (например, обращение к
/proc).
Пет-проект для закрепления
Задача: реализовать песочницу для выполнения Python-кода в контейнерах с ограничениями и проверкой на подозрительные конструкции.
Инструменты:
- Docker (или Podman)
- Python + docker SDK
- FastAPI (для HTTP API)
- (По желанию) библиотека
banditдля статического анализа кода.
Шаги:
- Напишите простой API с эндпоинтом
/execute, принимающим JSON с полемcode. - Перед выполнением проверьте код с помощью
bandit(базовые уязвимости:eval,exec,subprocess). - Создайте Docker-образ на основе
python:3.11-slimс минимальными зависимостями. - В обработчике запускайте контейнер с параметрами:
- read-only rootfs,
network='none',- лимиты: 128MB RAM, 0.5 CPU, 10 секунд timeout,
- удаление контейнера после выполнения.
- Возвращайте stdout/stderr или ошибку.
- Добавьте логирование всех запросов и метрики (через
prometheus_client).
Ожидаемый результат: веб-сервис, который безопасно выполняет произвольный Python-код. Попробуйте отправить while True: pass — сервис завершит по таймауту. Попробуйте import os; os.remove('/') — контейнер завершится с ошибкой, хост не пострадает.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 884 | Как проектировать инструменты для AI-агента? |
| 885 | Как обеспечить безопасность агента при вызове внешних инструментов? |
| 887 | Как обрабатывать ошибки в инструментах (timeout, unexpected output)? |
| 888 | Как логировать и мониторить работу агента? |
| 889 | Как масштабировать agentic RAG-систему? |
Навигация
- Предыдущий: 885
- Следующий: 887
- Индекс: 00. Индекс разборов