Skip to content

feat: add toggle to control key inclusion in backup/export#209

Merged
AmintaCCCP merged 3 commits into
mainfrom
feat/backup-include-keys-toggle
Jun 7, 2026
Merged

feat: add toggle to control key inclusion in backup/export#209
AmintaCCCP merged 3 commits into
mainfrom
feat/backup-include-keys-toggle

Conversation

@AmintaCCCP

@AmintaCCCP AmintaCCCP commented Jun 7, 2026

Copy link
Copy Markdown
Owner

概述

添加备份/导出时是否包含敏感密钥的可选开关,用户可以选择导出完整配置(包含密钥)或安全配置(密钥屏蔽)。

功能特性

🔒 安全优先设计

  • 默认不包含密钥:开关默认关闭(),避免用户意外泄露密钥
  • 智能屏蔽:关闭时用 *** 屏蔽真实密钥
  • 智能恢复:导入屏蔽数据时保留现有密钥,不会丢失

🎯 覆盖范围

开关控制以下所有密钥的备份/导出行为:

  1. AI 服务配置 - apiKey
  2. WebDAV 配置 - password
  3. 网络代理 - password
  4. 远程下载 (RPC) - secret
  5. 后端服务器 - backendApiSecret

📍 使用场景

  • 设置 - 备份与恢复:手动备份/恢复到 WebDAV
  • 设置 - 数据管理 - 数据导出与导入:导出/导入本地 JSON 文件

⚠️ 用户体验

  • 开关在两个面板中同步显示
  • 导入预览时检测屏蔽密钥并显示警告
  • 导入后如有屏蔽密钥会提示用户重新输入

技术实现

新增组件

  • IncludeKeysToggle.tsx - 可复用的开关组件

修改文件

  • useAppStore.ts - 新增 includeKeysInBackup 状态(v6→v7 迁移)
  • BackupPanel.tsx - 集成开关,处理备份/恢复密钥逻辑
  • DataManagementPanel.tsx - 集成开关,处理导出/导入密钥逻辑,添加警告提示
  • types/index.ts - 新增类型定义

数据结构变更

导出/备份数据新增字段:

  • includeKeysInBackup:标记该备份是否包含密钥
  • proxyConfig:网络代理配置
  • rpcDownloadConfig:远程下载配置
  • backendApiSecret:后端 API 密钥

审计问题修复

🔴 关键安全问题

  • ✅ 修复默认值为 false(安全优先)
  • ✅ 修复类型安全问题(undefined → 空字符串)

🟡 改进项

  • ✅ 添加导入预览警告提示
  • ✅ 导入后提示用户重新输入屏蔽的密钥
  • ✅ 智能回退逻辑防止密钥丢失

测试建议

测试场景

  1. 默认行为:新用户备份时密钥应被屏蔽
  2. 开关开启:备份包含真实密钥
  3. 开关关闭:备份屏蔽密钥为 ***
  4. 导入屏蔽数据:应保留现有密钥
  5. 导入完整数据:应恢复真实密钥
  6. 警告提示:导入屏蔽数据时应显示警告

验证点

  • ✅ 构建成功
  • ✅ 无 TypeScript 错误
  • ✅ 状态持久化正常
  • ✅ 两个面板开关状态同步

Breaking Change

⚠️ 注意:升级后 includeKeysInBackup 默认为 false,现有用户需要手动开启才能导出完整密钥。

截图

(建议添加开关界面和警告提示的截图)

Closes #207

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an optional toggle to include/exclude sensitive keys (API keys, passwords, secrets) in backups and exports.
    • Export now masks sensitive fields when keys are excluded and includes a flag indicating whether keys were included.
    • Import/restore respects the backup's key-inclusion flag; masked secrets are not restored and existing secrets are preserved.
    • Export UI shows the new toggle; import preview warns when secrets are masked. Preference is persisted across sessions.

- Add includeKeysInBackup state (default: false, security first)
- Create IncludeKeysToggle shared component
- Update BackupPanel with conditional key masking/restoration
- Update DataManagementPanel with conditional key masking/restoration
- Add warning for masked secrets in import preview
- Cover all secret types: AI API keys, WebDAV passwords, proxy passwords, RPC secrets, backend API secret
- Smart fallback: masked secrets preserve existing values on import
- Version migration: v6 -> v7

Closes #207

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c983a59-dd22-4221-a259-d28ea48e77ee

📥 Commits

Reviewing files that changed from the base of the PR and between a0fed55 and a7e958f.

📒 Files selected for processing (1)
  • src/components/settings/DataManagementPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/settings/DataManagementPanel.tsx

📝 Walkthrough

Walkthrough

Adds a persisted includeKeysInBackup preference and toggle; export now records the flag and masks secret fields as '***' when disabled; import/restore applies secret values only when backups indicate keys were included and values are unmasked, preserving on-device secrets otherwise.

Changes

Backup and export secret masking

Layer / File(s) Summary
Store state and persistence
src/types/index.ts, src/store/useAppStore.ts
AppState gains includeKeysInBackup: boolean. Store adds setIncludeKeysInBackup, persists the field, initializes default false, updates partialize, bumps persist version, and adds a v6→v7 migration.
Include keys toggle UI
src/components/settings/IncludeKeysToggle.tsx
New component renders an accessible switch bound to includeKeysInBackup and updates the store via setIncludeKeysInBackup.
Backup panel secret masking and restore
src/components/settings/BackupPanel.tsx
Backup serialization includes includeKeysInBackup and conditionally masks apiKey, WebDAV password, proxy password, RPC secret, and backendApiSecret as '***' when inclusion is disabled. Restore logic overwrites secrets only when backup flagged inclusion and values are unmasked; otherwise preserves existing in-app secrets.
Data export and import secret masking
src/components/settings/DataManagementPanel.tsx
Export schema extended with proxyConfig, rpcDownloadConfig, backendApiSecret, and includeKeysInBackup. Export masks or includes secrets per the toggle. Import (replace/merge) applies secrets only when backup indicated inclusion and values are unmasked; legacy backups default to included. Import preview and success messaging reflect masked secrets.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I flip the toggle with a twitchy nose,
Masks and keys where the backup goes.
Secrets kept sheltered if you choose so,
Local keys stay safe, no forced overthrow.
Hoppy restores, and peace in tow.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add toggle to control key inclusion in backup/export' accurately summarizes the main change—a new toggle component for controlling whether sensitive keys are included in backups/exports.
Linked Issues check ✅ Passed The PR fully addresses issue #207's requirements: prevents restore overwrites via opt-in toggle, excludes secrets when disabled, preserves local values on import, and includes warnings for masked secrets.
Out of Scope Changes check ✅ Passed All changes are in-scope: new IncludeKeysToggle component, app store state additions, backup/restore logic updates, and integration into panels—all directly supporting the toggle feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/backup-include-keys-toggle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/settings/BackupPanel.tsx (1)

190-299: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore should trust the backup metadata, not the current UI toggle.

All secret restore branches here are gated by the local includeKeysInBackup state. On a fresh machine that value defaults to false, so a backup that actually contains real secrets will restore with preserved/blank local values instead of the backed-up credentials. This also breaks legacy backups that predate the flag. Read the decision from backupData.includeKeysInBackup once and reuse it for the AI/WebDAV/proxy/RPC/backend branches, with a legacy fallback of true.

Suggested fix
         const backupData = JSON.parse(backupContent);
+        const backupIncludedKeys = backupData.includeKeysInBackup ?? true;
...
-                  apiKey: includeKeysInBackup && cfg.apiKey && cfg.apiKey !== '***' ? cfg.apiKey : existing.apiKey,
+                  apiKey: backupIncludedKeys && cfg.apiKey && cfg.apiKey !== '***' ? cfg.apiKey : existing.apiKey,
...
-                  password: includeKeysInBackup && cfg.password && cfg.password !== '***' ? cfg.password : existing.password,
+                  password: backupIncludedKeys && cfg.password && cfg.password !== '***' ? cfg.password : existing.password,
...
-              password: includeKeysInBackup && backupProxyConfig.password && backupProxyConfig.password !== '***'
+              password: backupIncludedKeys && backupProxyConfig.password && backupProxyConfig.password !== '***'
                 ? backupProxyConfig.password
                 : latestProxyConfig.password,
...
-              secret: includeKeysInBackup && backupRpcDownloadConfig.secret && backupRpcDownloadConfig.secret !== '***'
+              secret: backupIncludedKeys && backupRpcDownloadConfig.secret && backupRpcDownloadConfig.secret !== '***'
                 ? backupRpcDownloadConfig.secret
                 : latestRpcDownloadConfig.secret,
...
-          if (includeKeysInBackup && backupData.backendApiSecret !== undefined && backupData.backendApiSecret !== '***') {
+          if (backupIncludedKeys && backupData.backendApiSecret !== undefined && backupData.backendApiSecret !== '***') {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/settings/BackupPanel.tsx` around lines 190 - 299, The restore
logic incorrectly relies on the local includeKeysInBackup UI state instead of
the backup's metadata; change the code to read a single boolean from
backupData.includeKeysInBackup (use true as the legacy fallback when the field
is missing) once at the start of the restore and then use that local variable in
all secret decisions in restoreAIConfigs (the AI config block),
restoreWebDAVConfigs (WebDAV block), proxy restore (setProxyConfig block),
rpcDownload restore (setRpcDownloadConfig block), and backendApiSecret
assignment, preserving the existing '***' masking checks when deciding whether
to apply the backed-up secret.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/settings/DataManagementPanel.tsx`:
- Around line 713-715: Replace the direct state mutation that writes
backendApiSecret via useAppStore.setState with the proper store action
setBackendApiSecret so the value is persisted to sessionStorage and survives
reloads; specifically, inside the block that checks wasIncluded,
importedData.backendApiSecret !== undefined and
isRealSecret(importedData.backendApiSecret), call
setBackendApiSecret(importedData.backendApiSecret) (instead of
useAppStore.setState) to mirror the existing persistence behavior.
- Line 592: The import logic in DataManagementPanel uses const wasIncluded =
importedData.includeKeysInBackup ?? false which treats missing
includeKeysInBackup as false and strips legacy secrets; update the fallback to
treat undefined as a legacy export by defaulting to true (change the fallback
for importedData.includeKeysInBackup to true) so older exports retain keys on
import; update the declaration of wasIncluded and add a short comment noting the
backward-compatibility behavior.
- Around line 528-539: The export currently always writes proxyConfig,
rpcDownloadConfig and backendApiSecret into exportDataObj.data regardless of the
user's selected export checklist; change the logic so those fields are only
serialized when the user explicitly selects the appropriate category/flag (e.g.,
a new "secrets"/"sensitive configs" checkbox or an existing "settings/configs"
category) — update the code around exportDataObj.data.proxyConfig,
rpcDownloadConfig and backendApiSecret to guard on that selected category (not
just includeKeys), and ensure MASKED_SECRET behavior remains when the
secret-category is selected but includeKeys is false; also update the export
preview/export checklist code paths so these fields appear only when that
category is checked.

---

Outside diff comments:
In `@src/components/settings/BackupPanel.tsx`:
- Around line 190-299: The restore logic incorrectly relies on the local
includeKeysInBackup UI state instead of the backup's metadata; change the code
to read a single boolean from backupData.includeKeysInBackup (use true as the
legacy fallback when the field is missing) once at the start of the restore and
then use that local variable in all secret decisions in restoreAIConfigs (the AI
config block), restoreWebDAVConfigs (WebDAV block), proxy restore
(setProxyConfig block), rpcDownload restore (setRpcDownloadConfig block), and
backendApiSecret assignment, preserving the existing '***' masking checks when
deciding whether to apply the backed-up secret.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 552402ee-ae01-4920-8c71-2dd5d3e15818

📥 Commits

Reviewing files that changed from the base of the PR and between c061690 and e4ae81b.

📒 Files selected for processing (5)
  • src/components/settings/BackupPanel.tsx
  • src/components/settings/DataManagementPanel.tsx
  • src/components/settings/IncludeKeysToggle.tsx
  • src/store/useAppStore.ts
  • src/types/index.ts

Comment thread src/components/settings/DataManagementPanel.tsx Outdated
Comment thread src/components/settings/DataManagementPanel.tsx Outdated
Comment thread src/components/settings/DataManagementPanel.tsx
AmintaCCCP and others added 2 commits June 7, 2026 20:35
- Use backup metadata (includeKeysInBackup) instead of current UI state for restore
- Default legacy backups to true (treat missing flag as keys included)
- Use setBackendApiSecret action instead of direct setState
- Only export special configs (proxy/RPC/backend) when uiSettings is selected
- Add comment explaining legacy compatibility fallback

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Move toggle from inside Export panel to standalone container
- Place between section title and export/import panels
- Prevent toggle from being compressed in layout

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AmintaCCCP AmintaCCCP merged commit fb9d2d1 into main Jun 7, 2026
5 checks passed
@AmintaCCCP AmintaCCCP deleted the feat/backup-include-keys-toggle branch June 7, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 从 webdav 恢复秘钥的时候,把新机器上面的已有的秘钥覆盖了

1 participant