feat: v0.6.0 全量审计修复 — 安全加固 + 死代码激活 + 依赖治理
CI / 类型检查 + Lint + 单元测试 (push) Failing after 5m45s
CI / 全量测试 (Electron ABI) (push) Failing after 5m22s
CI / 产物编译验证 (push) Successful in 10m0s

基于全量源码审计(electron/ 70+ 文件、src/ 45+ 文件完整读取)的
四阶段迭代,修复 1 项安全缺陷、8 项功能缺陷、依赖与文档系统性脱节。

【P0 安全与数据正确性】
- S-1 数据导出泄露明文密钥:data:export 全量导出直接透传
  configService.getAll()(敏感 key 解密返回),导出文件含明文 API Key。
  新增 sanitizeExportConfig(shared.ts)逐 key 脱敏 + 5 项回归测试
- F-3 session_summaries 无级联删除:删会话后摘要永久残留。建表语句补
  FOREIGN KEY ON DELETE CASCADE + 迁移 8 重建存量表(幂等检测)+ 2 项测试
- F-4 macOS activate 重建窗口后确认弹框失效:窗口创建收敛为 createMainWindow
  单一入口(beforeLoad 补 setMainWindow/IPC 注册),TrayManager 补
  rebindWindow,app:selectFolder 改 event.sender 动态解析窗口

【P1 死代码激活与功能补全】
- F-1 会话右键菜单挂载:ContextMenu session 分支(重命名/置顶/归档/
  导出/删除)约 200 行此前无任何触发点,Sidebar SessionItem 挂载
  onContextMenu;置顶/归档 label 随状态切换显示
- F-2 归档会话不可找回:Sidebar 新增「已归档」折叠面板(恢复入口),
  归档功能形成完整闭环
- F-7 Provider 白名单校验:createAdapter/buildFallbackAdapter 未知
  provider 显式拒绝(此前静默落入 DeepSeekAdapter 以空配置失败)
- F-8 死配置治理:接通 5 项(llm.temperature/llm.maxTokens 注入引擎、
  security.promptInjectionDefense 控制 SecurityScanHook+消息检测、
  logging.auditEnabled 控制 AuditLogHook、logging.traceEnabled 控制
  SessionRecorder,均 fail-secure 仅显式 false 关闭);删除 4 项
  无消费者配置(requireWriteConfirmation/maxFileWriteSizeKB/fontSize/
  animationMode)

【P2 依赖治理与 UX 修缮】
- D-1 移除僵尸依赖 electron-store/zod/rehype-raw(源码零引用,
  共裁 24 包);README 技术栈表同步删除虚假宣称
- metona-toast 升级 0.2.1 → 0.5.0(API 全兼容:107 种图标类型、
  配置项超集,default/configure/use 接口不变)
- F-5 工作空间手输路径实时落库中间态:改 pendingPath 草稿 + 显式
  「校验」按钮(选择文件夹与手输共用 validatePath 流程)
- F-6 确认弹框超时滞留:倒计时归零时主动 refreshPending(后端超时
  已删条目,拉取后弹框自然消解)
- D-3 ChatInput accept 移除 .pdf(分类器不识别,误导性入口);
  Onboarding 切换 Provider 自动填充默认 URL(与 LLMSettings 一致)

【P3 文档口径收敛】
- README:工具数 30+→28、版本 0.6.0、测试数 252、配置表补 F-8 接通项
- built-in/index.ts 计数注释 30→28
- docs/网络工具 v2 存储键名统一为点号口径(searxng.enabled)
- docs/完整设计指南修正 db.handlers.ts 失效路径引用为 data.ts

【验证】
- lint 0 error / 0 warning
- typecheck 双工程(node+web)0 错误
- test:electron 24 文件 252 用例全通过(+7 新增:导出脱敏 ×5、
  级联删除 ×2)
- electron-vite build 成功(metona-toast 0.5.0 chunk 正常)
- 系统 Node 模式 npm test 225 通过 + 27 ABI skip(符合预期)
This commit is contained in:
2026-08-22 18:28:31 +08:00
parent 22028c91c3
commit 6b2b587c94
22 changed files with 723 additions and 504 deletions
+56 -14
View File
@@ -5,6 +5,7 @@
import { ipcMain } from 'electron';
import type { IPCContext } from './context';
import log from 'electron-log';
import { sanitizeExportConfig } from './shared';
/**
* 导出限流常量:单会话导出的最大消息条数。
@@ -33,7 +34,10 @@ const sanitizeExportMessage = (msg: Record<string, unknown>): Record<string, unk
void _omit;
return {
...msg,
toolResult: { ...toolResult, result: { ...rest, _displayNote: '[image base64 omitted in export]' } },
toolResult: {
...toolResult,
result: { ...rest, _displayNote: '[image base64 omitted in export]' },
},
};
}
}
@@ -61,15 +65,26 @@ export function registerDataHandlers(ctx: IPCContext): void {
if (sessionId) {
// 导出单个会话:剥离超大 base64 后返回
const messages = sessionService.getMessages(sessionId);
const sanitized = messages.map((m) => sanitizeExportMessage(m as unknown as Record<string, unknown>));
const sanitized = messages.map((m) =>
sanitizeExportMessage(m as unknown as Record<string, unknown>),
);
return { success: true, data: sanitized };
}
// 导出所有会话:剥离 dataUrl + 每会话限条数,防渲染进程 Blob 序列化 OOM
// S-1 修复:config 走 sanitizeExportConfig 脱敏 —— getAll() 对敏感 key
// 解密返回明文,直接透传会把明文 API Key 写入用户下载的导出文件
const sessions = sessionService.list();
const allData: Record<string, unknown> = { sessions: [], config: configService.getAll() };
const allData: Record<string, unknown> = {
sessions: [],
config: sanitizeExportConfig(configService.getAll()),
};
for (const session of sessions) {
const rawMessages = sessionService.getMessages(session.id, { limit: MAX_EXPORT_MESSAGES_PER_SESSION });
const sanitizedMessages = rawMessages.map((m) => sanitizeExportMessage(m as unknown as Record<string, unknown>));
const rawMessages = sessionService.getMessages(session.id, {
limit: MAX_EXPORT_MESSAGES_PER_SESSION,
});
const sanitizedMessages = rawMessages.map((m) =>
sanitizeExportMessage(m as unknown as Record<string, unknown>),
);
(allData.sessions as Array<Record<string, unknown>>).push({
...session,
messages: sanitizedMessages,
@@ -93,10 +108,16 @@ export function registerDataHandlers(ctx: IPCContext): void {
db.exec('DELETE FROM messages');
db.exec('DELETE FROM sessions');
db.exec('COMMIT');
log.info(`[DATA] All sessions cleared: ${sessCount.c} sessions, ${msgCount.c} messages deleted`);
log.info(
`[DATA] All sessions cleared: ${sessCount.c} sessions, ${msgCount.c} messages deleted`,
);
return { success: true, deletedSessions: sessCount.c, deletedMessages: msgCount.c };
} catch (error) {
try { db.exec('ROLLBACK'); } catch { /* 忽略回滚错误 */ }
try {
db.exec('ROLLBACK');
} catch {
/* 忽略回滚错误 */
}
log.error('[DATA] clearSessions failed:', (error as Error).message);
return { success: false, error: (error as Error).message };
}
@@ -108,19 +129,36 @@ export function registerDataHandlers(ctx: IPCContext): void {
const db = sessionService.getDB();
try {
// M-41 修复: 三个 DELETE 操作用事务包裹,防止部分失败导致三类记忆数据不一致
const epiCount = db.prepare('SELECT COUNT(*) as c FROM episodic_memories').get() as { c: number };
const semCount = db.prepare('SELECT COUNT(*) as c FROM semantic_memories').get() as { c: number };
const workCount = db.prepare('SELECT COUNT(*) as c FROM working_memories').get() as { c: number };
const epiCount = db.prepare('SELECT COUNT(*) as c FROM episodic_memories').get() as {
c: number;
};
const semCount = db.prepare('SELECT COUNT(*) as c FROM semantic_memories').get() as {
c: number;
};
const workCount = db.prepare('SELECT COUNT(*) as c FROM working_memories').get() as {
c: number;
};
db.exec('BEGIN');
db.exec('DELETE FROM episodic_memories');
db.exec('DELETE FROM semantic_memories');
db.exec('DELETE FROM working_memories');
db.exec('COMMIT');
log.info(`[DATA] All memories cleared: ${epiCount.c} episodic, ${semCount.c} semantic, ${workCount.c} working memories deleted`);
return { success: true, deletedEpisodic: epiCount.c, deletedSemantic: semCount.c, deletedWorking: workCount.c };
log.info(
`[DATA] All memories cleared: ${epiCount.c} episodic, ${semCount.c} semantic, ${workCount.c} working memories deleted`,
);
return {
success: true,
deletedEpisodic: epiCount.c,
deletedSemantic: semCount.c,
deletedWorking: workCount.c,
};
} catch (error) {
// M-41 修复: 失败时回滚事务,确保数据一致性
try { db.exec('ROLLBACK'); } catch { /* 忽略回滚错误 */ }
try {
db.exec('ROLLBACK');
} catch {
/* 忽略回滚错误 */
}
log.error('[DATA] clearMemories failed:', (error as Error).message);
return { success: false, error: (error as Error).message };
}
@@ -143,7 +181,11 @@ export function registerDataHandlers(ctx: IPCContext): void {
log.info('[DATA] Audit logs cleared');
return { success: true };
} catch (error) {
try { db.exec('ROLLBACK'); } catch { /* 忽略回滚错误 */ }
try {
db.exec('ROLLBACK');
} catch {
/* 忽略回滚错误 */
}
return { success: false, error: (error as Error).message };
}
});