Files
obsidian-vault/Research/Isolation of Agent Execution - OS and Container Level.md
T

196 lines
28 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 'Изоляция исполнения агента: OS и контейнерный уровень'
tags:
- agent-security
- sandboxing
- seccomp
- gvisor
- firecracker
- bubblewrap
- ssh
- docker
- research
created: '2026-09-29'
status: complete
---
# Изоляция исполнения агента: OS и контейнерный уровень
Исследование: 8 подходов + паттерны ограниченного доступа к хосту.
Контекст: дать агенту собирать/тестировать код, но не дать дотянуться до чужих файлов и секретов.
---
## 1. gVisor (Google) — application kernel в userspace (`runsc`)
- gVisor — **не seccomp-фильтр, не обёртка над Linux-примитивами и не VM**. Это «третий подход»: application kernel, реализующий Linux-подобный интерфейс, написанный на **Go (memory-safe)** и работающий в **userspace**. Себя описывают как «seccomp on steroids».
- Архитектура: **Sentry** — application kernel на песочницу (перехватывает системные вызовы, сигналы, управление памятью, потоки); **Gofer** — на контейнер, даёт доступ к ФС. Системные вызовы приложения **не проходят** насквозь в ядро хоста — их обрабатывает Sentry.
- OCI-runtime `runsc` интегрируется с Docker и Kubernetes (GKE Sandbox, minikube) — можно запускать обычные контейнеры «под gVisor» без переписывания.
- Накладные расходы: **эмерджентны** (threads/memory mappings, а не фиксированные guest-ресурсы), быстрый старт, но **плохо для syscall-heavy нагрузок** (компиляция — как раз syscall-heavy, важно учитывать).
- Преимущество для агентов: **защита от container-escape через kernel-эксплойты** — единственная поверхность это сам Sentry в Go. Комбинируется с rule-based (seccomp) для defense-in-depth.
- Ссылка: https://gvisor.dev/docs/
## 2. Firecracker microVM (AWS) + jailer
- Firecracker — lightweight **microVM** (KVM-based, Rust), созданный для multi-tenant serverless (Lambda, Fargate). **Аппаратная виртуализация** = самая сильная граница изоляции при быстром старте (десятки мс) и низком footprint.
- Компаньон **`jailer`** создаёт многослойную границу для VMM-процесса:
- `chroot` + `pivot_root` в новый mount namespace; копирование бинарника в jail (защита от shared memory с другими Firecracker).
- Сброс в **непривилегированный uid/gid** (`--uid`/`--gid`) — VMM не root.
- **cgroups v1/v2** — лимиты CPU/памяти (`--cgroup cpuset.cpus=0`, `--parent-cgroup`).
- `setrlimit` — **fsize** (ограничение размера создаваемых файлов) и **no-file** (по умолчанию 2048 дескрипторов).
- Новый **PID namespace** (`--new-pid-ns` → `clone(CLONE_NEWPID)`, процесс становится init(1)), опционально `--netns`, `setsid()` + redirect I/O в /dev/null при `--daemonize`.
- **Вычищает все env-переменные** родителя и закрывает все FD кроме stdin/stdout/stderr.
- Каждая microVM изолирована обычными Linux-barriers через jailer + **seccomp-фильтры** для самого VMM-процесса (см. отдельный документ про seccomp filtering).
- Ссылки: https://github.com/firecracker-microvm/firecracker/blob/main/docs/jailer.md , https://firecracker-microvm.github.io/
## 3. E2B (e2b.dev) — sandbox для AI-агентов
- Managed sandbox-API именно под AI-агентов; под капотом — **Firecracker microVM** (у E2B открытая инфра-стековая часть, Firecracker-основа). Долгоживущие песочницы с **persistence**.
- Жизненный цикл: `Sandbox.create({timeoutMs})`, `setTimeout` для продления, `kill()` для остановки. Лимиты **до 24ч (Pro) / 1ч (Base)**, далее pause+resume (состояние сохраняется, окно таймаута сбрасывается).
- **Эфемерность на задачу**: таймаут → авто-pause/kill; каждый sandbox — отдельная microVM (свой kernel, своя ФС, свой network). Агент не видит хост.
- API: `Sandbox` из JS/TS/Python SDK; getInfo → sandboxId/templateId/startedAt/endAt; metadata. Шаблоны (templates) из Dockerfile.
- Для паттерна «собирать/тестировать код»: sandbox = полное Linux-окружение, изолированное от хоста инженера; секреты хоста недостижимы физически.
- Ссылка: https://e2b.dev/docs/sandbox
## 4. Daytona — control/compute plane sandbox-платформа
- **Три плоскости**: Interface plane (SDK Python/TS/Ruby/Go/Java, CLI, Dashboard, **MCP-сервер**, SSH) → Control plane (NestJS API, Proxy, Snapshot builder, Sandbox manager; Redis/PostgreSQL/Auth0) → Compute plane (runners, sandbox daemon, snapshot store, volumes).
- Изоляция: каждый sandbox — **isolated instance с собственными Linux namespaces** (process, network, filesystem mounts, IPC) + **dedicated vCPU, RAM и disk на sandbox**. Multi-tenancy на уровне organization boundary.
- **Snapshot builder** собирает снапшоты из **Dockerfile или pre-built image** → пушит в внутренний OCI-registry. Быстрый provisioning эфемерных окружений под задачу.
- Network: отдельный HTTP-прокси с **host-based routing** — `{port}-{sandboxId}.{proxy-domain}`; инжектит auth-заголовки, поддерживает WS.
- **Sandbox daemon / Toolbox API** внутри sandbox: файловые и git-операции, исполнение кода, computer use, log streaming, терминальные сессии. Volumes — персистентное хранилище между sandbox.
- Ссылка: https://www.daytona.io/docs/en/architecture/
## 5. Modal Sandboxes — контейнеры для untrusted code на лету
- Явно позиционируются как **secure containers for executing untrusted user or agent code**; определяешь контейнер **в runtime** (`modal.Sandbox.create(...)`), исполняешь произвольный код. Use-case прямо: «execute code generated by a language model», checkout git-репы и запуск тест-сьютов.
- **Эфемерность**: default max lifetime **5 минут**, до **24 часов** через `timeout`; `idle_timeout` для авто-остановки (sandbox активен, если есть running command / stdin-write / open TCP over tunnel).
- Lifecycle: Created → Scheduled (provision CPU/mem/GPU/volumes) → Started → Ready (readiness probe) → Finished (exit/timeout/OOM/terminate).
- **Readiness probes** (TCP или Exec, interval_ms default 100ms, max 5 min) — идеально для «подняли окружение, накатили зависимости, готово к тестам».
- Filesystem Snapshots — сохранить состояние и восстановить в новом sandbox (для работ >24ч).
- Изоляция: каждое исполнение — отдельный контейнер на worker'е Modal; запуск команд через `sb.exec(...)` с таймаутами.
- Ссылка: https://modal.com/docs/guide/sandbox
## 6. Fly Machines (Fly.io) — быстрые VM с супербыстрым стартом
- **Быстро-запускаемые VM** с start/stop на **subsecond** скоростях; контроль количества машин, lifecycle каждой машины, ресурсов и region placement через **REST API или flyctl**.
- Модель «одна машина = один экземпляр приложения/окружения»: полная VM-граница изоляции, микро-уровень ресурсов. Подходит как эфемерная «машина на задачу» для агента — создать, выполнить сборку/тесты, остановить.
- Эфемерность: stop/start по API; отдельные `[machines]` с независимым lifecycle. Для агента удобно: run-per-task VM, старый экземпляр уничтожается.
- Интеграция с flyd / hardware-virtualization (Firecracker-подобное) — граница уровня гипервизора, а не просто namespaces.
- Ссылки: https://fly.io/docs/machines/ , https://fly.io/machines/overview
## 7. Anthropic Claude Code sandbox modes (macOS Seatbelt)
- **Platform support**: sandboxed Bash tool встроен; работает на **macOS, Linux, WSL2**. **Native Windows не поддерживается** (только WSL2).
- **macOS**: ничего ставить не надо — используется встроенный фреймворк **Seatbelt** (sandbox-exec / sandbox profiles).
- **Linux/WSL2**: требуются пакеты **`bubblewrap`** (ФС-изоляция) и **`socat`** (relay сетевого трафика через sandbox-прокси). Опционально seccomp-фильтр из `@anthropic-ai/sandbox-runtime` (блок AF_UNIX sockets).
- **Режимы-аналоги** (у Claude Code — permission modes + sandbox modes): **Manual** стартует read-only (built-in read-only команды `ls/cat/git status` без вопросов); **Accept Edits** авто-одобряет правки и фиксированный набор ФС-команд (`mkdir,touch,rm,mv,cp,sed`) в рабочей директории; **Auto mode** — отдельная classifier-модель ревьюит и блокирует небезопасные действия.
- **Working directory boundary**: в Manual режиме запись **только в стартовую папку и подпапки**; чтение вне границы через Read/Grep/Glob требует разрешения. Расширяется `--add-dir` / `permissions.additionalDirectories`, сужается sandbox `denyRead`.
- **Network isolation**: первый запрос к новому домену → промпт; `curl`/`wget` **не авто-одобряются**; deny-правила матчат команду «как написано», а независимую от текста сетевую политику даёт именно sandbox network isolation.
- Escape hatch: `dangerouslyDisableSandbox` параметр (retry вне sandbox через обычный permission flow); можно запретить `allowUnsandboxedCommands: false` / `failIfUnavailable: true` для managed-деплоя.
- Linux нюанс: на Ubuntu 24.04+ с `kernel.apparmor_restrict_unprivileged_userns=1` нужен AppArmor-профиль для `bwrap` (иначе unprivileged userns запрещён).
- Ссылки: https://docs.claude.com/en/docs/claude-code/sandboxing , https://docs.claude.com/en/docs/claude-code/security
## 8. OpenAI Codex CLI sandbox (Seatbelt на macOS, bubblewrap/Landlock на Linux)
- **Дефолт локально**: сеть **выключена**, write-права **ограничены активным workspace**; OS-enforced sandbox + approval policy сверху.
- **Sandbox modes**: `read-only`, `workspace-write` (рабочая директория + оговорённые исключения), `danger-full-access`. Профили прав: built-in `:read-only`, `:workspace`, `:danger-full-access`, кастомные через `[permissions.<name>]`.
- **macOS**: **Seatbelt** (как и Chromium). **Linux**: исторически Landlock, теперь **bubblewrap по умолчанию** (Landlock отвергается для ФС-политик, т.к. **не умеет изолировать app-server Unix sockets**; флаг `features.use_legacy_landlock` надо выключать при апгрейде).
- Реализация `codex-linux-sandbox` (крейт) поверх bwrap:
- **`PR_SET_NO_NEW_PRIVS`** + in-process **seccomp network filter**.
- ФС **read-only по умолчанию** (`--ro-bind / /`), writable-корни через `--bind <root> <root>`.
- **Защищённые подпути** под writable-корнями (`.git`, resolved `gitdir:`, `.codex`) переоткрываются read-only через `--ro-bind`.
- **`--unshare-user`**, `--unshare-pid` (по умолчанию), `--unshare-net` при запрете сети без прокси.
- Порядок по path-specificity: `/repo=write`, `/repo/a=none`, `/repo/a/b=write` корректно комбинируются.
- Защита от symlink/несуществующих путей: монтирование **`/dev/null`** поверх symlink или первого отсутствующего компонента.
- **Managed proxy mode**: `--unshare-net` + внутренний TCP→UDS→TCP bridge; после старта bridge seccomp **блокирует создание AF_UNIX/socketpair**.
- `bwrap` ищется на PATH вне CWD; при отсутствии — bundled fallback (`codex-resources/bwrap`); WSL1 не поддерживается (нет user namespaces).
- **Two-phase в Codex cloud**: *setup phase* (сеть есть, ставим зависимости) → *agent phase* (по умолчанию offline). **Секреты доступны только в setup и удаляются до старта agent phase** — это ровно паттерн «дать собирать, но не дать украсть секреты».
- Managed: `requirements.toml` может запретить `approval_policy = "never"` и `sandbox_mode = "danger-full-access"`.
- Ссылки: https://developers.openai.com/codex/sandbox/ , https://github.com/openai/codex/blob/main/codex-rs/linux-sandbox/README.md
## 9. Chromium / Chrome seccomp-bpf sandbox (эталон многослойности)
- **Multi-process security model**: процессы, обрабатывающие недоверенные данные (renderer, utility, GPU), ограничены; к привилегированным ресурсам — только через **медиацию Browser-процесса** (broker).
- **Linux**: `namespaces` + **seccomp-BPF** (+ опционально legacy SELinux/AppArmor/Landlock в зависимости от доступных фич ядра). Несколько слоёв: confinement процесса **и** сокращение exposed kernel attack surface.
- Ключевая проблема seccomp-bpf: фильтрация на уровне syscall даёт трудно-понятную семантику; если процесс A под одним seccomp-bpf policy, нужно **гарантировать, что он не может повлиять на целостность процесса B** под другим policy (иначе sandbox escape).
- **macOS**: `sandbox/mac` использует **Seatbelt**; **Windows**: restricted tokens + job objects + alternate desktops + integrity levels. Один код-бейз, три разных OS-механизма.
- **Broker-паттерн**: GPU/utility не ходят в ядро напрямую, а просят Browser-процесс выполнить операцию — переносится на любые агентские архитектуры (агент просит «runner» выполнить привилегированное действие).
- Ссылки: https://github.com/chromium/chromium/tree/main/sandbox , https://chromium.googlesource.com/chromium/src.git/+/master/sandbox/linux/
## 10. bubblewrap (bwrap) — unprivileged sandboxing (упоминался в DeepSeek Harness как sandbox seam)
- **Low-level unprivileged sandboxing tool** (Flatpak, rpm-ostree, и теперь Claude Code / Codex на Linux). Ключ: использует **Linux user namespaces**, поэтому доступен **непривилегированному пользователю** — в отличие от docker/systemd-nspawn, которые админам.
- Всегда создаёт **новый mount namespace**: root на **tmpfs**, невидимый с хоста, автоочистка при выходе последнего процесса. Опции конструируют ФС: `--ro-bind /usr /usr`, `--proc /proc`, `--dev /dev`, `--bind`.
- Namespaces: `CLONE_NEWUSER` (`--unshare-user`, маппинг uid/gid), `CLONE_NEWIPC`, `CLONE_NEWPID` (`--unshare-pid` + trivial pid1 reaps zombies — решает docker pid-1 problem), `CLONE_NEWNET` (`--unshare-net`, только loopback), `CLONE_NEWUTS`; плюс **seccomp-фильтры**.
- Использует **`PR_SET_NO_NEW_PRIVS`** — выключает setuid-бинарники, классический способ выйти из chroot.
- ⚠️ Важно: bwrap **не готовый sandbox с политикой** — уровень защиты целиком определяется переданными аргументами. Владельцы могут повысить риск DoS.
- ⚠️ Подводные камни: без фильтрации `TIOCSTI` нужен `--new-session` (CVE-2017-5226); **всё, что смонтировано, потенциально эскалирует привилегии** (напр. bind D-Bus socket = исполнение через systemd → используйте `xdg-dbus-proxy`); nested sandboxing: браузеры со своим seccomp требуют разрешить syscall `seccomp`, иначе не смогут применить собственные ограничения.
- **Установка на Ubuntu 24.04+**: `apparmor_restrict_unprivileged_userns=1` блокирует unprivileged userns → нужен AppArmor-профиль `profile bwrap /usr/bin/bwrap flags=(unconfined) { userns }`.
- Ссылка: https://github.com/containers/bubblewrap
---
# Доступ к хосту с ограниченным набором команд (изоляция машины инженера)
## Паттерн A: SSH forced command (`authorized_keys command=`)
- В `~/.ssh/authorized_keys` перед ключом — опции: `command="..."` заставляет **sshd игнорировать любую команду/шелл клиента и выполнить серверную команду**. Формат: `options keytype base64 comment`.
- В паре с **`restrict`** (новый агрегат) — отключает PTY, agent forwarding, X11 forwarding, TCP forwarding и user rc processing **одним словом**. Эквивалент набора: `no-pty, no-port-forwarding, no-agent-forwarding, no-X11-forwarding, no-user-rc`.
- Дополнительно: `from="IP"` — ограничение источника; `environment="..."` (только если `PermitUserEnvironment`); `permitopen`/`permitlisten` для точечного туннелирования.
- **Wrapper-паттерн**: `command="/usr/local/bin/agent-wrapper.sh"` — скрипт читает `$SSH_ORIGINAL_COMMAND`, валидирует по **whitelist** допустимых команд, и только затем исполняет через `exec`. Это и есть command-whitelist wrapper: агент шлёт `ssh host "git status"`, wrapper сверяет с allowlist, остальное → reject + лог.
- Реальный пример в природе: **`git-shell`** — логин-шелл только для git-операций; тот же принцип в GitHub/GitLab-деплой-ключах.
- Отдельно можно задать **`ForceCommand`** в `sshd_config` (глобально/по Match-блоку) — серверная сторона, независимая от клиента.
- Ссылки: https://man.openbsd.org/sshd.8 , https://www.simplified.guide/ssh/restrict-authorized-keys-command , https://superuser.com/questions/299927/
## Паттерн B: bastion / jump host (граница «хост-инженера ↔ агент»)
- Отдельный **bastion-пользователь** (`agentrunner`) на отдельном хосте/VM: агент аутентифицируется ключом **только** к bastion, не к машине инженера. С бастиона — либо `ProxyJump`, либо жёсткий wrapper к целевому хосту.
- `ssh -J bastion target` / `ProxyJump` в `~/.ssh/config` — единая точка аудита и rate-limit; удобно логировать весь трафик агента.
- Bastion в **непривилегированном** аккаунте, `rbash`/restricted shell, chroot-джейл (см. паттерн A + `sshd_config Match`), все forward'ы запрещены кроме разрешённых.
- Машина инженера полностью закрыта снаружи; даже скомпрометированный ключ агента ведёт лишь на узкий bastion с whitelist.
- Ссылка: https://man.openbsd.org/ssh.1 (раздел про `-J`, port forwarding, agent forwarding)
## Паттерн C: restricted shells (rbash) — как дополнение, не как основная защита
- `rbash`/`bash --restricted`: нельзя `cd`, менять `$PATH`/`$SHELL`, использовать `/` в именах команд, редиректы, `exec`. Работает только в связке с корректно настроенным `PATH` и неизменяемыми переменными.
- ⚠️ `rbash` **легко обходится** (интерпретаторы, `vi`, `less`, `awk`, `find -exec`, git hooks) — общеизвестен как слабая защита. Использовать **только как второй слой**; основной — forced command + wrapper + опционально контейнер/VM.
- Ссылка: https://man.openbsd.org/sshd.8 (login process), https://oneuptime.com/blog/post/2026-03-02-how-to-restrict-user-ssh-access-to-specific-commands-on-ubuntu/view
---
# Docker socket proxy: как дать `docker build` без доступа к `docker.sock`
## Tecnativa/docker-socket-proxy
- **Проблема**: дать сервису доступ к `/var/run/docker.sock` = дать **root на хосте** (даже на весь swarm). Прокси блокирует то, что сервису не нужно, возвращая **HTTP 403 Forbidden**.
- Реализация: официальный Alpine-based **HAProxy** + маленький конфиг; **plain HTTP proxy** к хосту-unix-socket (TLS нет by design — ограничивать доступ сетью/Docker firewall).
- Управление — **env-переменные по URL-префиксу** (напр. `AUTH` → блокирует `/auth/*`), значения **`0`=revoke, `1`=grant**:
- Разрешены по умолчанию: `EVENTS`, `PING`, `VERSION` (почти безвредны).
- **Запрещены по умолчанию (критично)**: `AUTH`, `SECRETS`, **`POST`** (при `POST=0` разрешены только GET/HEAD → весь API read-only).
- Прочее по умолчанию off: `BUILD`, `CONTAINERS`, `EXEC`, `IMAGES`, `NETWORKS`, `VOLUMES`, `ALLOW_START/STOP/RESTARTS/PAUSE/UNPAUSE`, `COMMIT`, `CONFIGS`, `DISTRIBUTION`, `GRPC`, `INFO`, `NODES`, `PLUGINS`, `SERVICES`, `SESSION`, `SWARM`, `SYSTEM`, `TASKS`.
- **Для `docker build`**: включить `IMAGES=1` и `BUILD=1` (или положиться на BuildKit-эндпоинты), но `POST` нужен разрешённым — иначе build не пройдёт. Точечно: НЕ давать `EXEC` (иначе агент влезет в чужой контейнер), `SECRETS`, `AUTH`.
- Запуск: `docker run -d --privileged --name dockerproxy -v /var/run/docker.sock:/var/run/docker.sock -p 127.0.0.1:2375:2375 tecnativa/docker-socket-proxy` → клиент через `DOCKER_HOST=tcp://localhost:2375`.
- **Обязательно**: не публиковать порт в публичную сеть — только во внутренний Docker network с прокси и потребителем. Использовать GitHub Container Registry образ, а не Docker Hub (остальные теги deprecated).
- **Более сильные альтернативы для build**: **rootless docker**, **BuildKit без root** (`buildkitd` как непривилегированный демон, `buildctl`), **kaniko**/**buildah** (сборка образа без демона вообще), **sysbox**, **Podman** с user-namespaced rootless containers. Для «агенту надо собрать образ» лучше не давать docker.sock вообще, а дать rootless `buildah`/`kaniko` или удалённый build-сервис.
- Ссылка: https://github.com/tecnativa/docker-socket-proxy
---
# Сводка: как собрать «сборка/тесты — да, чужие файлы/секреты — нет»
| Слой | Механизм | Инструмент |
|---|---|---|
| Граница железной изоляции | microVM | Firecracker (+jailer), Fly Machines |
| Сильная граница, но дешевле VM | userspace kernel | **gVisor** (`runsc`) |
| Unprivileged ФС-песочница | user/mount/pid/net namespaces + `NO_NEW_PRIVS` | **bubblewrap** |
| ФС-политика в ядре | Landlock / seccomp-bpf | Chromium, Codex (legacy) |
| macOS-граница | Seatbelt / sandbox-exec | Claude Code, Codex, Chromium |
| Ограничение syscall | seccomp-bpf | Chromium, Firecracker VMM, Codex net-filter |
| Непривилегированный юзер | uid/gid drop, rootless | jailer `--uid/--gid`, rootless docker |
| Эфемерность | timeout + авто-kill + снапшоты | E2B, Modal, Daytona, Fly |
| Секреты | доступ только в setup-фазе, удаляются до agent phase | **Codex two-phase** |
| Доступ к хосту | forced command + whitelist wrapper + bastion | `authorized_keys command=`/`restrict`, `ForceCommand`, `-J` |
| Docker без docker.sock | API-прокси с точечными правами | tecnativa/docker-socket-proxy |
**Рекомендуемый минимум для агента-сборщика**: непривилегированный отдельный пользователь → bubblewrap/gVisor sandbox с read-only корнем и одним writable workspace → `--unshare-net`/прокси для сети → секреты только на этапе setup → отдельный bastion-пользователь с forced command + whitelist wrapper для доступа к хосту инженера → docker build через socket-proxy или (лучше) rootless BuildKit/buildah/kaniko.