feat(backup): add user-passphrase encryption - #170
Closed
squarepots wants to merge 35 commits into
Closed
squarepots wants to merge 35 commits into
squarepots wants to merge 35 commits into
Conversation
squarepots
marked this pull request as ready for review
September 18, 2026 16:09
squarepots
force-pushed
the
feat/passphrase-backup-v2
branch
from
September 18, 2026 18:55
f78a7b7 to
aafdac3
Compare
squarepots
force-pushed
the
feat/passphrase-backup-v2
branch
from
September 19, 2026 08:37
dde6c23 to
30a6d18
Compare
This was referenced Sep 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
依赖 #169;本 PR 的完整导入事务复用该 PR 提供的 account-store mutation boundary。
本 PR 为 full backup 增加用户控制的 passphrase 加密,并让完整导入遵循共享的 account-store mutation boundary。
新导出始终使用 v2:每个文件生成独立的 16-byte random salt 和 24-byte random nonce,用 PBKDF2-HMAC-SHA256(210,000 次迭代)从用户 passphrase 派生 32-byte key,再使用 XChaCha20-Poly1305 做 authenticated encryption。v1 文件继续使用内置兼容路径只读导入,不能再导出。
passphrase 只存在于当前 command 和短暂的 React 遮罩输入状态中,不写入持久状态或日志。只拒绝空或全空白输入;其他 passphrase(包括首尾空格)按原样传给加密和解密。导入先完成解码和校验,再通过现有
mutate_accounts在同一事务中读取最新 account store、合并并写回;文件导入和浏览器 bytes 导入使用同一条路径。完整导入只增加缺失 profile,保留最新 store 的有效active_account_id,不会把备份中的历史 active id 变成新的运行时激活;无效的当前 projection 会被清除。并发回归测试确认导入不会覆盖事务开始前已提交的其他最新状态。验证
pnpm run test:backup-passphrase:3 个测试通过pnpm run test:web-boundary:1 个测试通过pnpm build:通过rustfmt --edition 2021 --check src/auth/storage.rs src/commands/account.rs:通过cargo check --locked:通过cargo test --locked:61 个 Rust 测试通过git diff --check:通过Closes #160
Depends on #169; full-import transactions in this PR reuse the account-store mutation boundary introduced there.
This PR adds user-controlled passphrase encryption for full backups and makes full imports use the shared account-store mutation boundary.
New exports always use v2: each file gets an independent 16-byte random salt and 24-byte random nonce, PBKDF2-HMAC-SHA256 derives a 32-byte key from the user passphrase with 210,000 iterations, and XChaCha20-Poly1305 provides authenticated encryption. Existing v1 files remain read-only import-compatible and are no longer exportable.
The passphrase exists only in the current command and transient masked React input state; it is never written to durable state or logs. Only empty or all-whitespace input is rejected. Every other passphrase, including leading or trailing whitespace, is passed unchanged to encryption and decryption. Import completes decoding and validation before using the existing
mutate_accountstransaction to read the latest account store, merge, and write it back. File and browser byte imports use the same path. Full import adds only missing profiles, preserves a validactive_account_idfrom the latest store, never turns the backup’s historical active id into a new runtime activation, and clears an invalid current projection. A concurrent regression test confirms that an unrelated update committed before the import transaction is preserved.Validation
pnpm run test:backup-passphrase: 3 tests passedpnpm run test:web-boundary: 1 test passedpnpm build: passedrustfmt --edition 2021 --check src/auth/storage.rs src/commands/account.rs: passedcargo check --locked: passedcargo test --locked: 61 Rust tests passedgit diff --check: passedCloses #160