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