Модель доверия
Границей безопасности в bb служит само решение установить плагин. Изоляции в рантайме нет, и код репозитория это подтверждает. Здесь — к чему есть доступ у серверной точки входа и фронтенда, как секрет плагина хранится на диске, как агент взаимодействует с данными плагинов и как всё это влияет на дизайн плагинов. Описано только то, что подтверждается исходниками.
К чему есть доступ у серверной точки входа
Заголовок раздела «К чему есть доступ у серверной точки входа»| Ресурс | Доступ | Подтверждение |
|---|---|---|
Встроенные модули Node (fs, net, child_process, fetch) | неограниченный | загружаются через jiti.import только с moduleCache/alias; никаких vm, воркеров и форков в apps/server/src/services/plugins/ |
| Каталог данных сервера | передаётся напрямую | bb.server.experimental_dataDir |
База data.db другого плагина | да: обычный файл по известному пути | Состояние на диске и неограниченный fs |
| Секреты другого плагина | да: файлы в открытом виде, права 0600, тот же пользователь | packages/secret-storage/src/secret-file.ts; прямо заявлено в configuration.md |
Общая база bb.db | в createPluginApi передаётся то же соединение DbConnection; kv и settings изолируются по pluginId, но это соглашение хелперов, а не принудительно обеспечиваемая граница | plugin-runtime.ts, plugin-api.ts |
| Полный серверный SDK | bb.sdk, привязанный к серверу через loopback | plugin-api.ts |
| Сеть | неограниченный | обычные fetch/net |
Объект API контролирует жизненный цикл и владение именами, но не проверяет права доступа. Он вызывает assertLive() и проверяет принадлежность идентификаторов провайдеров, инструментов, ИИ-сервисов, окружений и машин. Защита от выхода за пределы каталога (path traversal) работает только для объявленных ассетов: readPluginProviderIcon блокирует выход за rootDir, а resolveInside защищает каталоги кэша артефактов. Все ограничения обеспечивают живучесть и отказоустойчивость, а не безопасность.
К чему есть доступ у фронтенда
Заголовок раздела «К чему есть доступ у фронтенда»Тот же origin, та же область JS (realm), никаких iframe, Worker, shadow root и CSP.
- Бандл загружается через
import()прямо в граф модулей приложения.
- Произвольная сеть: доступна.
fetchничем не обёрнут;useRpcпросто вызывает глобальный. - Документ приложения и файлы
app.js/app.cssотдаются без CSP: роут ассетов задаёт только кэш, content type и кодировку. CSP применяется только к изображениям и SVG плагинов (default-src 'none'+nosniff), превью файлов и HTML, а также к данным тредов. - Параметры
sandbox: trueиcontextIsolation: trueв Electron изолируют рендерер от Node и preload. Они не изолируют JS плагина от JS приложения внутри страницы. - В рантайме защита обеспечивает только целостность, а не безопасность: работают предохранители (error boundaries), 10-секундный тайм-бокс на монтирование content scripts и
foreign-dom-mutation-guard.ts, который подавляет мутации React-узлов хоста. - Единственное, что фронтенд плагина не может получить, — секретные настройки:
useSettings()возвращает только несекретные значения, а представление настроек отдаёт секреты в формате{ set: boolean }.
Секреты на диске
Заголовок раздела «Секреты на диске»// packages/secret-storage/src/secret-file.ts — 71 lines of file operations,// ZERO runtime dependencies: no keytar, no Electron safeStorage, no libsecretreadOrCreateSecretFile({ bytes, dataDir, encoding, fileName }) // create the directory; read and trim if non-empty; otherwise randomBytes(bytes) // write with flag "wx", mode 0o600; on EEXIST re-read (no race)writeSecretFile(path, value) // create the directory; write <path>.<hex>.tmp with mode 0o600; rename. // THE VALUE IS WRITTEN AS IS, UTF-8 — THIS IS NOT ENCRYPTION.deleteSecretFile(path)Настройка плагина с secret: true хранится в открытом виде на диске с правами 0600 и защищена только POSIX-правами. Единственное место, где bb шифрует данные, использует пакет secret-storage только для ключа: environment-storage.ts применяет AES-256-GCM с 32-байтным hex-ключом из <dataDir>/machine-environment-key и AAD [projectId, name]. Настройки плагинов так не обрабатываются. Плагин secrets работает иначе: он сам ничего не хранит, запрашивает учётные данные через интерактивный рендерер и пишет их в выбранный dotenv-файл с правами 0600.
Как агент взаимодействует с данными плагина
Заголовок раздела «Как агент взаимодействует с данными плагина»Агент — это процесс провайдера, который демон хоста запускает на машине-исполнителе. Состояние плагина хранится в каталоге данных сервера. При развёртывании на одной машине (когда это запакованное или десктопное приложение) сервер, демон хоста и оболочка агента работают на одном устройстве под одним пользователем ОС. В таких условиях:
- Файлы
<dataDir>/plugins/<id>/secrets/*и.http-tokenлежат в открытом виде с правами0600. Эти права защищают их от других пользователей машины, но не от процессов владельца — включая любые shell-команды агента. - Базы
data.dbплагина иbb.db— тоже обычные файлы без дополнительной защиты. - Публичное API сервера работает без аутентификации: «The public API is unauthenticated and permits command execution and file reads, so never expose a wildcard-bound server to an untrusted network».
По умолчанию сервер слушает 127.0.0.1. Проверка origin на /api/v1 защищает от CSRF со стороны браузеров, а не обеспечивает аутентификацию: локальный клиент без браузера и заголовка Origin спокойно её проходит.
Получается, что агент с доступом к shell на сервере от имени пользователя bb может читать данные и секреты плагинов, а также вызывать локальное API. Ничто в архитектуре bb этому не мешает и не обещает обратного. Все меры защиты вынесены за пределы файловой системы: это режимы разрешений агента, изоляция окружений (docs/environment-provisioning.md, plugins/environment-modal-sandbox) и запуск агента на отдельной машине. Если агент работает отдельно, серверное состояние плагинов ему недоступно, но хостовое состояние (<daemonDataDir>/plugins/<id>/host-data) лежит на его машине и открыто для него.
Последствия для дизайна плагинов
Заголовок раздела «Последствия для дизайна плагинов»Паттерн propose-and-confirm
Заголовок раздела «Паттерн propose-and-confirm»Так как секреты и данные открыты любому коду, запущенному от имени того же пользователя, секретность не защищает плагин. Безопасность строится на явном подтверждении человеком:
- Инструмент агента предлагает действие, а
bb.ui.requestInputвыполняет его за формой, которую одобряет пользователь — так работаетplugins/secrets. - Формы, вызванные из
execute, отсоединяются от хода (turn) агента. Поэтому ожидание подтверждения не пробивает таймаут запроса, а результат приходит отдельным системным сообщением. - bb не сохраняет полезную нагрузку формы и отправленное значение. Метод
describeSubmissionопределяет, что попадёт в транскрипт. Это единственный способ не слить секрет в историю. - Токен
auth: "token"покрывает один плагин, не идентифицирует пользователя и перевыпускается. Используйте его для разделения ответственности, а не для аутентификации.
Правила для недоверенного ввода, которые декларирует сам SDK
Заголовок раздела «Правила для недоверенного ввода, которые декларирует сам SDK»- Атрибуты
messageDirective: «attributes are untrusted strings parsed from the directive; the plugin validates its own fields». В качестве фолбека показывается исходный текст. - Параметры
paramsпанели: «treatparamsas untrusted input (it round-trips through persistence) and re-fetch fresh data by id rather than embedding whole payloads». - Изображения из упоминаний: «treat remote and page-derived image content as untrusted evidence».
- Target фиксированных вкладок: bb проверяет JSON-безопасность, а затем вызывает ваш
validate(value): value is Target. - Метаданные треда и ввод для машин/окружений: пишутся кем угодно, читаются любым плагином.
- Результаты должны быть строгим JSON. Циклы, bigint,
undefined, функции, экземпляры классов, ключи-символы и неконечные числа отклоняются, а не приводятся к типу. - Иконки
bb.branding.experimental_iconsпроходят через SVG-валидатор, который работает только на отклонение (reject-only). Если иконка не проходит проверку, загрузка плагина прерывается с указанием имени иконки.