openflow · доверие

Как устроено и что хранится

Не обещания «мы всё зашифровали» — а прямые ссылки на то, что реально делает код: где живёт вход в Google, что лежит в базе и сколько, чем зашифрованы мастер-токены, что не хранится никогда, как отключить доступ и какие действуют лимиты частоты.

01 / 06

Где живёт вход в Google

Пароль и второй фактор Google на сервер не попадают. Это не обещание, а свойство схемы: сервер физически не видит пароль, потому что весь вход происходит в браузере на твоей машине.

твой терминалзапускает onboard_local.js
твой браузерplaywright-core, реальный Chrome
Googleты сам вводишь пароль и 2FA
очередь openFlowполучает только токен сессии

Скрипт /onboard_local.js открывает настоящий браузер на твоей машине и захватывает одноразовый oauth_token и email после того, как ты сам вошёл в Google — сервер этот момент не видит. Дальше ты передаёшь пару {email, oauth_token} инструменту add_account_token (или в POST /v1/onboard/token): сервер обменивает одноразовый токен на мастер-токен через библиотеку gpsoauth, проверяет его одним читающим вызовом и сохраняет уже зашифрованным.

oauth_token одноразовый и живёт минуты. Если обмен не произошёл сразу, токен протухает и процедуру нужно повторить — это не баг, а короткое окно жизни самого токена от Google.

02 / 06

Что хранится и сколько

Ниже — реальные таблицы из service/db.py и файл мастер-токенов, а не пересказ. Один пункт списка — одно поле таблицы.

задачи · jobs

таблица jobs
  • params — промпт и параметры вызова
  • status, error, attempts — что произошло с задачей
  • account_used, owner — каким аккаунтом и от чьего имени
  • created_at / started_at / updated_at — время постановки, старта, обновления

витрина · showcase

таблица showcase
  • prompt, model — то, что видно публично на главной
  • file, w, h — локальный файл кадра и его размер
  • строки нет = публикация включена по умолчанию; отключение — платный тариф, удаляет уже опубликованное, а не прячет

ключи доступа · users

таблица users
  • key_hash — только SHA-256 хэш ключа, сам ключ не хранится нигде
  • key_prefix, name — по какому обрезку узнать ключ и как ты его назвал
  • active, status — включён ли ключ прямо сейчас

Google-аккаунты · accounts

таблица accounts
  • email, project, reset_day — какой аккаунт подключён и когда сбрасывается квота
  • сам мастер-токен в этой таблице не лежит — он в отдельном зашифрованном файле, смотри следующий блок

вход через GitHub · identities

таблица identities
  • provider_user_id — числовой id GitHub, ключ записи
  • login — логин хранится только для показа, привязка не на нём: смена логина не рвёт доступ и не отдаёт его чужому

персонажи · characters

таблица characters
  • name, description — как карточка называется и что в ней
  • portrait_media_id — какой кадр несёт сходство лица
  • entity_id, project, account — карточка в самом Flow

подписка и оплата · subscriptions, payment_orders

две таблицы
  • status, until — активна ли подписка и до какого момента
  • amount, currency, provider, status — заказ на оплату, без данных карты: их принимает платёжный провайдер, не openFlow

согласие и выход · owner_settings, session_epochs

две таблицы
  • terms_accepted_at — когда принял правила
  • epoch — счётчик выходов: «выйти» обнуляет разом все ранее выданные сессии этого владельца
03 / 06

Чем зашифрованы мастер-токены

Мастер-токен Google-аккаунта — это полный доступ к нему. Он не в базе данных: он в отдельном файле, зашифрованном AES-256-GCM.

алгоритм

tools/token_crypto.py
  • AES-256-GCM: шифрование и проверка подлинности в одном шаге
  • формат значения — base64(nonce[12] || ciphertext+tag)
  • подменённый файл не расшифруется тихо в мусор — GCM это заметит

ключ

/etc/openflow/token.key
  • лежит отдельно от файла с токенами — утечка одного файла не значит утечку ключа
  • права 600, владелец root
  • без ключа зашифрованный файл — просто байты

Честно про границу защиты: от человека, получившего root на самом сервере, это не спасает — там ключ читается вместе с данными. Схема защищает от утечки ФАЙЛА (бэкап, rsync, случайный коммит) — это самый вероятный сценарий из двух, но не единственный.

04 / 06

Что не хранится и как отключить доступ

  • Пароль и второй фактор Google — сервер их физически не видит, вход целиком проходит в твоём браузере.
  • Сам MCP-ключ — в базе только его SHA-256 хэш; ключ показывается один раз в момент выпуска.
  • Генерации с твоими загруженными фото (персонаж из фото, image_inputs, control-изображения) — не попадают в публичную витрину ни при каких настройках и тарифах.

Отозвать один ключ

В кабинете — кнопка у конкретного ключа (/me/keys/{prefix}). Ключ на другой машине продолжает работать: отзыв всегда точечный.

Выключить публикацию

На платном тарифе в кабинете. Уже опубликованные работы удаляются из витрины, а не прячутся.

Отключить Google-аккаунт целиком

Кнопки пока нет — пиши оператору через GitHub. Честнее сказать об этом прямо, чем изображать самообслуживание, которого ещё нет.

05 / 06

Метки на сгенерированном

Две разные вещи, и обе стоит знать до генерации, а не после скачивания.

Невидимая метка SynthID — есть всегда

Google встраивает её в каждый кадр и каждый ролик Veo и Imagen. Отключить её нельзя ни настройкой, ни через нас: это метка Google, а не наша. Она не видна глазом, но по ней содержимое определяется как сгенерированное ИИ.

Видимая «искорка» в углу — зависит от аккаунта

С 28 июля 2026 у Flow есть настройка «Visible watermarking» в аккаунте Google, под которым идёт генерация. По умолчанию она выключена не везде: умолчание задаёт регион аккаунта, а в части стран знак нельзя отключить вовсе.

Что делаем мы

Проверяем каждый готовый файл и возвращаем ответ в результате задачи полем visible_watermark. Если посмотреть не смогли — так и пишем, а не выдаём это за «чисто».

На 29 июля 2026 свежие генерации выходят без видимого знака. Ролики, сделанные до 28 июля, «искорку» содержат — они уже сгенерированы, и снять её нельзя.

06 / 06

Лимиты частоты

Очередь одна на всех, но выдача справедливая: следующую задачу получает тот, у кого меньше всего работает сейчас. Поэтому чужая пачка на тысячу генераций не отодвигает твою одну. Пределы ниже — минимумы, и они растут вместе с числом подключённых Google-аккаунтов: за это отвечает service/api.py::_admit, через которую проходит любая постановка задачи.

3
задачи одновременно — минимум, плюс 5 на каждый подключённый аккаунт
10
задач в минуту — минимум, плюс 3 на аккаунт
100
задач в час — минимум, плюс 120 на аккаунт

Логика простая: один аккаунт делает одну генерацию одновременно, поэтому смысл имеет ровно та ёмкость, которую ты подключил. Десять аккаунтов — это десять параллельных генераций и 1200 в час, а не «три, потому что так решили».

Отказ — HTTP 429 с заголовком Retry-After и текстом, который называет предел, текущее число и через сколько секунд отпустит: чужой лимит не тратится, а свой не пропадает молча.