Перейти к содержимому

Модель доверия

Границей безопасности в 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
Полный серверный SDKbb.sdk, привязанный к серверу через loopbackplugin-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 libsecret
readOrCreateSecretFile({ 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) лежит на его машине и открыто для него.

Так как секреты и данные открыты любому коду, запущенному от имени того же пользователя, секретность не защищает плагин. Безопасность строится на явном подтверждении человеком:

  • Инструмент агента предлагает действие, а 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 панели: «treat params as 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). Если иконка не проходит проверку, загрузка плагина прерывается с указанием имени иконки.