Files
opencode-sandbox/README.ru.md
T

306 lines
21 KiB
Markdown
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.
# 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_SANDBOX` | `1` | `0` — только фильтр и netlock, без bubblewrap |
| `OCSB_WORKTREES_DIR` | `../<проект>-worktrees` | куда класть worktree |
| `OCSB_WT_PREFIX` | — | каталог дерева = `<префикс><slug>` |
| `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_WORKTREES_DIR>/<OCSB_WT_PREFIX><slug>` в новой ветке от
`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`. Всё остальное — только чтение.
- **Скрыто:** `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`.
- **Браузер:** `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 хранит историю — коммитьте чаще.
- `/tmp` общий с хостом.
## Решение проблем
| Симптом | Причина и что делать |
|---|---|
| `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` |
| `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)