中文翻译暂不可用,显示俄语原文。

Как делать 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: через cgroupsDocker параметры --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 для статического анализа кода.

Шаги:

  1. Напишите простой API с эндпоинтом /execute, принимающим JSON с полем code.
  2. Перед выполнением проверьте код с помощью bandit (базовые уязвимости: eval, exec, subprocess).
  3. Создайте Docker-образ на основе python:3.11-slim с минимальными зависимостями.
  4. В обработчике запускайте контейнер с параметрами:
    • read-only rootfs,
    • network='none',
    • лимиты: 128MB RAM, 0.5 CPU, 10 секунд timeout,
    • удаление контейнера после выполнения.
  5. Возвращайте stdout/stderr или ошибку.
  6. Добавьте логирование всех запросов и метрики (через prometheus_client).

Ожидаемый результат: веб-сервис, который безопасно выполняет произвольный Python-код. Попробуйте отправить while True: pass — сервис завершит по таймауту. Попробуйте import os; os.remove('/') — контейнер завершится с ошибкой, хост не пострадает.


Связь с другими вопросами

ВопросТема
884Как проектировать инструменты для AI-агента?
885Как обеспечить безопасность агента при вызове внешних инструментов?
887Как обрабатывать ошибки в инструментах (timeout, unexpected output)?
888Как логировать и мониторить работу агента?
889Как масштабировать agentic RAG-систему?

Навигация