diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json index e7d77c9..5e22abb 100644 --- a/apps/desktop/src-tauri/capabilities/default.json +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -1,7 +1,11 @@ { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", - "description": "Default permissions for the CodeIsCheap workbench", + "description": "Minimum system API permissions for the CodeIsCheap workbench", "windows": ["main"], - "permissions": ["core:default", "dialog:allow-save"] + "permissions": [ + "core:event:allow-listen", + "core:event:allow-unlisten", + "dialog:allow-save" + ] } diff --git a/docs/architecture.html b/docs/architecture.html index be3f052..7dd6cd4 100644 --- a/docs/architecture.html +++ b/docs/architecture.html @@ -31,7 +31,7 @@

技术选型

控制核心Rust + Tokio进程、网络、加密和性能 TLS 捕获mitmproxy sidecar复用成熟协议栈,避免自研 MITM 数据SQLite / SQLCipher本地事务、全文检索和加密 - 扩展声明式映射,复杂插件用 WASI最小权限、可隔离 + 扩展MVP 不加载可执行插件;后续复杂插件用 WASI启用前定义最小权限与隔离验收

桌面边界使用版本化 Desktop API:Rust DTO 同时生成 JSON Schema 与 React TypeScript bindings。Tauri command 惰性打开 OS 凭据库和 SQLCipher,失败时保留 WebView 并允许用户重试。

diff --git a/docs/delivery.html b/docs/delivery.html index cc827ec..28e1b96 100644 --- a/docs/delivery.html +++ b/docs/delivery.html @@ -10,7 +10,7 @@

范围切线

MVP 必须有

-

后续再做

+

后续再做

建议阶段

diff --git a/docs/progress.html b/docs/progress.html index da6a4c1..8c6e824 100644 --- a/docs/progress.html +++ b/docs/progress.html @@ -61,7 +61,7 @@

4. 工作流进度

Desktop / FrontendAPP-001~00697%In progressCodex / TBD完成屏幕阅读器、首次捕获耗时与真实运行验收 Capture / NetworkCAP-001~00788%In progressCodex / TBD完成 macOS helper 真实故障矩阵;生产签名随发布凭据补齐 Prompt / AdaptersPAR-001~007100%DoneCodex / TBD保持价格目录与 provider usage 映射可追溯 - Data / SecurityDAT-001~002、SEC-001~00495%In progressCodex / TBD继续 WASI 权限、正式签名制品与独立安全评审 + Data / SecurityDAT-001~002、SEC-001~00496%In progressCodex / TBD完成正式签名制品与独立安全评审;第三方 WASI 插件属于后续范围 Test / ReleaseTST-001~004、REL-001~00386%In progressCodex / TBD收集真实 Beta 证据并逐项关闭九个人工/真实环境 gate @@ -89,7 +89,7 @@

5. 当前迭代:S5 / S6

DAT-002写入、查询、保留与磁盘压力Done100%Codex / TBD2026-07-15批量写入原子提交;默认保留 30 天/50,000 条并以 500 条事务清理;低于 256 MiB 或 SQLITE_FULL 时暂停记录但保持 Gateway 转发 SEC-001范围策略和凭据 scrubberDone100%Codex / Security Reviewer TBD2026-07-22请求与响应 header/JSON body 双层脱敏;Profile 管理保持既有 method/path allowlist,仅扩展最多 16 个精确 host;canary 在 DB、WAL、诊断日志、临时文件和支持包中零命中 SEC-002脱敏与导出Done100%Codex / Security Reviewer TBD2026-07-18单请求、筛选批量及支持包均强制扫描、完整预览、SHA-256 防陈旧和只新建文件;版本化 corpus 以 36 个字段名、17 类文本 secret 及近似反例覆盖 Rust/Python/Web - SEC-003本地 IPC 与插件隔离In progress97%Codex / Security Reviewer TBD2026-08-25IPC 0.6 在 Windows 使用当前用户与 SYSTEM protected DACL、拒绝远端连接的随机 named pipe,创建后回读 owner/ACE 并逐连接核对 sidecar PID;macOS/Linux 使用当前 eUID `0700` 私有目录内 `0600` Unix socket,并逐连接核对 peer eUID/PID;统一使用 256-bit token、常量时间比较、1 KiB 认证帧、2 秒截止和 ACK,真实传输、超时恢复与旧 0.5 bundle 拒绝均有测试;仅插件启用后的 WASI 最小权限待完成 + SEC-003本地 IPC 与插件隔离Done100%Codex / Security Reviewer TBD2026-07-22IPC 0.6 在 Windows 使用当前用户与 SYSTEM protected DACL、拒绝远端连接的随机 named pipe,macOS/Linux 使用当前 eUID 私有目录与 Unix peer credential;统一使用 256-bit token、常量时间比较、1 KiB 认证帧、2 秒截止和 ACK。主 WebView 仅获事件 listen/unlisten 与保存对话框权限,禁止 remote origin;MVP 不提供可执行插件面,未来启用第三方插件前必须另行完成 WASI 最小权限验收 SEC-004更新与供应链In progress96%Codex / Release Owner TBD2026-08-25依赖、Action、所有权与高危漏洞门禁已接入。签名发布工作流仅接受 main 中版本 tag,要求 updater 私钥、Windows Authenticode、macOS Developer ID/notarization、已签名 sidecar,并在发布前复核版本、`.sig`、大小与 SHA-256;正式凭据和首批制品待发布负责人配置 PAR-001Prompt IR v0.1 与首批 fixturesDone100%Codex / TBD2026-07-14OpenAI/Anthropic fixtures、校验与 schema 完成 PAR-002解析器注册表与检测链Done100%Codex / TBD2026-07-15置信度排序、错误/panic 隔离、IR 校验、partial issues 与 Raw fallback 完成 @@ -127,7 +127,7 @@

6. 后续迭代承诺

S2CAP-001/002、PAR-002/003、APP-003DoneGateway、OpenAI 解析与千条实时工作台全部通过验收 S3PAR-004、APP-004、TST-001、DAT-002Done双厂商 Inspector、能力矩阵与数据生命周期全部通过验收 S4CAP-003~005、TST-002In progresssidecar bundle、桌面运行时、协议矩阵、Proxy 取消/背压、跨平台 CA 状态及两平台用户级信任生命周期已实现;签名、其余协议一致性与交互式验收待推进 - S5CAP-006/007、SEC-002/003、APP-006、TST-003In progressSEC-002、CAP-007 完成,IPC 抗阻塞、双模式 OS PID 归因、Unix owner-only socket、Windows owner-only named pipe、readiness 身份校验、PAC/CA 拒绝恢复、macOS 授权 helper 和兼容诊断树已接入;继续真实故障验收与 WASI 最小权限 + S5CAP-006/007、SEC-002/003、APP-006、TST-003In progressSEC-002/003、CAP-007 完成,IPC 抗阻塞、双模式 OS PID 归因、owner-only transport、readiness 身份校验、PAC/CA 拒绝恢复、macOS 授权 helper、最小 WebView capability 和兼容诊断树已接入;继续真实管理员故障验收 S6PAR-005~007、APP-005Done四厂商适配器、token/成本/指纹、全文搜索和结构/文本 Compare 全部完成 S7TST-004、性能、可访问性、诊断与保留In progressaxe、键盘与焦点、双主题截图、千条列表预算及 Gateway P95 门禁完成;继续屏幕阅读器和 Beta 真实环境测量 S8SEC-004、REL-001/002、安全评审In progress仓库供应链门禁、失败关闭的跨平台签名发布流水线、应用内更新、预更新恢复快照、只读历史恢复与支持处理闭环已接入;正式凭据、首批制品、真实回滚与安全评审待推进 @@ -245,6 +245,7 @@

11. 决策与变更记录

2026-07-21Desktop / Beta MetricsAPP-006 / REL-003 本地证据采集完成独立双槽文件记录首次捕获和干净/异常会话,SQLCipher 聚合声明支持端点的解析率;设置页可复核、复制和只新建导出,不包含 Prompt、请求标识、日志或自动上传Codex 2026-07-21Release / Beta EvidenceREL-003 离线聚合链完成每个安装使用随机 128-bit ID 防止重复贡献;严格验证器拒绝隐私异常、未知字段、重复、symlink、伪造率和错误版本,按 30 位贡献者与 1,000 请求/会话最小量计算三个 gate,最终报告不保留样本 IDCodex 2026-07-22Desktop / Capture ProfilesAPP-006 / TM-02 Profile 管理闭环设置页新增可访问的 Profiles 视图,编辑名称、无凭据 HTTP(S) Gateway origin 和最多 16 个逐行精确 host;活跃捕获或 Proxy 模式下失败关闭并提供 Pause/Return to Gateway,保存使用后端规范化结果回填,Restore defaults 可恢复内置 OpenAI 配置;桌面与 600×620 紧凑视口均无重叠或无意义溢出Codex + 2026-07-22Security / WebViewSEC-003 Tauri capability 最小化主 WebView 从 `core:default` 收紧为事件 listen/unlisten 与保存对话框三项权限,只匹配本地 `main` 窗口并禁止 remote origins;供应链门禁精确校验 capability 集合,宽窗口匹配、远端来源、非本地访问或额外权限均使 CI 失败。MVP 不加载可执行插件,WASI 隔离随未来插件功能单独验收Codex 后续范围、架构、日期或资源变化均在此追加,并链接对应 ADR/会议结论。 diff --git a/docs/threat-model.html b/docs/threat-model.html index ab49333..2aebb0d 100644 --- a/docs/threat-model.html +++ b/docs/threat-model.html @@ -45,13 +45,13 @@

威胁台账

- + - + diff --git a/scripts/tests/test_verify_supply_chain.py b/scripts/tests/test_verify_supply_chain.py index 0c707f7..ad9abb1 100644 --- a/scripts/tests/test_verify_supply_chain.py +++ b/scripts/tests/test_verify_supply_chain.py @@ -71,6 +71,21 @@ def fixture(self, root: Path) -> None: ) (root / "apps" / "desktop" / "src-tauri").mkdir(parents=True) + (root / "apps" / "desktop" / "src-tauri" / "capabilities").mkdir() + (root / "apps" / "desktop" / "src-tauri" / "capabilities" / "default.json").write_text( + json.dumps( + { + "identifier": "default", + "windows": ["main"], + "permissions": [ + "core:event:allow-listen", + "core:event:allow-unlisten", + "dialog:allow-save", + ], + } + ), + encoding="utf-8", + ) (root / "apps" / "desktop" / "package.json").write_text( json.dumps({"dependencies": {"react": "19.2.7"}}), encoding="utf-8" ) @@ -149,6 +164,30 @@ def test_movable_and_unpinned_dependencies_fail(self) -> None: self.assertIn("git dependency must pin a full rev SHA", violations) self.assertIn("must run from version tags", violations) + def test_broad_or_remote_desktop_capabilities_fail(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + self.fixture(root) + capability = root / "apps" / "desktop" / "src-tauri" / "capabilities" / "default.json" + capability.write_text( + json.dumps( + { + "identifier": "default", + "windows": ["*"], + "local": False, + "remote": {"urls": ["https://example.test"]}, + "permissions": ["core:default", "dialog:default"], + } + ), + encoding="utf-8", + ) + + violations = "\n".join(collect_violations(root)) + self.assertIn("only the main window may match", violations) + self.assertIn("remote origins are forbidden", violations) + self.assertIn("local app access is required", violations) + self.assertIn("permissions must be limited", violations) + if __name__ == "__main__": unittest.main() diff --git a/scripts/verify_supply_chain.py b/scripts/verify_supply_chain.py index 19b60f7..3e92305 100644 --- a/scripts/verify_supply_chain.py +++ b/scripts/verify_supply_chain.py @@ -31,6 +31,11 @@ "/sidecars/", ) DEPENDABOT_ECOSYSTEMS = {"cargo", "npm", "pip", "github-actions"} +DESKTOP_CAPABILITY_PERMISSIONS = { + "core:event:allow-listen", + "core:event:allow-unlisten", + "dialog:allow-save", +} RELEASE_WORKFLOW_REQUIREMENTS = { 'tags:\n - "v*"': "must run from version tags", "environment: release": "must use the protected release environment", @@ -226,6 +231,39 @@ def _check_repository_policy(root: Path, violations: list[str]) -> None: violations.append(f".github/workflows/release.yml: {requirement}") +def _check_desktop_capability(root: Path, violations: list[str]) -> None: + capability_path = root / "apps" / "desktop" / "src-tauri" / "capabilities" / "default.json" + if not capability_path.is_file(): + violations.append("apps/desktop/src-tauri/capabilities/default.json: capability is missing") + return + try: + capability = json.loads(capability_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + violations.append("apps/desktop/src-tauri/capabilities/default.json: capability is invalid") + return + if not isinstance(capability, dict): + violations.append("apps/desktop/src-tauri/capabilities/default.json: capability must be an object") + return + if capability.get("identifier") != "default": + violations.append("apps/desktop/src-tauri/capabilities/default.json: identifier must be default") + if capability.get("windows") != ["main"]: + violations.append("apps/desktop/src-tauri/capabilities/default.json: only the main window may match") + if "remote" in capability: + violations.append("apps/desktop/src-tauri/capabilities/default.json: remote origins are forbidden") + if capability.get("local", True) is not True: + violations.append("apps/desktop/src-tauri/capabilities/default.json: local app access is required") + permissions = capability.get("permissions") + if ( + not isinstance(permissions, list) + or any(not isinstance(permission, str) for permission in permissions) + or set(permissions) != DESKTOP_CAPABILITY_PERMISSIONS + ): + violations.append( + "apps/desktop/src-tauri/capabilities/default.json: permissions must be limited to " + + ", ".join(sorted(DESKTOP_CAPABILITY_PERMISSIONS)) + ) + + def collect_violations(root: Path) -> list[str]: root = root.resolve() violations: list[str] = [] @@ -234,6 +272,7 @@ def collect_violations(root: Path) -> list[str]: _check_python(root, violations) _check_rust(root, violations) _check_repository_policy(root, violations) + _check_desktop_capability(root, violations) return violations
ID威胁现有证据剩余控制 / 门槛
TM-01鉴权凭据进入 IPC、日志或 DB共享敏感字段策略;sidecar 与 Core 双层 scrubber;DB、WAL、诊断日志、临时文件和支持包端到端 canary 零命中独立安全评审与 corpus 持续维护
TM-02捕获非目标应用或端点Rust/Python 共用精确 host、POST method 与 path pattern 策略;版本化 Profile 仅接受无凭据 HTTP(S) origin 和最多 16 个精确附加 host,拒绝重复内置 host,Gateway 与 Proxy 共用同一策略;设置页仅允许在暂停的 Gateway 中原子更新并失败回滚独立安全评审与真实自定义端点验收
TM-03本机其他进程伪造、读取或阻塞 IPCIPC 0.6 在 Windows 使用随机本地 named pipe,拒绝远端客户端,protected DACL 仅允许当前用户与 SYSTEM,创建后回读 owner/ACE 并逐连接匹配 sidecar PID;每个连接使用独立 instance,处理当前连接前预建下一实例,超时或错误直接丢弃旧 handle。macOS/Linux 使用当前 eUID 所有的 `0700` 目录内 `0600` Unix socket,逐连接匹配 sidecar eUID/PID,symlink 与错误 owner 失败关闭;所有传输使用 256-bit 会话 token、常量时间比较、mitmproxy 来源、1 KiB 认证帧、4 MiB Envelope、2 秒全流程截止和 ACK;临时归因端点不进入 Envelope;旧 0.5 bundle 启动前拒绝插件启用后的 WASI 最小权限
TM-03本机其他进程伪造、读取或阻塞 IPCIPC 0.6 在 Windows 使用随机本地 named pipe,拒绝远端客户端,protected DACL 仅允许当前用户与 SYSTEM,创建后回读 owner/ACE 并逐连接匹配 sidecar PID;每个连接使用独立 instance,处理当前连接前预建下一实例,超时或错误直接丢弃旧 handle。macOS/Linux 使用当前 eUID 所有的 `0700` 目录内 `0600` Unix socket,逐连接匹配 sidecar eUID/PID,symlink 与错误 owner 失败关闭;所有传输使用 256-bit 会话 token、常量时间比较、mitmproxy 来源、1 KiB 认证帧、4 MiB Envelope、2 秒全流程截止和 ACK;临时归因端点不进入 Envelope;旧 0.5 bundle 启动前拒绝;MVP 不加载可执行插件未来启用第三方插件前完成 WASI 最小权限与隔离验收
TM-04数据库文件被离线读取SQLCipher、WAL、迁移、加密备份、错误密钥、DB/WAL canary 与跨平台 OS 凭据库 CI锁定状态与 key 丢失恢复体验
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 使用静态逐参数 quote 的授权命令启动受校验可执行文件,helper 控制 socket 分别校验用户 owner 与 root peer,owner 断连、helper 崩溃和下次启动均进入独立恢复链macOS 授权桌面 helper 的真实管理员交互与强杀故障矩阵
TM-07依赖、sidecar 或更新包被替换依赖、Action、所有权和高危漏洞由仓库策略门禁;sidecar manifest、SBOM 与启动前 hash 校验已接入。签名发布仅接受 main 中版本 tag,要求 Windows Authenticode、macOS Developer ID/notarization、有效 sidecar 平台签名和独立 Tauri updater 签名;公开前复核 artifact 大小、SHA-256、`.sig` 与 `latest.json`,并先上传草稿。安装前重新核对版本、恢复代理并生成加密恢复快照;主库失败只读打开受校验备份正式密钥托管、首批平台签名制品、真实回滚和发布演练;持续跟踪 Tauri Linux GTK3 链的 glib 上游修复
TM-08复制或导出泄露秘密单请求、最多 200 条批量导出及支持包均强制扫描与完整预览;版本化 corpus 覆盖 36 个敏感字段名、17 类文本 secret 及近似反例,Rust/Python/Web 一致性回归通过;稳定占位符、策略版本、SHA-256 防陈旧和只新建文件已实现独立安全评审与 corpus 持续维护
TM-09恶意 Prompt 触发 UI 注入React 默认转义、Tauri CSPRaw 仅纯文本、URL 不自动执行、XSS fixture
TM-09恶意 Prompt 触发 UI 注入React 默认转义、Tauri CSP;主 WebView 只匹配本地 `main` 窗口,无 remote origin,仅允许事件 listen/unlisten 与保存对话框系统 API,策略门禁拒绝 `core:default` 或额外权限Raw 仅纯文本、URL 不自动执行、XSS fixture 与独立安全评审
TM-10超大/高速流量耗尽内存或磁盘认证帧 1 KiB、Envelope 4 MiB、sidecar 有界队列、IPC 连接截止时间、存储配额与磁盘压力暂停完善丢弃/降级指标与高压故障注入
TM-11将推断内容误标为已观察 PromptPrompt IR evidence 类型UI 强制显示 observed / inferred / unknown
TM-12诊断、崩溃报告或遥测外泄当前无产品遥测;256 KiB 诊断 journal 仅保存受限事件代码和时间戳;支持包最多包含最近 100 条事件,排除 Prompt、Raw、请求标识和日志详情,并由用户预览后保存;接收、保留与删除流程已发布未来遥测必须显式选择且先脱敏