# opencode-sandbox [English](README.md) · **Русский** Дайте [OpenCode 2](https://opencode.ai) работать над проектом с полными правами — без вопросов «разрешить этот путь?», — а его ошибки пусть остаются внутри песочницы. ```bash cd my-project opencode-sandbox init # один раз на проект opencode-sandbox run # фильтр → netlock → самопроверки → веб-интерфейс OpenCode ``` ## Что это даёт | | | |---|---| | **Изоляция файлов** | bubblewrap: агент пишет только в проект, его worktree и разрешённые вами пути. Остальная система — только чтение; SSH-ключи, облачные учётки, `~/.kube/config`, профили браузеров и конфиги других агентов скрыты | | **Сетевой фильтр** | весь HTTP(S) идёт через squid с правилами проекта; прод и внутренние сети недоступны, пока вы их не разрешите | | **Netlock** | nftables на уровне ядра отклоняет всё, что идёт в обход фильтра: прямой TCP, IP-адреса, ping, обычный SSH | | **Без подтверждений** | внутри песочницы агенту разрешено всё, кроме `sudo` и `systemd-run` | | **Worktree** | одна сессия — одно дерево; создаются и удаляются командой `opencode-sandbox wt`, с хуками проекта (порты, `.env`, запуск стека) | | **Дополнительно** | панель «Токены» в веб-интерфейсе (состав контекста, расход, скорость модели), скилы после compaction возвращаются, [cc-safety-net](https://github.com/kenryu42/cc-safety-net) против разрушительных git/rm, уведомления на рабочий стол | Только Linux: bubblewrap, nftables, systemd, Docker. ## Содержание - [Требования](#требования) · [Установка](#установка) · [Быстрый старт](#быстрый-старт) - [Файлы проекта](#файлы-проекта) · [Настройки](#настройки) · [Сетевые правила](#сетевые-правила) - [Worktree](#worktree) · [Скилы и плагины](#скилы-и-плагины) - [Как это устроено](#как-это-устроено) · [Модель безопасности и ограничения](#модель-безопасности-и-ограничения) - [Решение проблем](#решение-проблем) · [Удаление](#удаление) · [Команды](#команды) ## Требования - Linux с непривилегированными user namespaces (bubblewrap должен работать от обычного пользователя) - `docker`, `bwrap` (bubblewrap), `sg` (shadow-utils), `curl`, `jq`, `ncat` (nmap-ncat), `git` - `sudo` один раз — для установки netlock - OpenCode 2: `npm i -g @opencode/cli` или локальная установка, путь — в `OCSB_OPENCODE_BIN` - по желанию: `xdg-dbus-proxy` — уведомления из песочницы ## Установка ```bash git clone --recursive https://git.neotokai.ru/public/opencode-sandbox.git ~/opencode-sandbox cd ~/opencode-sandbox make install # ссылка opencode-sandbox в ~/.local/bin, плагины opencode-sandbox doctor # проверка зависимостей и настроек хоста ``` Первый `run` ставит netlock через `sudo` (или вручную: `opencode-sandbox netlock setup`). Вас добавят в группу `opencode-sandbox`; перелогиниваться не нужно. Обновление: `git pull && make plugins`. ## Быстрый старт ```bash cd my-project opencode-sandbox init # создаст .opencode-sandbox/ и .cc-safety-net/policy.json $EDITOR .opencode-sandbox/egress.conf # разрешите хосты, которые нужны проекту ssh-add # только если агенту нужен git по SSH opencode-sandbox run ``` `run` поднимает фильтр, проверяет netlock, прогоняет самопроверки и запускает OpenCode. Браузер откроется на `http://127.0.0.1:4196/`: логин `opencode`, пароль — в `~/.local/share/opencode-sandbox/<проект>/password` (выводится при первом запуске). Закоммитьте `.opencode-sandbox/` (кроме `local/`) и `.cc-safety-net/` — каждый, кто склонирует проект, получит ту же песочницу. ## Файлы проекта ``` my-project/ ├── .opencode-sandbox/ │ ├── sandbox.conf настройки (bash) в git │ ├── egress.conf сетевые правила в git │ ├── hooks/ │ │ ├── wt-post-add после создания worktree в git │ │ └── wt-pre-rm перед удалением worktree в git │ └── local/ личные переопределения, не в git │ ├── sandbox.conf │ └── egress.conf └── .cc-safety-net/policy.json исключения cc-safety-net в git ``` Состояние проекта лежит вне репозитория, в `~/.local/share/opencode-sandbox/<имя>/`: база сессий OpenCode, кэши, пароль веб-интерфейса, конфиг Docker-клиента, сгенерированный squid.conf. ## Настройки `sandbox.conf` — файл bash. Слои, побеждает последний: 1. умолчания пакета 2. `~/.config/opencode-sandbox/sandbox.conf` — ваши, для всех проектов 3. `.opencode-sandbox/sandbox.conf` — проекта 4. `.opencode-sandbox/local/sandbox.conf` — ваши, для этого проекта 5. `OCSB_<ИМЯ>=…` в окружении — на один запуск (только скаляры) `opencode-sandbox config` показывает итоговые значения. Когда файлы читаются, `OCSB_PROJECT_DIR` уже задан — его можно использовать в значениях. Относительные пути — от проекта; `~/` работает. | Настройка | По умолчанию | Что задаёт | |---|---|---| | `OCSB_NAME` | имя каталога проекта | имя каталога состояния и контейнера фильтра | | `OCSB_WEB_PORT` | `4196` | порт веб-интерфейса; сам OpenCode — на следующем | | `OCSB_EGRESS_PORT` | `3128` | порт фильтра на 127.0.0.1 и шлюзе docker0 | | `OCSB_OPENCODE_BIN` | `opencode` из PATH | бинарник OpenCode 2 | | `OCSB_ENV_FILE` | — | файл `KEY=VALUE` с токенами MCP-серверов и провайдера модели | | `OCSB_KUBECONFIG` | — | kubeconfig агента (`~/.kube/config` остаётся скрытым) | | `OCSB_ENV` | `()` | дополнительные `KEY=VALUE` для OpenCode | | `OCSB_RW` | `()` | дополнительные пути на запись | | `OCSB_HIDE` | `()` | дополнительные скрытые пути | | `OCSB_RW_HOME` | `.cache .npm .asdf .bundle .gem …` | каталоги инструментов в `~` на запись (если есть) | | `OCSB_HIDE_HOME` | `.gnupg .aws .kube/config .claude …` | скрытые пути в `~` | | `OCSB_SKILLS` | `(opencode-sandbox)` | скилы, которые монтируются агенту | | `OCSB_SKILL_DIRS` | `()` | дополнительные каталоги со скилами | | `OCSB_PLUGINS` | skill-compact-resurrect | файлы плагинов из `plugins/` (или абсолютные пути) | | `OCSB_PANEL` | `1` | панель «Токены» в веб-интерфейсе | | `OCSB_NOTIFICATIONS` | `1` | уведомления через фильтрующий прокси D-Bus | | `OCSB_DISPLAY` | `0` | `1` — окна агента (например, браузер Playwright) на вашем экране через сокет Wayland | | `OCSB_TMP` | `private` | `private` — свой `/tmp` агента в каталоге состояния; `shared` — `/tmp` хоста | | `OCSB_SANDBOX` | `1` | `0` — только фильтр и netlock, без bubblewrap | | `OCSB_WORKTREES_DIR` | `../<проект>-worktrees` | куда класть worktree | | `OCSB_WT_PREFIX` | — | каталог дерева = `<префикс>` | | `OCSB_WT_BASE` | текущий `HEAD` | от какой ветки создаются деревья | | `OCSB_WT_SLUG_REGEX` | — | регулярка с одной группой: slug из имени ветки | | `OCSB_OPENCODE_CONFIG` | `~/.config/opencode-sandbox/opencode.jsonc` или пакета | глобальный конфиг OpenCode | | `OCSB_STATE_DIR` | `~/.local/share/opencode-sandbox/<имя>` | каталог состояния | | `OCSB_NETLOCK_GROUP` | `opencode-sandbox` | группа netlock | | `OCSB_DOCKER_GW` | адрес docker0 | где контейнеры видят фильтр | Пример — проект с внутренними сервисами, файлом токенов и worktree: ```bash OCSB_NAME=shop OCSB_WEB_PORT=4296 # разные порты — несколько проектов одновременно OCSB_EGRESS_PORT=3138 OCSB_ENV_FILE=~/.config/shop/agent.env OCSB_HIDE=(../shop-secrets) OCSB_ENV=("SHOP_ENV=dev") OCSB_WT_PREFIX=shop-wt- OCSB_WT_BASE=main OCSB_WT_SLUG_REGEX='SHOP-([0-9]+)' ``` ## Сетевые правила Политика — чёрный список: что не запрещено, то разрешено. Всегда запрещены, если не разрешены явно: внутренние сети (10/8, 172.16/12, 192.168/16, 100.64/10, link-local, loopback), имена без точки, порты кроме 80 и 443. ``` allow jira.example.com gitlab.example.com # побеждает block allow-regex ^[a-z0-9-]+\.staging\.example\.com$ block .example.com # домен и все поддомены block-regex ^prod- connect git.example.com:2222 10.0.0.5:6443 # туннели: git по SSH, API Kubernetes expect-block https://prod.example.com/ # самопроверки для `run` и `egress verify` expect-pass https://gitlab.example.com/ expect-pass https://10.0.0.5:6443/version -k # опции curl — после URL ``` Порядок проверки: `connect` → порты кроме 80/443 запрещены → `allow` → `block` → имена без точки и внутренние сети запрещены → остальное разрешено. Слои (правила складываются): [egress/defaults.conf](egress/defaults.conf) → `~/.config/opencode-sandbox/egress.conf` → `.opencode-sandbox/egress.conf` → `.opencode-sandbox/local/egress.conf`. **Куда класть хосты компании.** В `egress.conf` проекта: он лежит в репозитории компании, и новый коллега получает рабочие правила вместе с `git clone`. То, что нельзя коммитить, — в `local/egress.conf`. В самом пакете хостов компаний нет. Изменения применяются при следующем `run` или сразу командой `opencode-sandbox egress up` (перечитывает правила без перезапуска squid). `egress render` показывает сгенерированный squid.conf, `egress logs` — каждый запрос и отказ в реальном времени. ## Worktree ```bash opencode-sandbox wt add feature-SHOP-42-cart --seed # аргументы после ветки — хуку opencode-sandbox wt rm 42 # откажет, если есть незакоммиченное opencode-sandbox wt rm 42 --force # выбросит незакоммиченное opencode-sandbox wt list ``` `wt add` создаёт `/` в новой ветке от `OCSB_WT_BASE` и запускает в нём `hooks/wt-post-add`. `wt rm` запускает `hooks/wt-pre-rm`, проверяет незакоммиченные изменения и удаляет дерево (ветка остаётся). Хуки получают `WT_DIR`, `WT_BRANCH`, `WT_SLUG`, `MAIN_ROOT` и дополнительные аргументы; ошибка хука прерывает команду. Шаблоны — `hooks/*.example` после `init`. Агенту сказано создавать worktree только для изменений кода в новой ветке — не для вопросов, поиска и ревью. ## Скилы и плагины - **Скилы.** `OCSB_SKILLS` — каталоги скилов (с `SKILL.md`), которые монтируются агенту только на чтение как глобальные: из [skills/](skills/) или ваших `OCSB_SKILL_DIRS`. Скилл `opencode-sandbox` объясняет агенту среду: что доступно на запись, что значит 403 от прокси, как попросить хост, как работать с worktree. - **Плагины** пакета — git submodules в [plugins/](plugins/): [context-panel](plugins/context-panel) (панель «Токены») и [skill-compact-resurrect](plugins/skill-compact-resurrect) (скилы переживают compaction). - **Глобальный конфиг OpenCode** [opencode/opencode.jsonc](opencode/opencode.jsonc) добавляет opencode-notifier и cc-safety-net. Замените его своим `~/.config/opencode-sandbox/opencode.jsonc`. Собственный `opencode.jsonc` проекта (модель, MCP-серверы, плагины проекта) работает как обычно. ## Как это устроено ``` opencode-sandbox run ├─ docker: opencode-sandbox-egress-<имя> squid, сеть хоста, 127.0.0.1 + docker0 ├─ xdg-dbus-proxy сессионная шина — только уведомления └─ sg opencode-sandbox netlock: группе доступны только loopback и DNS └─ bwrap / только чтение; проект, worktree, состояние — запись └─ opencode serve :4197 прокси в окружении, свои XDG, права = allow браузер → :4196 (панель внутри OpenCode) → :4197 ``` Внутри песочницы: - **На запись:** проект, каталог worktree, каталог состояния, `OCSB_RW`, `OCSB_RW_HOME` и собственный `/tmp`. Всё остальное — только чтение. - **Свой `/tmp`:** в `/tmp` хоста лежат сокеты, через которые можно выйти из песочницы (tmux, X11, сокеты браузеров, других программ), поэтому у агента свой `/tmp` — в `<состояние>/tmp` (файлы старше 7 дней удаляются при запуске). Скриншоты и логи агента ищите там. Docker смонтирует путь из `/tmp` из `/tmp` хоста — монтируйте пути проекта. - **Скрыто:** `OCSB_HIDE_HOME`, `OCSB_HIDE`, приватные ключи в `~/.ssh` (`config`, `known_hosts` и `*.pub` видны; подписывает ssh-agent хоста). - **Сеть:** `HTTP(S)_PROXY` указывают на фильтр. `GIT_SSH_COMMAND` пускает ssh через него с `-F ~/.ssh/config` (внутри user namespace системные конфиги ssh, принадлежащие root, выглядят как принадлежащие `nobody`, и ssh их отвергает). `DOCKER_CONFIG` передаёт прокси сборкам и контейнерам. - **OpenCode:** свои каталоги `XDG_*` в каталоге состояния (ваш `~/.config/opencode` не используется); права — через `OPENCODE_CONFIG_CONTENT`: разрешено всё, `sudo` и `systemd-run` запрещены. - **Сессионная шина:** заменена на `xdg-dbus-proxy`, который пропускает только `org.freedesktop.Notifications`; настоящая шина позволила бы агенту запускать процессы вне песочницы через `systemd-run --user`. - **Экран:** по умолчанию нет — `DISPLAY`, `WAYLAND_DISPLAY` и `XAUTHORITY` пустые, cookie X скрыт; браузеры — headless. С `OCSB_DISPLAY=1` агент получает только сокет Wayland (и `/dev/dri` для видеокарты): его окна появляются на вашем экране, но, в отличие от X11, клиент Wayland не может читать чужие окна и подделывать в них ввод. X11 не пробрасывается никогда. `xdg-open` отключён; интерфейс открывает `run` из вашей обычной сессии. - **Без тихого отката:** при `OCSB_SANDBOX=1` и неработающем bubblewrap `run` не запустится. **Netlock** (`netlock setup`, sudo) создаёт группу `opencode-sandbox`, добавляет в неё вас и ставит `opencode-sandbox-netlock.service`: таблица nftables разрешает сокетам этой группы только `127.0.0.0/8`, `::1` и DNS-серверы из `resolv.conf`, остальное отклоняет. OpenCode запускается с этой группой — под правило попадают агент и всё, что он запускает. ## Модель безопасности и ограничения Цель — удержать агента, который ошибается (не тот путь, `rm -rf` не там, запрос в прод), а не остановить целенаправленного злоумышленника. - **Docker — выход наружу.** Агенту доступен сокет Docker, а Docker может смонтировать любой путь хоста (`docker run -v /:/host`). Если это важно — не давайте агенту Docker; закрыть это можно фильтрующим прокси для сокета или rootless Podman. - **Общее ядро.** bubblewrap — это namespaces, а не виртуальная машина. - **Разрешённые хосты — канал утечки.** Данные могут уйти на любой хост, который пропускают правила. - **Проект доступен на запись.** Ошибки внутри него настоящие; cc-safety-net блокирует типичные разрушительные команды, git хранит историю — коммитьте чаще. - С `OCSB_TMP=shared` доступны `/tmp` хоста и его сокеты. - С `OCSB_DISPLAY=1` агент может открывать окна на вашем экране и пользоваться буфером обмена, пока его окно в фокусе. ## Решение проблем | Симптом | Причина и что делать | |---|---| | `Read-only file system` | путь вне разрешённых на запись. Если агенту он правда нужен — добавьте в `OCSB_RW` | | `403` от прокси, `CONNECT tunnel failed` | хост не разрешён: добавьте `allow` (или `connect host:port` для порта не 443) в `egress.conf`, выполните `opencode-sandbox egress up` | | git по SSH: `Permission denied (publickey)` | в ssh-agent нет ключей: `ssh-add` перед `run` | | git по SSH: `403` | добавьте `connect <хост>:<порт>` в `egress.conf` | | `port … is busy` | другой проект или старый экземпляр: задайте другие `OCSB_WEB_PORT` / `OCSB_EGRESS_PORT` | | `netlock is NOT working` | `opencode-sandbox netlock setup`, затем `netlock verify` | | `bubblewrap cannot create a sandbox` | отключены непривилегированные user namespaces (`kernel.unprivileged_userns_clone`, ограничения AppArmor) | | нет панели «Токены» | открывайте порт 4196, а не 4197; журнал — `<состояние>/data/opencode/log/context-panel.log` | | нет уведомлений | установите `xdg-dbus-proxy` | | браузерные инструменты падают (`Missing X server`, `Failed to connect to Wayland display`) | в песочнице нет экрана: включите `OCSB_DISPLAY=1` (сессия Wayland) или запускайте браузер headless (Playwright MCP: `--headless`) | | не найти файл, который агент положил в `/tmp` | он в `~/.local/share/opencode-sandbox/<имя>/tmp` | | `docker build` не скачивает образы | реестры доступны через squid: смотрите `egress logs` | ## Удаление ```bash opencode-sandbox egress down # в каждом проекте opencode-sandbox netlock teardown # sudo; группа остаётся: sudo groupdel opencode-sandbox cd ~/opencode-sandbox && make uninstall rm -rf ~/.local/share/opencode-sandbox # состояние: сессии, кэши, пароли docker rmi opencode-sandbox-egress ``` ## Команды ``` opencode-sandbox init добавить .opencode-sandbox/ в текущий репозиторий opencode-sandbox config итоговые настройки opencode-sandbox doctor зависимости и настройки хоста opencode-sandbox run [аргументы opencode] всё сразу, затем OpenCode opencode-sandbox prepare фильтр + netlock + самопроверки, без OpenCode opencode-sandbox egress up|down|status|logs|verify|render opencode-sandbox netlock setup|teardown|status|verify opencode-sandbox wt add <ветка> [аргументы] | rm <имя|путь> [--force] | list ``` ## Лицензия [MIT](LICENSE)