VFLIN
ВойтиСкачать

docs · docs/60-policy/SECURITY_POLICY.md · перевод от 2026-09-12 · с английской ревизии 6c8b535b7ae8

Политика безопасности

что где закрыто шлюзом и почему элемент интерфейса никогда не заявляет шлюз, которого код не выполняет

Назначение: сохранить сильные возможности сильными, но сделать опасные действия закрытыми шлюзом, поддающимися аудиту и, где возможно, обратимыми. Мы не калечим возможности ради «театра безопасности»; мы добавляем уровни, аудит и зелёное чтение, которое показывает радиус поражения. (Пересмотрено ADR-0036.)

1. Режимы политики

Режим Для чего Что разрешает
normal по умолчанию, повседневное CRUD профилей, запуск и остановка, прогоны автоматизации, синхронизация
power продвинутые операторы массовые операции, экспорт cookies и профилей, импорт миграции
danger/admin явно, редко жёсткое удаление, чтение секрета из хранилища, browser.eval, разрушительные операции над задачами
  • Режим привязан к сессии, задаётся явно (system.policy.set) и пишется в журнал при изменении.
  • Вызову, которому нужен режим выше, чем у сессии, отказывают с внятным «как повысить».
  • Режим — это заголовок на каждый запрос X-Vflin-Mode, по умолчанию normal, источник — --mode (CLI) и VFLIN_MCP_MODE (MCP, из окружения, чтобы агент не мог повысить себя сам). При неизвестном значении — fail-closed. Повышения на сессию нет, и system.policy.set нет. (ADR-0036)

2. Реестр опасных действий

Каждое действие ниже ОБЯЗАНО: объявить требуемый режим, породить запись в Action Log и назвать зелёное чтение, которое показывает его радиус поражения, — или записать, почему такого чтения нет (ADR-0034:131, ADR-0036). Пробного прогона, где он осуществим, нет.

  • export cookies / export profile (power) — материал сессии, который можно унести.
  • read vault secret (danger) — раскрытие секрета в открытом виде.
  • hard delete профиля или группы (danger) — необратимо.
  • mass update по многим профилям (power) — широкий радиус поражения.
  • browser.eval / произвольный код на странице (danger) — произвольное выполнение.
  • migration import (power) — принимает недоверенные внешние данные → валидация и песочница.
  • materialize → raw path (power) — путь в файловой системе обходит границу управления; удалённые клиенты получают непрозрачный дескриптор, сырой путь — только локальному интерфейсу или владельцу, всегда с аудитом.
  • разрушительные операции над задачами (danger).
  • profile.session.set (danger) — принимает в хранилище материал уровня аккаунта (cookies, токены сессий, пароли, семена TOTP). Секреты идут по loopback, как у vault.set, и обратно не читаются.
  • profile.session.clear (danger) — необратимо: убирает не только привязку, но и сами секреты из хранилища. Если в хранилище была единственная копия токена или семени, её больше нет. Отдельного пробного прогона нет, потому что profile.session.get (зелёный) уже показывает ровно те ссылки, которые сотрёт clear.
  • применение сессии при запуске — то самое использование этого материала и причина, по которой две записи выше — ещё не вся история:
    • session.inject (normal) — восстанавливает СОБСТВЕННОЕ сохранённое состояние браузера этого профиля. Идёт в аудит по профилю и сервису, чтобы запуск, распечатавший сессию, отличался от открытия пустого профиля.
    • session.login (danger) — ведёт настоящую форму входа и отправляет третьей стороне сохранённый пароль и сгенерированный код 2FA. Требуемый режим вычисляется из привязок профиля, а не из маршрута profile.start, именно потому, что агент может вызвать start в normal (SESSION_LOGIN_MODE_REQUIRED). Автозапуск автоматизации считается normal, так что сам по себе он никогда не может вызвать первую отправку учётных данных.
  • пространства имён хранилища, принадлежащие модулям (session/, wallet/), отвергаются обычным vault.set (VAULT_NAMESPACE_RESERVED). В них пишут только их собственные операции, закрытые шлюзом danger, — иначе вызывающий в режиме normal мог бы подменить учётные данные, которые подставит будущий запуск, и обойти тот шлюз этажом ниже.

3. Аудит (Action Log)

  • Только на дозапись, с выявляемой подделкой через цепочку хэшей (каждая запись несёт хэш предыдущей → любая правка или удаление рвёт цепочку), с необязательным периодическим внешним якорем для команд и комплаенса. По каждому актору (пользователь, CLI, MCP-агент): действие, цель, режим, краткое описание аргументов (секреты вычищены), результат, отметка времени. Для команд синхронизируется в облако.
  • Чтения журнала сами пишутся в журнал, когда речь о команде или комплаенсе.

4. Управление ключами (нулевое разглашение, несколько KEK — ADR-0008)

  • Шифрование конвертами: случайный ключ данных (DEK) на профиль, обёрнутый несколькими независимыми KEK, — разворачивает любой. По умолчанию: пароль (Argon2id) + обязательный Recovery Key; необязательно passkey (WebAuthn PRF) + связка ключей ОС; командные профили добавляют KEK ключа организации (восстановление с помощью администратора). Сервер хранит только шифртекст и копии обёрнутого DEK.

  • Сервер никогда не видит ни ключей, ни содержимого в открытом виде, в том числе при восстановлении, — никакого депонирования на сервере. Потеря всех факторов = потеря данных (честный предел сквозного шифрования, теперь многофакторный).

  • Параметры Argon2id подобраны высокими для настольной машины (OWASP и выше; точные числа ещё не назначены); DEK на профиль ограничивают радиус поражения и позволяют делиться по одному профилю. См. KEY_RECOVERY_COMPARISON.md.

  • org-KEK команды — это и якорь восстановления, и единственная точка компрометации. Хранение в GA (ADR-0028): orgPriv держит один администратор; принятый остаточный риск GA — компрометация одного администратора; пороговая схема и Шамир (без единственного держателя) — отложенная работа по упрочнению, а не требование GA (пересмотрено ADR-0028; сервер и ADR-0017 под Шамира не строились). Отзыв = ротация пары ключей организации и переобёртывание разделяемых DEK под новый orgPub' при удалении участника или смене владельца — это срез вперёд (удалённый участник не получает будущего доступа); настоящий срез назад (перевыпуск самих DEK и блобов, O(всех разделяемых)) отложен (ADR-0028, решение 4). Держите orgPriv в клиентском хранении администратора — никогда на сервере. Никакого депонирования на сервере, никогда (даже под давлением поддержки); предохранитель — обязательное подтверждение при настройке, что Recovery Key сохранён. (PHASE0_REVIEW M2; ADR-0028.)

  • ЧТЕНИЕ разделённого с командой = подлинность DEK (ADR-0029). Участник читает профиль, разделённый с его командой, открывая блоб выведенным для организации DEK и опираясь на тег AEAD и хэш содержимого BLAKE3, а НЕ на подпись владельца Ed25519 (проверка до расшифровки между аккаунтами структурно невозможна: у участника нет записи для ключа устройства владельца). Облако не может подделать блоб, который откроется под этим DEK (DEK у него никогда не было), так что нулевое разглашение и целостность против подмены облаком сохраняются. Принятый остаточный риск: злонамеренный участник команды, у которого есть DEK, мог бы подменить блоб для остальных — это угроза инсайдера внутри взаимно доверяющей команды (любой участник и так может прочитать и унести содержимое); закрыть её можно только аутентифицированными ключами устройств между аккаунтами, которых модель не раздаёт, а ключ владельца, положенный в раздачу, пришёл бы из того же неаутентифицированного каталога (ISSUE-029).

  • Локальная разблокировка: что и где хранится (ADR-0039). По умолчанию аккаунт открывается, не вводя ничего: случайный локальный ключ лежит в хранилище ключей ОС ($VFLIN_HOME/local-unlock.json {"dpapi": …}, CryptProtectData, область пользователя), а мастер-ключ, обёрнутый под ним (localWrappedMk), живёт в ЛОКАЛЬНОЙ части keyring’а — никогда в синхронизируемом конверте. session.unlock {local:true} заставляет демон открыть его; фактор никогда не пересекает Local API. Необязательный мастер-пароль переобёртывает MK и убирает локальный ключ (инвариант 6 — не вторая дверь рядом с открытой); хранится он только как свой Argon2id-обёрток MK, никогда в открытом виде. Код восстановления (Recovery Key из ADR-0008) показывается один раз, демоном никогда не хранится и служит путём на случай забытого пароля; его можно ротировать. Флаг masterPassword едет в конверте аккаунта связанным MAC, чтобы сервер не мог его перевернуть. Область пользователя DPAPI = другой пользователь Windows (или восстановленная машина) блоб не откроет — это свойство, а не изъян; путь внутрь там — пароль или код. Keychain в macOS и Secret Service в Linux — названные швы, ещё не построенные; где хранилища ключей нет, фактор сохраняет форму открытого текста, читаемого только владельцем, и keyring.status сообщает localFactor: plaintext (никогда молча).

  • Верификатор входа в облако (ADR-0040, ISSUE-185). Из authVerifier для входа не должен выводиться KEK шифрования. Верификатор auth-v1 — это Argon2id над BLAKE3-derive("vflin cloud auth verifier v1", server_salt): те же параметры, что у KEK, и односторонняя область соли, которую сервер не может подобрать в KEK, — версионированный в login-init (kdf), и клиент отвергает неизвестную версию. Клиентская половина построена; серверная и сайтовая (S9/W6) — должок. Пока сервер не объявит v1, действует прежний вывод (по умолчанию выключено).

5. Гигиена секретов

  • Секреты — только через хранилище, по ключу-ссылке. Никогда в исходниках скриптов, журналах, сообщениях об ошибках, манифестах блобов или выводе MCP/CLI. Вычистка обеспечивается централизованно на уровне Local API.

6. Открытость Local API

  • По умолчанию только loopback; токен на сессию. Никакой привязки к удалённому интерфейсу без явного задокументированного согласия (и никогда по умолчанию).

7. Цепочка поставок

  • Зависимости фиксируются и проверяются; свои артефакты (приложение, CLI, ядра) подписываются. Ядра проверяются перед запуском.
  • Node-sidecar для модулей: сторонний npm работает с минимальными правами; никакого неявного доступа к хранилищу, файловой системе, связке ключей или токену API — RPC по возможностям и лестница изоляции, самый строгий уровень для недоверенного хаба (ADR-0022).

Открытые вопросы

Механизм выявления подделки журнала аудита, отгружается ли browser.eval в v1 → OPEN_QUESTIONS.md.