# ADR-0003: Изоляция брейнов через отдельные PostgreSQL БД **Дата:** 2026-05-20 **Статус:** Принято ## Контекст ZBrain хостит несколько брейнов с разным уровнем чувствительности данных: - **zetit** — внутренние процессы ZETIT, не публичные - **telerapharma** — корпоративная инфраструктура с NDA - **personal** — личные данные владельца - **community** — данные о Смоленской 10, дом-чат, ЖКХ Утечка MCP токена с правами на один брейн **не должна** давать доступ к другим брейнам. Варианты изоляции в Postgres (от слабой к сильной): 1. **Одна БД, одна схема, отдельные таблицы с префиксами** — слабая изоляция, любой SQL injection даёт доступ ко всему 2. **Одна БД, отдельные schema'ы (zetit.pages, telerapharma.pages)** — изоляция через `search_path`, но один Postgres user видит всё 3. **Одна БД, отдельные пользователи с row-level security** — сложно, легко допустить ошибку в RLS политиках 4. **Отдельные БД с отдельными пользователями** — изоляция на уровне СУБД ## Решение Вариант 4: **отдельная БД + отдельный Postgres-пользователь на каждый брейн**. ``` PostgreSQL cluster ├── brainhub (БД) │ └── владелец: brainhub │ └── таблицы: users, sessions, tokens, brains, sources, projects, audit_log │ ├── gbrain_zetit (БД) │ └── владелец: gbrain_zetit │ └── полная схема gbrain (pages, chunks, embeddings, links, ...) │ ├── gbrain_telerapharma (БД) │ └── владелец: gbrain_telerapharma │ └── ... │ ├── gbrain_personal (БД) │ └── владелец: gbrain_personal │ └── ... │ └── gbrain_community (БД) └── владелец: gbrain_community └── ... ``` Каждый gbrain-instance подключается к Postgres под своим пользователем и **физически не видит** другие БД: ```sql -- Под пользователем gbrain_zetit \l -- Покажет только gbrain_zetit (и template/postgres) \c gbrain_telerapharma -- ERROR: permission denied for database gbrain_telerapharma ``` Brainhub использует свою БД `brainhub` под пользователем `brainhub` (тоже не видит остальные). ## Реализация В `scripts/create-brain.sh`: ```sql CREATE USER gbrain_${BRAIN_NAME} WITH PASSWORD '${random_password}'; CREATE DATABASE gbrain_${BRAIN_NAME} OWNER gbrain_${BRAIN_NAME}; GRANT ALL PRIVILEGES ON DATABASE gbrain_${BRAIN_NAME} TO gbrain_${BRAIN_NAME}; -- НЕ выдаём этому юзеру ничего на other databases ``` Пароль для каждого gbrain-юзера генерируется случайно и хранится: 1. В `/var/lib/zbrain/brains//config.json` (chmod 600, owner zbrain) 2. В DATABASE_URL внутри systemd unit (Environment="DATABASE_URL=...") `brainhub` НЕ хранит эти пароли в своей БД (они не нужны для admin-операций) — только знает на каком порту слушает gbrain instance. ## Последствия ### Плюсы - Физическая изоляция через ACL Postgres - SQL injection в одном брейне не даёт доступ к другим - pg_dump каждого брейна тривиально по отдельности - Удаление брейна = одна команда `DROP DATABASE gbrain_X` - Разные параметры тюнинга на разные брейны (если потребуется) ### Минусы - Нельзя сделать JOIN между брейнами в SQL (но это и не нужно — поиск через brainhub) - Каждая БД имеет свои индексы, общая нагрузка на shared_buffers Postgres делится - Backup делает дамп каждой БД отдельно (несложно) ### Митигация - shared_buffers задан с запасом (2GB на 8GB RAM) - HNSW индексы строятся в `maintenance_work_mem=512MB`, одного брейна за раз достаточно ## Альтернативы рассмотренные 1. **Row-level security в одной БД** — сложно, требует консистентного применения политик на каждой таблице gbrain (а у gbrain их десятки). Один пропущенный CREATE POLICY = утечка 2. **Отдельные Postgres-кластеры на каждый брейн** — overkill, 4× процессов, 4× shared_buffers 3. **Отдельные VM на каждый брейн** — экстремальная изоляция, не нужна на нашем масштабе