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 必须有
- Gateway + 显式代理
- 四类协议适配
- Anatomy / Raw / Timeline
- 结构化比较
- 本地搜索与脱敏导出
- Windows / macOS
-
后续再做
- 浏览器扩展与 TUN
- 团队云同步
- AI 语义分析
- 完整评测平台
- 移动和远程设备
- Linux GA
+
后续再做
- 浏览器扩展与 TUN
- 第三方 WASI 插件
- 团队云同步
- AI 语义分析
- 完整评测平台
- 移动和远程设备
- Linux GA
建议阶段
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 / Frontend | APP-001~006 | 97% | In progress | Codex / TBD | 完成屏幕阅读器、首次捕获耗时与真实运行验收 |
| Capture / Network | CAP-001~007 | 88% | In progress | Codex / TBD | 完成 macOS helper 真实故障矩阵;生产签名随发布凭据补齐 |
| Prompt / Adapters | PAR-001~007 | 100% | Done | Codex / TBD | 保持价格目录与 provider usage 映射可追溯 |
- | Data / Security | DAT-001~002、SEC-001~004 | 95% | In progress | Codex / TBD | 继续 WASI 权限、正式签名制品与独立安全评审 |
+ | Data / Security | DAT-001~002、SEC-001~004 | 96% | In progress | Codex / TBD | 完成正式签名制品与独立安全评审;第三方 WASI 插件属于后续范围 |
| Test / Release | TST-001~004、REL-001~003 | 86% | In progress | Codex / TBD | 收集真实 Beta 证据并逐项关闭九个人工/真实环境 gate |
@@ -89,7 +89,7 @@ 5. 当前迭代:S5 / S6
| DAT-002 | 写入、查询、保留与磁盘压力 | Done | 100% | Codex / TBD | 2026-07-15 | 批量写入原子提交;默认保留 30 天/50,000 条并以 500 条事务清理;低于 256 MiB 或 SQLITE_FULL 时暂停记录但保持 Gateway 转发 |
| SEC-001 | 范围策略和凭据 scrubber | Done | 100% | Codex / Security Reviewer TBD | 2026-07-22 | 请求与响应 header/JSON body 双层脱敏;Profile 管理保持既有 method/path allowlist,仅扩展最多 16 个精确 host;canary 在 DB、WAL、诊断日志、临时文件和支持包中零命中 |
| SEC-002 | 脱敏与导出 | Done | 100% | Codex / Security Reviewer TBD | 2026-07-18 | 单请求、筛选批量及支持包均强制扫描、完整预览、SHA-256 防陈旧和只新建文件;版本化 corpus 以 36 个字段名、17 类文本 secret 及近似反例覆盖 Rust/Python/Web |
- | SEC-003 | 本地 IPC 与插件隔离 | In progress | 97% | Codex / Security Reviewer TBD | 2026-08-25 | IPC 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 与插件隔离 | Done | 100% | Codex / Security Reviewer TBD | 2026-07-22 | IPC 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 progress | 96% | Codex / Release Owner TBD | 2026-08-25 | 依赖、Action、所有权与高危漏洞门禁已接入。签名发布工作流仅接受 main 中版本 tag,要求 updater 私钥、Windows Authenticode、macOS Developer ID/notarization、已签名 sidecar,并在发布前复核版本、`.sig`、大小与 SHA-256;正式凭据和首批制品待发布负责人配置 |
| PAR-001 | Prompt IR v0.1 与首批 fixtures | Done | 100% | Codex / TBD | 2026-07-14 | OpenAI/Anthropic fixtures、校验与 schema 完成 |
| PAR-002 | 解析器注册表与检测链 | Done | 100% | Codex / TBD | 2026-07-15 | 置信度排序、错误/panic 隔离、IR 校验、partial issues 与 Raw fallback 完成 |
@@ -127,7 +127,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 归因、Unix owner-only socket、Windows owner-only named pipe、readiness 身份校验、PAC/CA 拒绝恢复、macOS 授权 helper 和兼容诊断树已接入;继续真实故障验收与 WASI 最小权限 |
+ | S5 | CAP-006/007、SEC-002/003、APP-006、TST-003 | In progress | SEC-002/003、CAP-007 完成,IPC 抗阻塞、双模式 OS PID 归因、owner-only transport、readiness 身份校验、PAC/CA 拒绝恢复、macOS 授权 helper、最小 WebView capability 和兼容诊断树已接入;继续真实管理员故障验收 |
| S6 | PAR-005~007、APP-005 | Done | 四厂商适配器、token/成本/指纹、全文搜索和结构/文本 Compare 全部完成 |
| S7 | TST-004、性能、可访问性、诊断与保留 | In progress | axe、键盘与焦点、双主题截图、千条列表预算及 Gateway P95 门禁完成;继续屏幕阅读器和 Beta 真实环境测量 |
| S8 | SEC-004、REL-001/002、安全评审 | In progress | 仓库供应链门禁、失败关闭的跨平台签名发布流水线、应用内更新、预更新恢复快照、只读历史恢复与支持处理闭环已接入;正式凭据、首批制品、真实回滚与安全评审待推进 |
@@ -245,6 +245,7 @@ 11. 决策与变更记录
| 2026-07-21 | Desktop / Beta Metrics | APP-006 / REL-003 本地证据采集完成 | 独立双槽文件记录首次捕获和干净/异常会话,SQLCipher 聚合声明支持端点的解析率;设置页可复核、复制和只新建导出,不包含 Prompt、请求标识、日志或自动上传 | Codex |
| 2026-07-21 | Release / Beta Evidence | REL-003 离线聚合链完成 | 每个安装使用随机 128-bit ID 防止重复贡献;严格验证器拒绝隐私异常、未知字段、重复、symlink、伪造率和错误版本,按 30 位贡献者与 1,000 请求/会话最小量计算三个 gate,最终报告不保留样本 ID | Codex |
| 2026-07-22 | Desktop / Capture Profiles | APP-006 / TM-02 Profile 管理闭环 | 设置页新增可访问的 Profiles 视图,编辑名称、无凭据 HTTP(S) Gateway origin 和最多 16 个逐行精确 host;活跃捕获或 Proxy 模式下失败关闭并提供 Pause/Return to Gateway,保存使用后端规范化结果回填,Restore defaults 可恢复内置 OpenAI 配置;桌面与 600×620 紧凑视口均无重叠或无意义溢出 | Codex |
+ | 2026-07-22 | Security / WebView | SEC-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 @@ 威胁台账
| 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 | 本机其他进程伪造、读取或阻塞 IPC | IPC 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 | 本机其他进程伪造、读取或阻塞 IPC | IPC 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 CSP | Raw 仅纯文本、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 | 将推断内容误标为已观察 Prompt | Prompt IR evidence 类型 | UI 强制显示 observed / inferred / unknown |
| TM-12 | 诊断、崩溃报告或遥测外泄 | 当前无产品遥测;256 KiB 诊断 journal 仅保存受限事件代码和时间戳;支持包最多包含最近 100 条事件,排除 Prompt、Raw、请求标识和日志详情,并由用户预览后保存;接收、保留与删除流程已发布 | 未来遥测必须显式选择且先脱敏 |
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