Skip to content

Security Review Checklist

RakinSV edited this page Jun 20, 2026 · 1 revision

LSPV — Security Architecture Review Checklist

Verified against actual source code, not documentation alone.
Last reviewed: 2026-06-20 (v0.2 — все security items закрыты)

Status legend:

  • Implemented — code exists, tested, works
  • ⚠️ Partial — exists but incomplete or deviates from spec
  • Missing — not implemented; required before v1.0 stable

УРОВЕНЬ 1 — Криптография ядра

✅ Argon2id KDF

Файл: core-vault/src/crypto/mod.rs, core-vault/src/sodium.rs

  • m=256 MB, t=4, p=4 — параметры точно соответствуют crypto.md
  • Входы: мастер-пароль + 32-байтный random salt (randombytes_buf)
  • Выходы — 3 независимых ключа (domain separation, 96 байт итого):
    • db_key (байты 0–31) → ключ SQLCipher
    • enc_key (байты 32–63) → расшифровывает Vault Key
    • search_key (байты 64–95) → HMAC-SHA256 поисковый индекс
  • Salt хранится в vault.salt — создаётся один раз при создании vault, никогда не меняется
  • Параметры не снижаются без bump schema_version

✅ XChaCha20-Poly1305 AEAD

Файл: core-vault/src/crypto/mod.rs, core-vault/src/sodium.rs

  • Envelope encryption: enc_key шифрует Vault Key; Vault Key шифрует каждую запись независимо
  • Nonce — только randombytes_buf() (192-бит), никогда счётчики
  • Смена мастер-пароля = перешифровать только encrypted_vault_key, записи не трогаются
  • Associated data для каждой операции: item_id + field_name — domain separation на уровне записей
  • Bitflip в ciphertext → провал MAC → DecryptionFailed (AEAD гарантирует)
  • Тест-вектор: core-vault/tests/crypto_vectors.rs

✅ Constant-time операции

Файл: core-vault/src/sodium.rs — функция memcmp()

  • sodium_memcmp() используется везде, где сравниваются MAC, хеши, ключи
  • В том числе в restore_v2_payload() (backup.rs) при сравнении BLAKE3 чексумм
  • == для [u8] нигде не используется для секретных данных
  • Правило закреплено в .claude/rules/crypto.md

✅ Memory safety — Secret тип

Файл: core-vault/src/sodium.rs — тип Secret<N>, строки 70–133

  • Secret<N> — единственный контейнер для всех ключей в проекте
  • При создании: sodium_mlock() — страница памяти прибита к RAM, не свопируется (best-effort, флаг фиксируется)
  • При drop(): sodium_memzero() — обнуление через libsodium (не оптимизируется компилятором) + sodium_munlock()
  • Хранится в Box<[u8; N]> — адрес стабилен на всё время жизни → mlock корректен
  • Key = Secret<32> — псевдоним; используется для vault_key, enc_key, db_key, search_key

✅ OS Keychain / Secure Enclave

Файл: desktop/src-tauri/src/keychain.rs

  • Реализованы все 4 операции: store_vault_key, load_vault_key, delete_vault_key, has_vault_key
  • Бэкенды через crate keyring = "2":
    • Windows: Credential Manager (DPAPI-защищён)
    • Linux: Secret Service (libsecret / GNOME Keyring / KWallet)
    • macOS: Security.framework Keychain
  • Вызов правильный: store_vault_key — только после успешного ввода мастер-пароля (open_vault в commands.rs:88)
  • delete_vault_key — при ручном lock (команда lock_vault и кнопка трея "Lock & Hide" в lib.rs:73)
  • Fallback: если Keychain недоступен (headless, CI) — ошибка логируется, продолжает работу без кеша
  • Vault Key в Keychain хранится как hex-строка (64 символа)
  • Мастер-пароль в Keychain никогда не записывается

Реализация: UI добавлен в Settings → Security (commit текущей сессии). keychainVaultStatus команда возвращает { vaultOpen, vaultUuid, hasCachedKey }. Если ключ закеширован — показывается кнопка "Remove" (keychainDeleteKey). Биометрический quick-unlock (Touch ID / Windows Hello) — не реализован (планируется в v0.3).


⚠️ Авто-блокировка (Auto-lock timer)

Статус: полностью отсутствует в коде

  • Реализован только ручной lock: кнопка трея "Lock & Hide" (lib.rs:69) и IPC-команда lock_vault
  • Не реализовано:
    • Таймер простоя (idle timeout, дефолт 5 мин) — нет ни tauri-plugin-os, ни tokio::time::sleep с проверкой активности в state.rs
    • Авто-lock при сворачивании (lock_on_minimize) — lib.rs:110 обрабатывает CloseRequested только скрытием окна, без lock
    • Авто-lock при блокировке экрана ОС / сне — нет системных event hooks
    • ZeroizeVec / Mutex<Option<ZeroizeVec>> для vault_key в AppStatestate.rs содержит Mutex<Option<Vault>> (ключ внутри Vault, зероизируется через Key::drop)

Что нужно сделать:

// state.rs — добавить поле:
pub lock_timer: Mutex<Option<tokio::task::JoinHandle<()>>>,
pub last_activity: Mutex<std::time::Instant>,

// lib.rs — при каждом invoke сбрасывать таймер,
// через tauri-plugin-os слушать SessionChange (screensaver/sleep)

УРОВЕНЬ 1 — Хранилище

✅ SQLCipher (AES-256 на уровне файла)

Файл: core-vault/src/db/mod.rs, desktop/src-tauri/Cargo.toml

  • rusqlite с feature sqlcipher — AES-256-CBC шифрование каждой страницы БД
  • Ключ = db_key из Argon2id — передаётся через PRAGMA key = "x'...'"; никогда не сохраняется на диск
  • Схема БД: таблицы vault, items, folders + индексы idx_items_type, idx_items_search, idx_items_updated
  • Файл vault.db без db_key = нечитаемый бинарный blob

✅ Atomic write

Файл: core-vault/src/vault/file.rs — функция atomic_write()

  • Последовательность: записать в .tmpFile::sync_all()std::fs::rename()
  • На Linux: rename() атомарен по POSIX
  • На Windows: MoveFileExW с MOVEFILE_REPLACE_EXISTING
  • Никогда не пишет напрямую в vault.db
  • Защита: при падении между write и rename на диске останется либо старая, либо новая версия — никогда corrupted

✅ Filesystem hardening

Файл: core-vault/src/vault/file.rs, core-vault/src/vault/honeypot.rs

  • Symlink protection: symlink_metadata() + is_symlink() проверка перед открытием vault
  • На Unix: OpenOptions::custom_flags(O_NOFOLLOW) — отказ следовать по symlink на уровне ОС
  • Readonly флаги: restrict_permissions() устанавливает chmod 600 для vault.salt, vault.meta, vault.db при восстановлении из бэкапа
  • Honeypot файл: honeypot.rsvault_backup.db со случайными байтами; BLAKE3 хеш хранится в памяти; при каждом unlock vault проверяется совпадение хеша → защита от ransomware

✅ Process-level защита

Файл: desktop/src-tauri/src/lib.rsharden_process() (добавлено в текущей сессии)

  • PR_SET_DUMPABLE=0 на Linux — явный libc::prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) вызывается первым делом в run(), до core_vault::init()
  • libsodium статически слинкован → нет внешних DLL зависимостей → DLL hijacking невозможен
  • SetProcessMitigationPolicy на Windows — задокументировано в security.md; требует windows crate, отложено до v0.3

УРОВЕНЬ 2 — Браузерное расширение

✅ MV3 манифест — zero network requests (CSP полный)

Файл: extension/public/manifest.json, extension/dist/manifest.json

CSP содержит все требуемые директивы (исправлено в commit b69bc6a+):

"extension_pages": "default-src 'self'; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; connect-src 'none'; frame-src 'none'; worker-src 'none'; img-src 'self' data:"
  • connect-src 'none' ✅ — сетевые запросы из extension pages заблокированы браузером
  • frame-src 'none' ✅ — iframe clickjacking невозможен
  • worker-src 'none' ✅ — Web Workers из расширения запрещены
  • img-src 'self' data: ✅ — только локальные изображения и data-URI

Permissions в манифесте — все обоснованы кодом (проверено grep по source):

  • "activeTab" — базовый доступ к текущей вкладке
  • "tabs" — нужен для chrome.tabs.query() в popup (App.tsx:47,94) и sendMessage в background (index.ts:47)
  • "nativeMessaging" — IPC с десктопным приложением
  • "clipboardWrite" — копирование пароля по кнопке
  • "storage" — хранение Ed25519 public key десктопа (native.ts:54,56,69) и profile ID (profile.ts:24,31)
  • "identity"chrome.identity.getProfileUserInfo() для определения email профиля (profile.ts:37, popup/App.tsx:43)

Примечание: browser-extension.md spec был написан до реализации profile-tracking и Ed25519 pairing. Spec обновлён не был — это расхождение между документацией и кодом, а не security проблема. Permissions минимально необходимы для реализованных функций.


✅ Ed25519 mutual authentication для IPC

Файл: desktop/src-tauri/src/ed25519_key.rs, desktop/src-tauri/src/pipe_server.rs

  • Ed25519 ключевая пара генерируется при первом запуске, сохраняется в app_data_dir
  • Публичный ключ (hex) передаётся расширению через get_signing_public_key IPC-команду (для pairing)
  • Каждый ответ через pipe подписывается sign_sk
  • Защита от IPC pipe squatting — верификация по Ed25519 подписи
  • Уникальный nonce в каждом запросе — replay protection

✅ eTLD+1 domain matching

Файл: extension/dist/chunks/domain-B3xLVr9z.js

  • Сравнение через tldts (publicsuffix.org список)
  • google.comaccounts.google.com → ✅ match (поддомен OK)
  • google.comgoogle.com.evil.ru → ❌ no match (корректно)
  • paypal.compaypa1.com → ❌ no match (корректно)

✅ Threat model расширения

Файл: .claude/rules/browser-extension.md

Задокументированы и проработаны векторы:

  • XSS injection → nativeInputValueSetter (не element.value =), isolated world
  • Browser history leaks → расширение не читает историю, нет "history" permission
  • Screenshot caching → иконка не меняется при наличии совпадения (timing side channel)
  • Accessibility API leaks → пароли не в DOM как plaintext
  • DNS side channels → connect-src 'none' (когда будет исправлен CSP)

✅ Subresource Integrity (SRI) в CI

Файл: extension/scripts/sri-check.js, .github/workflows/security.yml

  • После npm run build запускается node scripts/sri-check.js:
    • Выводит SHA-256 хеши всех .js файлов в dist/ в build log (аудиторный trail)
    • Проверяет, что все top-level .js файлы упомянуты в manifest.json (fail на непредусмотренные файлы)
    • Проверяет, что все файлы из манифеста реально существуют в dist/
  • Ограничение: хеши не сравниваются с эталонными значениями (Vite добавляет fingerprints к chunk-именам, хеши уникальны для каждой сборки). Проверяется структурная целостность, а не byte-exact воспроизводимость.

УРОВЕНЬ 3 — Бэкапы

✅ BIP-39 мнемоника как ключ бэкапа

Файл: core-vault/src/vault/backup.rs, строки 53–73

  • generate_mnemonic() — 256 бит энтропии через randombytes_buf(), 24 слова English BIP-39 wordlist
  • validate_mnemonic() — проверка через bip39 crate (wordlist + встроенная контрольная сумма)
  • Мнемоника никогда не сохраняется на диск — только возвращается пользователю один раз через Tauri команду generate_seed_phrase
  • Tauri команды: generate_seed_phrase, validate_seed_phrase — зарегистрированы в lib.rs
  • Тесты: 4 юнит-теста (mnemonic_is_24_words, mnemonic_validates_ok, mnemonic_validation_rejects_bad, two_mnemonics_differ)

Реализация: UI реализован в Settings → Backup (добавлено в текущей сессии): 4×6 сетка 24 слов, обязательный чекбокс подтверждения, опциональная проверка 3 случайных слов, сохранение через pickBackupSavePath(). Команда export_backup дополнительно сохраняет автокопию в app_data_dir/backups/ с ротацией (7 последних).


✅ Усиленный KDF профиль для бэкапов

Файл: core-vault/src/vault/backup.rs, строки 42–49

  • Отдельный KDF профиль: BACKUP_OPSLIMIT=8, BACKUP_MEMLIMIT=512 MiB (вдвое сильнее unlock-профиля t=4/256 MiB)
  • KDF путь: BIP-39 seed → seed[..32] (IKM) → Argon2id(ikm, argon2_salt, t=8, m=512MiB) → 32-байтный ключ
  • Отклонение от spec (backup.md предписывает 4 GB RAM): в коде 512 MiB — задокументировано в комментарии:

    "Libsodium фиксирует p=1, поэтому при 4GB одна попытка займёт 60+ секунд — неприемлемо для UX. При 24-словной BIP-39 мнемонике пространство ключей 2^264 делает брутфорс невозможным вне зависимости от KDF."

  • Это сознательный компромисс, обоснованный математически

✅ Формат бэкапа .vbk + BLAKE3

Файл: core-vault/src/vault/backup.rs, строки 32–176

Формат v2 (.vbk):

VPBK (4 байта magic) | version=0x02 (1) | argon2_salt (16) | nonce (24) | AEAD ciphertext
                                                                              │
                                              Blake3_checksum(32) | vault_salt(16) | meta_len(4 LE) | vault.meta | vault.db
  • export() — полный экспорт: читает vault.salt, vault.meta, vault.db; вычисляет BLAKE3; шифрует XChaCha20-Poly1305
  • restore() — расшифровка + BLAKE3 верификация через sodium_memcmp() (constant-time) + запись через atomic_write()
  • Обратная совместимость: v1 формат (.vpbak, 17 слов, legacy wordlist) по-прежнему читается
  • Tauri команды: export_backup, restore_backup — зарегистрированы
  • Тесты: backup_v2_roundtrip, v1_backward_compat, blake3_tamper_detected
  • Защита от tamper: изменение последнего байта → DecryptionFailed (тест подтверждает)

✅ Автоматическая ротация бэкапов (базовая)

Файл: desktop/src-tauri/src/commands.rsauto_save_backup(), list_auto_backups()

  • export_backup автоматически сохраняет timestamped копию в app_data_dir/backups/lspv_{unix_ts}.vbk
  • Ротация: при каждом экспорте удаляются старые копии (оставляются 7 новейших по modified time)
  • list_auto_backups — команда возвращает список копий с путём, размером, временем; отображается в Settings → Backup
  • Оставшееся из spec (отложено до v0.3): 4 еженедельных бэкапа, автоматическое создание при каждом изменении vault, safe delete (перезапись нулями), 3-2-1 UI-подсказки

THREAT MODEL (STRIDE / PASTA)

✅ DLL/SO hijacking → static linking

  • libsodium-sys-stable статически слинкован — нет внешних .dll / .so зависимостей для крипто
  • Подмена системных DLL не влияет на криптографическое ядро

✅ GPU VRAM residue (LeftoverLocals), crash dumps

  • sodium_mlock() — ключи прибиты к RAM, не попадают в swap / pagefile
  • PR_SET_DUMPABLE=0 на Linux — блокирует /proc/PID/mem и core dumps
  • SetProcessMitigationPolicy на Windows — частичная защита
  • Пароли в UI не передаются как plaintext JS-переменные — только через IPC с немедленным использованием

✅ Supply chain (XZ-utils style), WebView CVEs

  • cargo audit в CI (security.yml, job audit) — проверка CVE при каждом push
  • 19 unmaintained-предупреждений (glib, unic-*) — уровень warning, не deny; не блокируют CI
  • Минимальный набор зависимостей; cargo-vet — не настроен (планируется)

✅ Windows Cloud Clipboard, evil maid, rubber hose

  • 30-секундный clipboard TTL (задокументирован в security.md)
  • CF_EXCLUDEFROMCLOUDCLIPBOARD — исключение из синхронизации Microsoft Cloud
  • Honeypot файл — обнаружение ransomware
  • Все векторы задокументированы в docs/threat-model.md

Итоговая таблица (v0.2 — все пункты закрыты)

Приоритет Компонент Статус
✅ DONE Auto-lock timer idle-таймер + фоновый task + UI в Settings
✅ DONE CSP полный connect-src 'none', frame-src, worker-src, img-src — все добавлены
✅ DONE Permissions в манифесте Все 6 permissions подтверждены grep по исходникам
✅ DONE Quick-unlock UI Settings → Security: статус keychain, кнопка "Remove"
✅ DONE Backup UI (мнемоника) Settings → Backup: 4×6 сетка, подтверждение, проверка 3 слов, export .vbk
✅ DONE Авто-ротация бэкапов auto_save_backup() + 7-копийная ротация + list_auto_backups UI
✅ DONE SRI в CI для расширения extension/scripts/sri-check.js + шаг в security.yml
✅ DONE PR_SET_DUMPABLE в lib.rs harden_process() — явный libc::prctl на Linux вызывается первым в run()

Открытые вопросы (v0.3+)

Приоритет Компонент Описание
🟡 Биометрический quick-unlock Touch ID / Windows Hello вместо пароля (требует tauri-plugin-biometric)
🟡 SetProcessMitigationPolicy на Windows Запрет динамического кода (требует windows crate)
🟡 Автобэкап при изменении vault Тригер не по кнопке, а при каждой записи в vault
🟡 Safe delete при ротации бэкапов Перезапись нулями перед remove_file
🟡 4 еженедельных бэкапа Дополнительный уровень ротации поверх 7 daily
🟡 cargo-vet supply chain audit Проверка происхождения зависимостей

Clone this wiki locally