Local Vault: формат файла, recovery и резервное копирование
Translation note: the canonical version is the English
local-vault.md(per the OSS-docs-in-English rule). This Russian copy is kept for the team; if it diverges, the English version is authoritative.
Local vault — собственное локальное хранилище зашифрованных секретов из devboy-tools. Один файл, три способа разблокировки, recovery-фраза для аварий. Документ объясняет формат, когда выбирать local-vault вместо OS keychain или внешних источников, и как правильно бэкапить.
См. ADR-023 §3.1–§3.3 и ADR-021 §4 для полной спецификации.
Когда использовать local-vault
Главный критерий: local-vault — это для случаев, когда нет подходящего системного хранилища. Если OS keychain или 1Password доступны — берите их. Local-vault даёт переносимость и контроль над файлом ценой того, что бэкап и rotate ключей становятся вашей ответственностью.
Формат файла
Файл живёт по умолчанию в ~/.devboy/secrets/local-vault.dvb. Бинарный layout (см. crates/devboy-vault-crypto/src/format.rs):
Магия DVB1 фиксирована — её достаточно, чтобы file local-vault.dvb отличил vault от случайных байтов.
Метаданные читаются без unlock
description, retrieval_url, expires_at, rotation_method, pattern_id лежат в открытом виде. Это умышленно — secrets list, doctor и discovery-флоу должны работать без PIN-prompt'а. Угрозовая модель: локальный процесс с правами на чтение файла уже видит то, что система отдаёт через ls -la. Шифровать метаданные — добавить prompt без реальной защиты.
Способы разблокировки (envelopes)
Один vault может одновременно поддерживать несколько способов разблокировки. Каждый способ — отдельная envelope, которая независимо хранит зашифрованную копию vault-key. Любая из них разблокирует те же blobs.
1. Passphrase envelope (по умолчанию)
Параметры Argon2id по умолчанию (KdfParams::DEFAULT): m=64 MiB, t=3, p=1, salt 32 B. На 2024-class hardware один разблок занимает ~250 ms. Менять параметры можно через devboy secrets vault rekey — старая salt сохраняется, чтобы все остальные envelope продолжали работать.
2. Keychain envelope (опционально)
OS keychain хранит fresh случайный 32-byte ключ; envelope wrapped_key — это XChaCha20-Poly1305(keychain_key).encrypt(vault-key). Удобно: vault разблокируется одним fingerprint/PIN-prompt'ом из системы, без ввода passphrase.
Доступно только на macOS / Windows / Linux с gnome-keyring; другие платформы — passphrase + recovery phrase.
3. Recovery phrase envelope (BIP39)
12-слов BIP39, генерируется при создании vault. Envelope использует HKDF(BIP39_seed) без Argon2id (фраза сама по себе высокоэнтропийная). Recovery phrase — запасной ключ от vault на случай, когда:
- Passphrase забыта.
- Keychain-запись стёрта (новая ОС, переустановка).
- Файл vault мигрирует на машину без keychain.
Recovery phrase никогда не хранится автоматически. CLI печатает её один раз при создании vault, и пользователь сам решает, куда её положить (бумажный список, password manager, sealed envelope).
⚠ Без recovery phrase potentially permanent loss: если passphrase забыта и keychain недоступен и recovery phrase не сохранена — vault не разблокировать. Дубликата ключа в системе нет, brute-force Argon2id с дефолтными параметрами займёт годы. Бэкапьте recovery phrase отдельно от vault-файла.
Recovery: что делать, когда всё пошло не так
Сценарий A — забыта passphrase
-
Если есть keychain envelope → разблок через keychain:
rekeyпересоздаёт passphrase envelope с новой фразой; keychain envelope остаётся в силе. -
Если keychain тоже недоступен → recovery phrase:
-
Если ни keychain, ни recovery phrase → восстанавливаете из бэкапа vault-файла, сделанного до потери ключа.
Сценарий B — повреждён vault-файл
Магия не сходится / TOML не парсится / blob не декодируется:
- Не паникуйте — не пишите ничего поверх повреждённого файла.
- Скопируйте
~/.devboy/secrets/local-vault.dvbв безопасное место (local-vault.dvb.broken). - Восстановите файл из последнего бэкапа.
- Прогоните
devboy secrets validate. Если зелёный — продолжайте работу. - Проанализируйте, что повредило файл: full disk,
kill -9во время записи, ручное редактирование. Атомарная запись (tmpfile + rename) исключает повреждение от прерывания, но не от внешнего вмешательства.
Сценарий C — мигрируете vault на новую машину
Поскольку vault — это один файл, миграция тривиальна:
После разблокировки на новой машине добавьте новую keychain envelope:
Резервное копирование
Vault — один файл, поэтому бэкап = регулярная копия + хранение recovery phrase отдельно.
Минимальная политика (один разработчик)
- Раз в сутки cron /
systemd --userкопирует~/.devboy/secrets/local-vault.dvbв зашифрованное хранилище (Time Machine, Backblaze, Restic-репозиторий с собственным паролем). - Recovery phrase — на бумаге в физически безопасном месте И в личном password manager (например, 1Password Personal). Никогда вместе с vault-файлом.
- Раз в квартал — тестовое восстановление: разворачиваете бэкап на чистую машину/контейнер и пытаетесь разблокировать через recovery phrase.
Что не делать
- ❌ Положить vault и recovery phrase в один и тот же бэкап-каталог. Если бэкап утечёт — утечёт всё.
- ❌ Хранить recovery phrase в plain-text файле в репозитории / на git-сервере. BIP39 — высокоэнтропийная, но публичная утечка превращает её в тривиальный unlock-key.
- ❌ Отправлять vault-файл через email / мессенджеры. AEAD защищает значения, но KDF salt в header утекает, что облегчает offline-перебор passphrase. Защищённые каналы (
scp,magic-wormhole, S3 with KMS) — обязательно. - ❌ Использовать слабую passphrase. Argon2id замедляет brute-force, но
password123всё равно перебирается. Минимум 4 случайных слова черезdiceware/xkpasswdили 16+ символов из password manager.
Хороший workflow
Security boundary
- AEAD выбор: XChaCha20-Poly1305. Длинный nonce (24 байта) → можно генерировать случайно без счётчика. По производительности эквивалент ChaCha20.
- AAD: каждый ciphertext привязан к kind envelope (
passphrase/keychain/recovery). Перенос blob между несовместимыми envelope'ами обнаруживается на этапе декрипта. - Zeroize:
vault-keyиunlock-keyобёрнуты вsecrecy::SecretBox, который вызываетzeroizeна drop. Передача через FFI к keychain backend'ам делается через minimal lifetime. - Lock posture: после
idle_timeout(по умолчанию 60 секунд без запросов) daemon zeroize'ит in-memory ключи. Разблок требуется заново. - Не защищает от:
- root/admin локального процесса с правами на чтение vault-файла + перехват envelope unlock-key из памяти daemon'а.
- Side-channel-атак на CPU (Spectre-class).
- Социальной инженерии (фишинг recovery phrase).
См. также
onboarding.md— первичная установка и настройка sources.agent-protocol.md— как AI-агенты взаимодействуют с vault через MCP.- ADR-023 §3.1 (формат) и §3.2–§3.3 (envelopes) — формальная спецификация.
crates/devboy-vault-crypto/— исходный код формата + AEAD + KDF.