feat: 完成T-503敏感信息提示与脱敏

- 保存或变更账号密码、AI API Key 前弹出本地明文保存提示

- 新增敏感值打码、结构化日志脱敏和自由文本替换工具

- 补充 GUI/appconfig 单测,覆盖明文提示和脱敏边界

- 同步任务看板、当前状态、架构、API、路由和编码规则文档
This commit is contained in:
chengma
2026-06-29 10:05:16 +08:00
parent 82d390c25a
commit 01e319cad8
13 changed files with 250 additions and 40 deletions
+47 -3
View File
@@ -85,6 +85,17 @@ if QT_IMPORT_ERROR is None:
from . import config as account_config
PLAINTEXT_SECRET_TITLE = "本地明文保存提示"
PLAINTEXT_API_KEY_WARNING = (
"API Key 会以本地明文保存到 config/ai_models.json,仅供本机调用 AI 使用。"
"该文件已 gitignore,UI 打码显示,日志/导出不记录明文。"
)
PLAINTEXT_PASSWORD_WARNING = (
"密码会以本地明文保存到本地 SQLite,仅供人工参考,不会自动登录/自动填。"
"数据库文件已 gitignore,请勿提交或分享。"
)
def _database_path(db_path=None, config=None) -> str:
return db_path or appconfig.db_path(config)
@@ -2496,7 +2507,7 @@ if QT_IMPORT_ERROR is None:
def execute(self):
result = appconfig.test_ai_model(self.model_name, path=self.ai_models_path)
payload = dict(result or {})
payload = dict(appconfig.sanitize_for_log(result or {}))
payload["name"] = self.model_name
return payload
@@ -2787,6 +2798,9 @@ if QT_IMPORT_ERROR is None:
model = self._form_values()
if model is None:
return
current = self._current_model()
if self._should_warn_plaintext_api_key(model, current):
self._show_plaintext_api_key_warning()
try:
if self.current_model_name is None:
appconfig.add_ai_model(model, path=self.ai_models_path)
@@ -3141,6 +3155,18 @@ if QT_IMPORT_ERROR is None:
QMessageBox.warning(self, "设置", message)
self._set_status(message)
def _should_warn_plaintext_api_key(self, model, current):
new_key = str((model or {}).get("api_key") or "")
current_key = str((current or {}).get("api_key") or "")
return bool(new_key) and new_key != current_key
def _show_plaintext_api_key_warning(self):
QMessageBox.warning(
self,
PLAINTEXT_SECRET_TITLE,
PLAINTEXT_API_KEY_WARNING,
)
def _current_model(self):
return self._model_by_name(self.current_model_name)
@@ -3293,11 +3319,14 @@ if QT_IMPORT_ERROR is None:
dialog = AccountDialog(self, default_port=default_port, config=self.config)
if dialog.exec() != QDialog.Accepted:
return
values = dialog.values()
if self._should_warn_plaintext_password(values):
self._show_plaintext_password_warning()
try:
accounts.create_account(
path=self.db_path,
config=self.config,
**dialog.values(),
**values,
)
except Exception as exc:
self._show_error(exc)
@@ -3317,12 +3346,15 @@ if QT_IMPORT_ERROR is None:
)
if dialog.exec() != QDialog.Accepted:
return
values = dialog.values()
if self._should_warn_plaintext_password(values, account):
self._show_plaintext_password_warning()
try:
updated = accounts.update_account(
account.alias,
path=self.db_path,
config=self.config,
**dialog.values(),
**values,
)
except Exception as exc:
self._show_error(exc)
@@ -3421,6 +3453,18 @@ if QT_IMPORT_ERROR is None:
self._set_status(f"快捷方式已生成:{shortcut_path}")
QMessageBox.information(self, "账号管理", f"快捷方式已生成:\n{shortcut_path}")
def _should_warn_plaintext_password(self, values, account=None):
new_password = str((values or {}).get("password") or "")
current_password = str(getattr(account, "password", None) or "")
return bool(new_password) and new_password != current_password
def _show_plaintext_password_warning(self):
QMessageBox.warning(
self,
PLAINTEXT_SECRET_TITLE,
PLAINTEXT_PASSWORD_WARNING,
)
class MainWindow(QMainWindow):
"""Main application window with the fixed five-tab workflow."""