{
let user_certificate = macos_domain_certificate_trust(Domain::User, certificate_der)?;
@@ -879,7 +906,7 @@ fn macos_wait_for_user_trust(
}
}
-#[cfg(target_os = "macos")]
+#[cfg(any(target_os = "macos", test))]
fn macos_security_error(action: &str, code: i32) -> io::Error {
let detail = match code {
-128 | -60_006 => "the user cancelled the security prompt",
@@ -1963,6 +1990,50 @@ MAoGCCqGSM49BAMCA0gAMEUCIQC1PB8+NumezrQf5unFGhVeufUcyw/sjH6p1aqs
));
}
+ #[test]
+ fn macos_ca_refusal_cleans_new_certificate_and_reports_cleanup_failure() {
+ use std::cell::Cell;
+
+ let removed = Cell::new(false);
+ let error = set_macos_trust_with_new_certificate_cleanup(
+ true,
+ || Err(macos_security_error("set user certificate trust", -128)),
+ || {
+ removed.set(true);
+ Ok(())
+ },
+ )
+ .expect_err("cancelled trust must fail");
+ assert!(removed.get());
+ assert!(error.to_string().contains("user cancelled"));
+
+ removed.set(false);
+ let _ = set_macos_trust_with_new_certificate_cleanup(
+ false,
+ || Err(macos_security_error("set user certificate trust", -128)),
+ || {
+ removed.set(true);
+ Ok(())
+ },
+ )
+ .expect_err("pre-existing certificate trust refusal must fail");
+ assert!(
+ !removed.get(),
+ "pre-existing certificates must be preserved"
+ );
+
+ let error = set_macos_trust_with_new_certificate_cleanup(
+ true,
+ || Err(macos_security_error("set user certificate trust", -60_006)),
+ || Err(io::Error::other("fault-injected keychain cleanup refusal")),
+ )
+ .expect_err("failed cleanup must remain actionable");
+ let detail = error.to_string();
+ assert!(detail.contains("user cancelled"));
+ assert!(detail.contains("newly added certificate cleanup failed"));
+ assert!(detail.contains("fault-injected keychain cleanup refusal"));
+ }
+
#[cfg(windows)]
#[test]
fn windows_certificate_store_lifecycle_is_exact_and_idempotent() {
diff --git a/docs/progress.html b/docs/progress.html
index 6e32850..a9a264e 100644
--- a/docs/progress.html
+++ b/docs/progress.html
@@ -99,7 +99,7 @@ 5. 当前迭代:S5 / S6
| PAR-007 | token、成本和语义指纹 | Done | 100% | Codex / TBD | 2026-07-17 | 四厂商 reported usage 归一化、显式 estimated 估算、版本化价格匹配、未知价格留空与 BLAKE3-256 语义指纹完成 |
| TST-001 | 协议 fixture 与 golden tests | Done | 100% | Codex / TBD | 2026-07-17 | 版本化能力矩阵覆盖 OpenAI、Anthropic、Gemini 与 Ollama 的请求、响应、流式、工具、多模态、错误和 Raw fallback;声明均由 fixture 与 golden 验证 |
| TST-002 | Gateway/Proxy 集成测试 | In progress | 82% | Codex / TBD | 2026-08-25 | 认证 IPC、暂停丢弃、真实模式切换与进程树清理、压缩、流式脱敏、非目标 TLS、HTTP/2 基线一致性及 Windows CA 精确增删通过;真实 sidecar 在客户端取消后保持存活,IPC 停止消费且 72 个事件超过容量 64 时,36 个目标响应仍在 15 秒内完成;真实 ROOT 往返和其余协议一致性待扩展 |
- | TST-003 | 平台故障注入 | In progress | 70% | Codex / TBD | 2026-08-25 | Windows/macOS 强杀恢复、活 owner 防误恢复、sidecar 独立退出回退和端口冲突拒绝已通过;PAC 原状态下应用与同步回滚双重失败会交由独立 watchdog 恢复;readiness token 不复用 IPC token,错误 listener 与旧 addon 不会触发系统代理切换;CA 拒绝与 macOS helper 故障矩阵待完成 |
+ | TST-003 | 平台故障注入 | In progress | 75% | Codex / TBD | 2026-08-25 | Windows/macOS 强杀恢复、活 owner 防误恢复、sidecar 独立退出回退和端口冲突拒绝已通过;PAC 原状态下应用与同步回滚双重失败会交由独立 watchdog 恢复;macOS 用户拒绝 CA 信任时清理本次新增证书,清理失败返回双重错误,桌面保持 not trusted 并允许重试;macOS helper 故障矩阵待完成 |
| REL-002 | 诊断与支持包 | In progress | 75% | Codex / Support Owner TBD | 2026-09-22 | 版本化 JSON 支持包可预览、复制和保存,包含不带请求标识的兼容诊断树;256 KiB code-only journal 与最近 100 条事件接入,排除 Prompt、Raw 和日志详情;支持处理流程待完成 |
| SPIKE-001 | Gateway 流式透明转发验证 | Done | 100% | Codex / TBD | 2026-07-14 | 双向流式、取消传递、头清理与稳定 502 集成测试通过 |
| SPIKE-002 | mitmproxy sidecar IPC/打包验证 | Done | 100% | Codex / TBD | 2026-07-14 | 凭据清理、IPC、打包与真实转发通过 |
@@ -123,7 +123,7 @@ 6. 后续迭代承诺
| S2 | CAP-001/002、PAR-002/003、APP-003 | Done | Gateway、OpenAI 解析与千条实时工作台全部通过验收 |
| S3 | PAR-004、APP-004、TST-001、DAT-002 | Done | 双厂商 Inspector、能力矩阵与数据生命周期全部通过验收 |
| S4 | CAP-003~005、TST-002 | In progress | sidecar bundle、桌面运行时、协议矩阵、Proxy 取消/背压、跨平台 CA 状态及两平台用户级信任生命周期已实现;签名、其余协议一致性与交互式验收待推进 |
- | S5 | CAP-006/007、SEC-002/003、APP-006、TST-003 | In progress | SEC-002、CAP-007 完成,IPC 抗阻塞、双模式 OS PID 归因、readiness 身份校验和兼容诊断树已接入;继续 macOS helper、OS socket ACL、PAC 与 CA 拒绝故障注入 |
+ | S5 | CAP-006/007、SEC-002/003、APP-006、TST-003 | In progress | SEC-002、CAP-007 完成,IPC 抗阻塞、双模式 OS PID 归因、readiness 身份校验、PAC/CA 拒绝恢复和兼容诊断树已接入;继续 macOS helper 与 OS socket ACL |
| S6 | PAR-005~007、APP-005 | Done | 四厂商适配器、token/成本/指纹、全文搜索和结构/文本 Compare 全部完成 |
| S7 | TST-004、性能、可访问性、诊断与保留 | Not started | 功能冻结 |
| S8 | SEC-004、REL-001/002、安全评审 | In progress | code-only 诊断与支持包已接入;签名更新、安装回滚、支持流程与安全评审待推进 |
@@ -222,6 +222,7 @@ 11. 决策与变更记录
| 2026-07-18 | Test / Proxy | TST-002 取消与背压矩阵完成 | 单元测试以阻塞 IPC worker 和容量 1 队列证明满队列 submit 立即失败;首次 IPC 失败会清空 backlog、熔断 10 秒并自动探测恢复;真实打包 sidecar 在 IPC listener 停止消费后承受 36 个目标请求、72 个捕获事件和一次客户端取消,转发响应保持一致且进程继续存活;两个结论成为 Python 与 Rust bundle 强制合同 | Codex |
| 2026-07-18 | Security / Recovery | TST-003 sidecar 端口冲突关闭 | 桌面为每次启动生成与 IPC token 隔离的 256-bit readiness token;打包 addon 在保留域名内部终止版本化探针,Rust runtime 常量时间核对响应后才判定 ready;普通 listener、错误 token、旧 addon 和端口抢占均失败并清理进程树,系统代理不会切换到未知进程 | Codex |
| 2026-07-18 | Recovery / Fault injection | TST-003 PAC 双重失败恢复完成 | 代理应用部分写入且进程内回滚失败时不再删除 armed journal;owner 主动关闭控制通道并等待独立 watchdog 按预加载 PAC 快照恢复,独立恢复也失败时保留 journal 并返回完整三段错误 | Codex |
+ | 2026-07-18 | Certificate / Fault injection | TST-003 CA 拒绝恢复完成 | macOS 用户拒绝信任时删除本次刚加入登录钥匙串的证书;删除也失败时同时报告拒绝与残留清理错误;桌面保持 not trusted、显示原因并允许用户再次触发系统信任流程 | Codex |
| 后续范围、架构、日期或资源变化均在此追加,并链接对应 ADR/会议结论。 |
diff --git a/docs/threat-model.html b/docs/threat-model.html
index 9de8042..35bfd6c 100644
--- a/docs/threat-model.html
+++ b/docs/threat-model.html
@@ -47,8 +47,8 @@ 威胁台账
| TM-02 | 捕获非目标应用或端点 | Rust/Python 共用精确 host、POST method 与 path pattern 策略 | Profile UI 与自定义端点策略管理 |
| TM-03 | 本机其他进程伪造、读取或阻塞 IPC | IPC 0.4 仅限 loopback;每次 Proxy 会话生成 256-bit token 并常量时间比较;Windows/macOS/Linux 首连接通过 OS socket 表绑定已启动 sidecar PID,Linux 仅接受精确 socket inode 的唯一 owner;sidecar 在服务端 ACK 前保持连接;认证帧 1 KiB、Envelope 4 MiB;要求 mitmproxy 来源声明;连接全流程 2 秒未完成即释放;临时归因端点严格限制为同地址族 loopback 且不进入 Envelope;旧协议 bundle 启动前拒绝 | 用户级 socket ACL;插件启用后的 WASI 最小权限 |
| TM-04 | 数据库文件被离线读取 | SQLCipher、WAL、迁移、加密备份、错误密钥、DB/WAL canary 与跨平台 OS 凭据库 CI | 锁定状态与 key 丢失恢复体验 |
- | TM-05 | 本地 CA 私钥泄露或残留信任 | 每设备 CA、跨平台私钥权限检查、指纹 UI、Windows CurrentUser 与 macOS User 幂等卸载已实现 | 两平台交互式验收;发布前独立安全评审 |
- | TM-06 | 崩溃后系统代理残留 | Windows/macOS 强杀恢复实验;Windows 桌面已接入私有 journal、watchdog、启动恢复和活 owner 校验;sidecar 单独退出会按代际恢复代理并回退 Gateway;端口占用者无法通过独立 256-bit readiness token 校验,冲突启动会清理进程树且不会切换系统代理 | macOS 特权 helper;PAC 与 CA 拒绝故障注入 |
+ | TM-05 | 本地 CA 私钥泄露或残留信任 | 每设备 CA、跨平台私钥权限检查、指纹 UI、Windows CurrentUser 与 macOS User 幂等卸载已实现;macOS 信任拒绝会清理本次新增证书,桌面保持未信任并允许重试 | 两平台交互式验收;发布前独立安全评审 |
+ | TM-06 | 崩溃后系统代理残留 | Windows/macOS 强杀恢复实验;Windows 桌面已接入私有 journal、watchdog、启动恢复和活 owner 校验;sidecar 单独退出会按代际恢复代理并回退 Gateway;端口占用者无法通过独立 256-bit readiness token 校验,冲突启动会清理进程树且不会切换系统代理;PAC 双重失败由 watchdog 恢复 | macOS 特权 helper |
| TM-07 | sidecar 或更新包被替换 | 打包 manifest 含 SHA-256 | 签名、SBOM、启动前 hash 校验;发布硬门槛 |
| TM-08 | 复制或导出泄露秘密 | 单请求、最多 200 条批量导出及支持包均强制扫描与完整预览;版本化 corpus 覆盖 36 个敏感字段名、17 类文本 secret 及近似反例,Rust/Python/Web 一致性回归通过;稳定占位符、策略版本、SHA-256 防陈旧和只新建文件已实现 | 独立安全评审与 corpus 持续维护 |
| TM-09 | 恶意 Prompt 触发 UI 注入 | React 默认转义、Tauri CSP | Raw 仅纯文本、URL 不自动执行、XSS fixture |