0
0

给 Electron AI 会话做本地密文存储时,我会先把索引和密钥边界拆开

最近给一个 Electron 桌面端 AI 助手补会话恢复能力时,我遇到一个很现实的问题:用户希望关掉窗口后继续上次的分析,但会话里可能有需求文档、接口参数、工具调用结果和模型摘要。直接把整段消息写到本地 JSON 很快能跑通,后面调试、迁移和隐私边界都会变得很脆。

Electron AI 会话本地密文存储流程图,展示 renderer、preload、主进程加密服务和本地密文仓库

原创示意图:把 Electron AI 会话存储拆成 UI 命令、受控桥接、主进程加密服务、明文索引和密文 payload。 来源:Codex image generation

问题背景

Electron 的 renderer 适合承载聊天 UI,main process 更适合处理文件系统、系统能力和安全边界。官方 contextBridge 文档强调通过 preload 暴露受控 API,安全教程也要求持续收窄渲染进程的能力面。会话存储刚好踩在这条边界上:UI 要快速列出历史记录,主进程要控制本地路径、加密、解密和审计。

我把存储目标拆成三类:索引信息记录 sessionId、标题、更新时间和消息数量;敏感正文保存用户原文、工具入参、检索片段和长回答;密钥材料只在主进程加密服务里出现,不能透传给 renderer。

踩坑和关键难点

第一个坑是把前端 store 当成本地事实源。renderer 里有完整消息当然方便,但窗口刷新、DevTools、崩溃恢复都会放大暴露面。我的处理方式是让 renderer 只拿列表、摘要和当前页需要的片段,完整密文读写都走主进程接口。

第二个坑是把加密当成单点答案。落盘加密只能保护静态文件,运行时仍然要处理 preload 暴露范围、IPC 参数校验、日志脱敏和导出确认。Agent 工具结果里常混着路径、token 片段和内部接口响应,保存前要先做字段级裁剪。

第三个坑是忽略跨平台能力差异。Electron 的 safeStorage 提供字符串加密和解密能力,也提供 isEncryptionAvailable()。我会在启动阶段检查可用性,能力不足时关闭敏感会话自动保存,只保留非敏感索引和明确提示。

解决思路

我现在会把本地会话做成双层结构:明文索引用来支撑列表和搜索,密文 payload 用来存放完整消息。索引放在 app.getPath('userData') 下的 sessions 目录,记录 sessionId、preview、updatedAt、payloadHash 和 encryptionVersion。正文单独写入 vault 目录,每个 session 一个密文文件。

密钥边界放在主进程。初始化时生成本地数据密钥,再用 safeStorage.encryptString() 保护后写入 key blob。真正保存大段 payload 时,由主进程里的 Node.js crypto 模块完成加密和认证标签处理。renderer 发起保存请求时,只能传结构化会话内容,拿不到密钥,也不能指定任意写入路径。

关键步骤

第一步是收窄 preload API。我通常只暴露 listSessionsreadSessionsaveSessiondeleteSession 四个方法,每个方法都做参数白名单校验。renderer 不能直接传文件路径,只能传 sessionId 和业务字段。

第二步是把写入做成两段提交。先生成 payload 密文和 hash,写入临时文件;再更新明文索引;最后把临时文件改名为正式文件。这样应用在中途退出时,恢复扫描可以根据索引和 payloadHash 找到半写入状态。

第三步是建立最小可用审计。审计日志只记录动作、sessionId、调用来源和结果,不记录消息正文。删除会话时先标记索引状态,再删除密文 payload,最后写一条删除完成记录。

可复用经验

这套方案最有价值的地方是职责清楚。索引服务负责快,密文仓库负责稳,主进程加密服务负责边界,preload 负责把 UI 能力收窄成少量命令。以后要加全文检索、导出、云同步或多设备迁移,也可以先判断哪一层需要变化。

我现在做 Electron AI 会话存储,会先问四个问题:哪些字段可以明文索引,哪些内容必须密文落盘,谁能触发解密,失败时是否允许静默降级。答案明确后,后续排查就能从索引、payload、密钥、IPC 四条线分开定位。

主要来源

Electron safeStorage

Electron app API

Electron contextBridge API

Electron Security Tutorial

Node.js Crypto

评论