-
-
Notifications
You must be signed in to change notification settings - Fork 1
Security 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
Файл: 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
Файл: 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
Файл: core-vault/src/sodium.rs — функция memcmp()
-
sodium_memcmp()используется везде, где сравниваются MAC, хеши, ключи - В том числе в
restore_v2_payload()(backup.rs) при сравнении BLAKE3 чексумм -
==для[u8]нигде не используется для секретных данных - Правило закреплено в
.claude/rules/crypto.md
Файл: 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
Файл: 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).
Статус: полностью отсутствует в коде
- Реализован только ручной 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 вAppState—state.rsсодержитMutex<Option<Vault>>(ключ внутри Vault, зероизируется черезKey::drop)
- Таймер простоя (idle timeout, дефолт 5 мин) — нет ни
Что нужно сделать:
// 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)Файл: core-vault/src/db/mod.rs, desktop/src-tauri/Cargo.toml
-
rusqliteс featuresqlcipher— 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
Файл: core-vault/src/vault/file.rs — функция atomic_write()
- Последовательность: записать в
.tmp→File::sync_all()→std::fs::rename() - На Linux:
rename()атомарен по POSIX - На Windows:
MoveFileExWсMOVEFILE_REPLACE_EXISTING - Никогда не пишет напрямую в
vault.db - Защита: при падении между write и rename на диске останется либо старая, либо новая версия — никогда corrupted
Файл: 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.rs—vault_backup.dbсо случайными байтами; BLAKE3 хеш хранится в памяти; при каждом unlock vault проверяется совпадение хеша → защита от ransomware
Файл: desktop/src-tauri/src/lib.rs — harden_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; требуетwindowscrate, отложено до v0.3
Файл: 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.mdspec был написан до реализации profile-tracking и Ed25519 pairing. Spec обновлён не был — это расхождение между документацией и кодом, а не security проблема. Permissions минимально необходимы для реализованных функций.
Файл: desktop/src-tauri/src/ed25519_key.rs, desktop/src-tauri/src/pipe_server.rs
- Ed25519 ключевая пара генерируется при первом запуске, сохраняется в
app_data_dir - Публичный ключ (hex) передаётся расширению через
get_signing_public_keyIPC-команду (для pairing) - Каждый ответ через pipe подписывается
sign_sk - Защита от IPC pipe squatting — верификация по Ed25519 подписи
- Уникальный nonce в каждом запросе — replay protection
Файл: extension/dist/chunks/domain-B3xLVr9z.js
- Сравнение через
tldts(publicsuffix.org список) -
google.com↔accounts.google.com→ ✅ match (поддомен OK) -
google.com↔google.com.evil.ru→ ❌ no match (корректно) -
paypal.com↔paypa1.com→ ❌ no match (корректно)
Файл: .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)
Файл: 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/
- Выводит SHA-256 хеши всех
- Ограничение: хеши не сравниваются с эталонными значениями (Vite добавляет fingerprints к chunk-именам, хеши уникальны для каждой сборки). Проверяется структурная целостность, а не byte-exact воспроизводимость.
Файл: core-vault/src/vault/backup.rs, строки 53–73
-
generate_mnemonic()— 256 бит энтропии черезrandombytes_buf(), 24 слова English BIP-39 wordlist -
validate_mnemonic()— проверка черезbip39crate (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 последних).
Файл: 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."
- Это сознательный компромисс, обоснованный математически
Файл: 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.rs — auto_save_backup(), list_auto_backups()
-
export_backupавтоматически сохраняет timestamped копию вapp_data_dir/backups/lspv_{unix_ts}.vbk - Ротация: при каждом экспорте удаляются старые копии (оставляются 7 новейших по
modifiedtime) -
list_auto_backups— команда возвращает список копий с путём, размером, временем; отображается в Settings → Backup - Оставшееся из spec (отложено до v0.3): 4 еженедельных бэкапа, автоматическое создание при каждом изменении vault, safe delete (перезапись нулями), 3-2-1 UI-подсказки
-
libsodium-sys-stableстатически слинкован — нет внешних.dll/.soзависимостей для крипто - Подмена системных DLL не влияет на криптографическое ядро
-
sodium_mlock()— ключи прибиты к RAM, не попадают в swap / pagefile -
PR_SET_DUMPABLE=0на Linux — блокирует/proc/PID/memи core dumps -
SetProcessMitigationPolicyна Windows — частичная защита - Пароли в UI не передаются как plaintext JS-переменные — только через IPC с немедленным использованием
-
cargo auditв CI (security.yml, jobaudit) — проверка CVE при каждом push - 19 unmaintained-предупреждений (glib, unic-*) — уровень
warning, неdeny; не блокируют CI - Минимальный набор зависимостей;
cargo-vet— не настроен (планируется)
- 30-секундный clipboard TTL (задокументирован в
security.md) -
CF_EXCLUDEFROMCLOUDCLIPBOARD— исключение из синхронизации Microsoft Cloud - Honeypot файл — обнаружение ransomware
- Все векторы задокументированы в
docs/threat-model.md
| Приоритет | Компонент | Статус |
|---|---|---|
| ✅ 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()
|
| Приоритет | Компонент | Описание |
|---|---|---|
| 🟡 | Биометрический 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 |
Проверка происхождения зависимостей |