Skip to content

v2.1.0: 一轮最高标准审计后的数据安全、隐私与健壮性加固

Choose a tag to compare

@github-actions github-actions released this 24 Aug 09:32

v2.1.0: 一轮最高标准审计后的数据安全、隐私与健壮性加固

这是一轮系统性代码审计后的集中修复版本:覆盖数据丢失、隐私泄漏、进程健壮性、发布链安全与无障碍打磨,共修复 29 项审计条目并新增回归测试(单元测试 116 → 160)。绝大多数用户无需任何操作即可获益;少数条目仅影响新保存的数据(见下)。

🔒 数据安全与隐私

  • 多句听写不再只保留最后一句:跨长停顿的首句音频不再被裁掉。
  • 实时听写(Bailian/Volcengine)在 socket 中途断开或收尾报错时,不再摧毁已采集的完整录音——有兜底时降级到本地/批量转写,无兜底时如实呈现原始错误。
  • 「允许本地兜底」对实时独占供应商真正生效:空转写不再过早判定「无语音」而丢弃录音。
  • Secure Input(密码框、sudo、部分登录框)下不再静默丢字:粘贴被系统丢弃时保留剪贴板供手动粘贴,并在胶囊明确提示。
  • 听写中途切换输入框,文字不再落进原来的框。
  • 转写文本写入剪贴板时标记为「机密」(org.nspasteboard.ConcealedType),Alfred / Paste / Raycast / Maccy / Pastebot 等剪贴板管理器不再长期留存听写内容。
  • 敏感应用(密码管理器、银行等)保护统一覆盖插入 / 历史 / 日志 / 云推理四条出口——密码框里的听写不再被上传云端或写入历史。
  • 删除历史 / 清空后不再在数据库与 -wal 里残留明文。
  • 保留策略(90 天 / 行数上限)现在有明确的界面告知。

🛠 健壮性

  • 实时事件改为单消费者顺序处理,杜绝迟到的 partial 覆盖已定稿的 final 造成转写错乱。
  • 停止 增加看门狗,异常挂起也能在有界时间内收尾,不再永久卡在「停止中」。
  • 慢连接冷启动不再静默丢失开头语音;五家实时供应商的收尾等待统一到一致的超时预算。
  • 本地模型服务器进程在崩溃/强退后会被回收,不再泄漏数 GB 内存;退出仅 SIGTERM 无效时升级到 SIGKILL。
  • 本地模型下载改为校验和 + 原子替换,失败不再删掉已有可用模型。
  • 供应商错误提示统一截断与脱敏,不再把上游 HTML 或回显的请求内容抛给用户。
  • 初始化失败不再假装「就绪」:进入可重试的降级态;退出前对在途听写、剪贴板恢复、设置写入做有界 flush。

⚡️ 性能与打磨

  • 听写时打开控制面板不再每帧全量重渲染(50Hz → 仅相关视图)。
  • 设置改动改为去抖写盘,滑块拖拽不再每帧重写整块设置。
  • 历史清理不再每次写入都跑一次全表反连接。
  • 词汇规则(避免词 / 替换规则)真正生效:作为推理提示软约束 + 收尾文本后处理。
  • 无障碍:胶囊 VoiceOver 播报覆盖全部相位;HUD 与控制面板尊重「减弱动态效果」「增强对比度」;小图标命中区达 44pt;引导页在拒绝辅助功能权限时可跳过。

🧰 发布链

  • CI 增加 pbxproj/project.yml 漂移门禁、appcast「本次条目」校验、以及测试执行/跳过下限门禁。
  • 发布产物校验断言 bundled 模型运行时与 MediaRemote 适配器确实在 .app 内且架构匹配;DMG 打包改用唯一卷名与相对路径校验和。

v2.1.0: Data-safety, privacy, and robustness hardening after a top-standard audit

This is a consolidated fix release following a systematic code audit: it covers data loss, privacy leaks, process robustness, release-pipeline safety, and accessibility polish — 29 audit items fixed with regression tests added (unit tests 116 → 160). Nearly all users benefit with no action required; a few items affect only newly-saved data (see below).

🔒 Data safety & privacy

  • Multi-utterance dictation no longer keeps only the last sentence — the first utterance across a long pause is no longer trimmed away.
  • Realtime dictation (Bailian/Volcengine) no longer destroys the captured recording when the socket dies mid-session or finalize errors — it degrades to local/batch transcription when a fallback exists, and surfaces the real error otherwise.
  • "Allow local fallback" now actually works for realtime-only providers: an empty transcript no longer prematurely reports "no speech" and discards the recording.
  • Under Secure Input (password fields, sudo, some login forms) text is no longer silently dropped — when the paste is rejected the transcript is kept on the clipboard for a manual paste and the capsule explains why.
  • Switching text fields mid-dictation no longer lands the text in the old field.
  • Transcripts written to the clipboard are marked concealed (org.nspasteboard.ConcealedType), so clipboard managers (Alfred / Paste / Raycast / Maccy / Pastebot) no longer retain dictation content.
  • Sensitive-app protection (password managers, banking, etc.) now covers all four sinks — insertion / history / logs / cloud reasoning — so a password dictated into a protected app is neither uploaded to the cloud nor stored in history.
  • Clearing history no longer leaves plaintext behind in the database or the -wal sidecar.
  • The retention policy (90 days / row cap) is now disclosed in the UI.

🛠 Robustness

  • Realtime events are now processed by a single ordered consumer, so a late partial can no longer overwrite a finalized result.
  • stop() gains a watchdog so a hung teardown still finalizes within a bounded time instead of parking in "stopping" forever.
  • Slow cold-start connects no longer silently drop leading audio; the finalize wait across all five realtime providers now shares one consistent timeout budget.
  • Local model-server processes are reaped after a crash/force-quit instead of leaking multiple GB; quit escalates SIGTERM → SIGKILL when a child ignores SIGTERM.
  • Local model downloads now verify a checksum and swap atomically, so a failed download no longer deletes an existing good model.
  • Provider errors are uniformly truncated and sanitized, so upstream HTML or echoed request content no longer reaches the user.
  • A failed init no longer pretends to be "ready" — it enters a retryable degraded state; quit performs a bounded flush of in-flight dictation, clipboard restore, and settings writes.

⚡️ Performance & polish

  • Opening the control panel during dictation no longer re-renders the whole panel every frame (50 Hz → only the relevant views).
  • Settings changes are debounced to disk, so a slider drag no longer rewrites the entire settings blob every frame.
  • History pruning no longer runs a full-table anti-join on every single write.
  • Vocabulary rules (avoided terms / replacement rules) now actually take effect: as a soft reasoning-prompt constraint plus final-text post-processing.
  • Accessibility: the capsule's VoiceOver announcements cover every phase; the HUD and control panel honour Reduce Motion and Increase Contrast; small icon buttons meet the 44 pt hit-target minimum; onboarding can be advanced when Accessibility permission is declined.

🧰 Release pipeline

  • CI gains a pbxproj/project.yml drift gate, an appcast "current release present" check, and a test execution/skip floor gate.
  • Release artifact verification asserts the bundled model runtimes and the MediaRemote adapter are actually inside the .app with matching architecture; DMG packaging switched to a unique volume name and relative-path checksums.