0. Что сервер видит и чего не видит — прочитайте, прежде чем что-либо арендовать
Обещание продукта — хранение с нулевым разглашением (zero-knowledge; ADR-0014, ADR-0019): сервер хранит то, что ему нужно для координации, и не может прочитать профиль. Это было перемерено, а не перечитано, 2026-09-04 — запросами к базе данных и листингом bucket после полного круга между двумя устройствами [MEASURED]:
| где | в открытом виде | запечатано / захэшировано (сервер не может прочитать) |
|---|---|---|
accounts |
адрес почты · флаг totp_enabled · дата создания (S10: для неподтверждённого адреса — время его ПОСЛЕДНЕЙ регистрации: замещающая регистрация перезапускает 24-часовой отсчёт) · 16-байтная соль входа (не секретна по замыслу) · verified_at (S8: когда адрес подтверждён кодом из письма; NULL = зарегистрирован, но не подтверждён — войти нельзя, удаляется через 24 h после последней регистрации) · email_code_failures (бюджет неверных кодов, 0–5) · auth_kdf (S10, ADR-0040: версия вывода сохранённого верификатора — NULL = прежний, auth-v1; метка, записанная вместе с верификатором и объявляемая на login-init, которая говорит клиенту, как выводить, и никогда не позволяет выводить что-либо серверу) |
password_hash — хэш Argon2id верификатора, выведенного на клиенте, а не парольной фразы (ADR-0019 C1) · totp_seed_enc 60 байт, AEAD под VFLIN_TOTP_KEY |
plans (S12) |
прайс владельца как ДАННЫЕ: код, цена за месяц, потолки (NULL — потолка нет), продаётся ли тариф ещё. Это не про какой-то аккаунт; строка здесь потому, что правка цены не должна быть релизом | — |
email_codes (S8) |
relay_response + relayed_at (S12, ISSUE-204: что ответил почтовый релей, когда взял письмо, — строка состояния и, у большинства релеев, id очереди. Это про КОНВЕРТ: кода там нет никогда, и именно это владелец предъявляет провайдеру, когда письмо потерялось) · для какого аккаунта код, его назначение (verify — активирует аккаунт; device — допускает одно новое устройство и привязан к хэшу этого устройства), срок действия (15 min), использован ли он — и HMAC под секретом сервера от 6-значного кода (code_hash), а не сам код; строка с NULL вместо аккаунта — это приманка, которую при resend-code оставляет неизвестный или уже подтверждённый адрес, чтобы обе ветви делали одинаковую работу |
сам код (отправляется один раз, одна попытка, 15 min) |
devices |
id устройства (UUID демона) · имя устройства · когда было в сети последний раз | refresh_token_hash (43 символа; сам токен никогда не хранится) · secret_hash (S9, 43 символа: SHA-256 секрета устройства, который последний вход выдал этому устройству, — то, что делает его «знакомым»; заменяется при каждом входе, исчезает с device.revoke, NULL, пока устройство снова не войдёт с кодом из письма; сам секрет никогда не хранится) |
profiles_meta |
id профиля · ключ блоба · хэш содержимого · размер шифртекста · wallets (S12: сколько кошельков держит профиль — число, которое ЗАЯВЛЯЕТ клиент при push/commit, а не считает сервер; именно против него проверяется потолок кошельков в тарифе, то есть потолок ограничивает честного клиента, а не изменённого, и контракт это говорит) · вектор версий (id устройств → счётчики) · fence (S5: fence синхронизационной аренды последнего принятого push, монотонный счётчик — метаданные координации, не выведенные ни из какого открытого текста) · состояние · дата обновления |
— |
account_keyrings |
JSON-конверт: version, параметры KDF (mCost/tCost/pCost), saltKek, реестр ключей устройств (id устройства → 32-байтный открытый ключ подписи), accountPub |
wrappedMkPassword, wrappedMkRecovery, wrappedAccountPriv (nonce AEAD + шифртекст) · MK-MAC |
bucket vflin-blobs |
для каждого блоба — открытый манифест (VFB1 + JSON): id профиля · id устройства · вектор версий · coreVersion · payloadSize открытого текста · внутренний хэш содержимого — описан в PROFILE_STORAGE_SYNC.md §«манифест (открытый, с защитой целостности)» |
wrappedDek · sealedRecord (имя / отпечаток / прокси) · полезная нагрузка — AEAD, непрозрачные байты |
audit_log, orgs, shares, subscriptions |
пусты после этого круга: демон никогда не отправляет записи журнала действий (log.append для него недостижим), команда не создавалась, тариф не покупался |
— |
password_resets (S4) |
для какого аккаунта сброс, его срок действия, использован ли он — и хэш отправленного токена (token_hash), а не сам токен; строка с NULL вместо аккаунта — приманка, которую оставляет неизвестный адрес, чтобы reset-init делал одинаковую работу в обеих ветвях |
сам токен сброса (отправляется один раз, 30 min, одноразовый) |
billing_events (S4) |
для какого аккаунта событие, его тариф/статус, номер заказа у провайдера, когда оно пришло — чтение истории платежей для счетов в кабинете | — |
Итак, оператор с root-доступом к VPS узнаёт: кто завёл аккаунт, сколько у него устройств и профилей, какого размера каждый профиль, какая версия Chromium открывала его последней и когда он менялся. Он не узнаёт ни парольной фразы, ни содержимого профиля, ни cookies, ни отпечатка, ни прокси. Говорите пользователям именно это; не говорите «сервер ничего не хранит».