From 1669a32e834ae180e9296e596e4c499b55eef26e Mon Sep 17 00:00:00 2001 From: oratis Date: Sat, 8 Aug 2026 18:25:32 +0800 Subject: [PATCH] docs: close out the governance layer and cut 0.3.0 Records the threat-model delta, marks the adoption plan implemented with an honest deviation log, and bumps to 0.3.0 (minor, not patch: the unattended permission clamp in #244 is breaking on 0.x). security-model.md gains three threats (#8 in-project secret reads, #9 unattended permissive inheritance, #10 no audit or undo) and a residual-risk section stating plainly what the file contract is not. It constrains dispatcher tool calls, not Bash; path normalization is string math, not realpath. Describing it as secret protection would be the false confidence the plan named as this feature's main risk. The adoption plan now carries a deviation table rather than a claim of clean execution. Six entries, including two where the plan was simply wrong: PR 0's premise that unattended runs might silently allow (the ask path was already fail-closed), and the four-client consistency test (only two hosts resolve policy independently; VS Code and LSP consume the server's answer, and the tests say so instead of implying four verified paths). Two P2 items remain undone and are listed as such. Found while bumping: the repo's own version-consistency test caught Cargo.lock still pinning 0.2.0, a sixth location RELEASING.md's four-item list never mentioned. Both fixed. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 88 ++++++++++++++++++++++++++ README.md | 7 +- apps/cli/package.json | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/tauri.conf.json | 2 +- docs/FLOATBOAT_ADOPTION_PLAN.md | 24 ++++++- docs/RELEASING.md | 9 ++- docs/security-model.md | 43 ++++++++++--- packages/core/src/index.ts | 2 +- 11 files changed, 163 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a0098..253a8b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,94 @@ All notable changes to DeepCode are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] — 2026-08-08 + +A workspace-governance layer: what the agent may touch, what it changed, and how +to undo it. Derived from a first-hand study of Floatboat's open **Selfware +protocol** — see [`docs/research/floatboat.md`](docs/research/floatboat.md) for +the research (with evidence grading) and +[`docs/FLOATBOAT_ADOPTION_PLAN.md`](docs/FLOATBOAT_ADOPTION_PLAN.md) for what was +adopted, what was rejected, and where the implementation diverged from the plan. + +### ⚠️ Breaking + +- **Unattended runs no longer inherit a permissive permission mode.** A + `permissions.defaultMode` of `bypassPermissions` or `acceptEdits` — chosen for + interactive convenience — is clamped to `default` for scheduled jobs, which + run with nobody present to approve anything. Set a job's `profile.mode` + explicitly to opt back in. The clamp names itself and the fix in the job log + on the first run after upgrading. (#244) + +### 🔒 Security + +- **`.env` could be read, and there was no way to say otherwise.** Permission + rules match on the _tool_; their only path-aware match is a prefix compare + against an argument that is usually an absolute path, so `Read(.env*)` matched + nothing. The new [file contract](docs/file-contract.md) adds the missing axis — + glob × read/write/execute × allow/ask/deny — composed with the existing rules + by most-restrictive-wins. It can only tighten. (#238, #239) +- A contract `deny` **cannot be waived by `bypassPermissions`**. It states + something standing about a path rather than prompting about one call, so the + mode that exists to skip prompts has no business clearing it. (#239) +- Plugin subprocesses are gated by the same path rules; the capability bridge + previously called the dispatcher without a contract. (#239) +- `/combo` drafts exclude paths the contract denies reading and redact + credential-shaped values. A rule that stops at the tool call but not at the + export is not much of a rule. (#243) + +### ✨ Added + +- **File contract** — `deepcode contract `. Optional; with no + contract file, behaviour is unchanged. (#238, #239) +- **Change ledger** — `deepcode ledger `. An + append-only record pairing each mutation with the request that motivated it + and the checkpoint that reverses it, on two timelines (`changes`, + `governance`). Stored outside the repository so `git status` stays clean. + (#240, #241) +- **No Silent Apply** — explain, preview, accept/reject/defer, rollback point + first. `confirm` is a required argument, so a caller that cannot ask a human + cannot apply. (#241) +- **`runtime/capabilities`** — a protocol method answering what the runtime may + write and which actions always stop for a human, distinct from `initialize`'s + protocol-feature flags. The CLI and app-server build it through one function, + with a test asserting they agree field-for-field. (#242) +- **`/combo`** — distil a finished thread into a `SKILL.md` draft, with + `allowed-tools` derived from the tools actually called. (#243) +- **Trigger profiles** — per-job `mode`, `permissions`, and `sandbox` for + scheduled work. Permissions and sandbox can only tighten. (#244) +- `onApprovalRequired: 'abort'` for scheduled jobs, plus exit code `6`. A job + whose first write is refused otherwise grinds on and reports a confidently + wrong result. (#237) +- `deepcode doctor` prints the runtime capability declaration and the + file-contract warnings. (#242) + +### 🐛 Fixed + +- Aborting mid-batch left `tool_use` blocks unanswered, which a provider rejects + on resume. Remaining calls now get an explicit "never ran" result. (#237) +- `docs/cli-flags.md`'s exit-code table contradicted the implementation (it + listed `3` as "tool denied" and `5` as "API key invalid"). Corrected against + `apps/cli/src/headless.ts`, which owns the contract. (#237) + +### 📄 Documentation + +- New: [`docs/file-contract.md`](docs/file-contract.md), + [`docs/change-ledger.md`](docs/change-ledger.md), + [`docs/combo.md`](docs/combo.md), + [`docs/research/floatboat.md`](docs/research/floatboat.md), + [`docs/FLOATBOAT_ADOPTION_PLAN.md`](docs/FLOATBOAT_ADOPTION_PLAN.md). +- `docs/security-model.md` gains threats #8–#10 and a **residual-risk** section + stating plainly that the file contract is policy, not a boundary: it does not + constrain Bash, and only the sandbox does. + +### 🚫 Deliberately not adopted + +`.self` self-executing distribution (a supply-chain surface for a coding agent), +Floatboat's passive habit observation across files and browser tabs (a privacy +line, and unnecessary — `/combo` gets the value from an explicit invocation), +cross-organisation agent networks, and a second loopback HTTP runtime alongside +the app-server. Reasoning in the adoption plan §3. + ## [0.2.0] — 2026-08-02 Largest release so far: the desktop app, VS Code extension, and LSP server stop diff --git a/README.md b/README.md index c48d5b5..02b663b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ DeepCode 让 DeepSeek 可以在本地代码库中执行读取、编辑、命令、审阅、MCP 和可恢复会话工作。项目最初以 Claude Code 兼容为目标,现在正转向经过验证的 Codex 式运行模型:统一任务生命周期、可靠中断、清晰权限边界和跨客户端一致行为。 -- **已可用**:Node.js CLI、Tauri macOS 客户端、核心工具、MCP、hooks、skills、plugins、sandbox、sessions、background tasks 与 voice input。 +- **已可用**:Node.js CLI、Tauri macOS 客户端、核心工具、MCP、hooks、skills、plugins、sandbox、sessions、background tasks、voice input,以及 0.3.0 的工作区治理层(file contract、change ledger、runtime capabilities、`/combo`、trigger profile)。 - **在收敛**:VS Code/LSP、统一权限、真实取消、thread/turn/item 协议与跨客户端恢复。 - **兼容优先**:继续读取既有 `settings.json`、`DEEPCODE.md`、`AGENTS.md` 和 Claude 风格扩展资产,但不以未经验证的“1:1 parity”作为安全或完成度承诺。 - **设计路线**:完整审查、正反方审议和分阶段 PR 见 [Codex alignment plan](docs/CODEX_ALIGNMENT_PLAN.md)。 @@ -53,6 +53,9 @@ Mac 客户端(v1 即将发布):拖入 Applications → 首启完成 onboar | [docs/BEHAVIOR_PARITY.md](docs/BEHAVIOR_PARITY.md) | 与 Claude Code 的逐项行为对比 | | [docs/SHIPPING_MAC.md](docs/SHIPPING_MAC.md) | 给 maintainer:Apple Dev ID + 签名 + 公证全流程 | | [docs/VOICE_INPUT.md](docs/VOICE_INPUT.md) | 装 whisper.cpp 本地语音输入 | +| [docs/file-contract.md](docs/file-contract.md) | 路径维度权限契约(`deepcode contract`) | +| [docs/change-ledger.md](docs/change-ledger.md) | 变更账本与回滚(`deepcode ledger`) | +| [docs/combo.md](docs/combo.md) | `/combo` —— 把做完的 thread 蒸馏成 skill | | [docs/DEMO_SCRIPT.md](docs/DEMO_SCRIPT.md) | 5 分钟 launch 视频逐段录制脚本 | ### 设计文档 @@ -61,6 +64,8 @@ Mac 客户端(v1 即将发布):拖入 Applications → 首启完成 onboar | ---------------------------------------------------------------------------- | --------------------------------------------------- | | [docs/CODEX_ALIGNMENT_PLAN.md](docs/CODEX_ALIGNMENT_PLAN.md) | 当前整体改造计划、审计证据、正反方审议与 PR 路线 | | [docs/THREE_WAY_REVIEW.md](docs/THREE_WAY_REVIEW.md) | 与 Claude Code / Codex 的三方能力+界面对比与优先级 | +| [docs/FLOATBOAT_ADOPTION_PLAN.md](docs/FLOATBOAT_ADOPTION_PLAN.md) | 工作区治理层的采纳/拒绝决策记录与实施偏差 | +| [docs/research/floatboat.md](docs/research/floatboat.md) | Floatboat / Selfware 调研(带证据分级) | | [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md) | 整体开发方案 v0.5(1500+ 行 / §3 模块 / §6 里程碑) | | [docs/VISUAL_DESIGN.html](docs/VISUAL_DESIGN.html) | 视觉设计 v0.4(11 屏 mockup) | | [docs/security-model.md](docs/security-model.md) | 威胁模型 + 防御层 + 攻击向量测试 + 已知缺口 | diff --git a/apps/cli/package.json b/apps/cli/package.json index 3c59e6f..52e3eb6 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "deepcode-cli", - "version": "0.2.0", + "version": "0.3.0", "description": "DeepCode CLI — DeepSeek-powered AI coding agent for real codebases", "license": "MIT", "type": "module", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 15b9dfe..9e8333b 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@deepcode/desktop", - "version": "0.2.0", + "version": "0.3.0", "private": true, "description": "DeepCode Mac desktop client — Tauri + React", "license": "MIT", diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 0be1be8..0825dec 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -672,7 +672,7 @@ dependencies = [ [[package]] name = "deepcode_desktop" -version = "0.2.0" +version = "0.3.0" dependencies = [ "dirs 5.0.1", "libc", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index cf7ed95..fb787a1 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deepcode_desktop" -version = "0.2.0" +version = "0.3.0" description = "DeepCode Mac desktop client" authors = ["oratis"] edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 038108e..9c768e3 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "DeepCode", - "version": "0.2.0", + "version": "0.3.0", "identifier": "dev.deepcode.desktop", "build": { "frontendDist": "../dist", diff --git a/docs/FLOATBOAT_ADOPTION_PLAN.md b/docs/FLOATBOAT_ADOPTION_PLAN.md index e1838dd..5e3642e 100644 --- a/docs/FLOATBOAT_ADOPTION_PLAN.md +++ b/docs/FLOATBOAT_ADOPTION_PLAN.md @@ -1,6 +1,7 @@ # 用 Floatboat / Selfware 机制优化 DeepCode 的整体方案 -> 状态:**提案,未实现**。等待评审后再拆 PR。 +> 状态:**已实现**(2026-08-08,PR #237 / #238 / #239 / #240 / #241 / #242 / #243 / #244)。 +> 本文保留为决策记录 —— 采纳理由、拒绝理由和未解假设的最终结论都在这里。 > 基线:`main@ec94748` · 日期 2026-08-08 > 调研依据:[`docs/research/floatboat.md`](research/floatboat.md)(另一独立 PR) > 与 [`CODEX_ALIGNMENT_PLAN.md`](CODEX_ALIGNMENT_PLAN.md) 的关系:**不取代,正交补充**。alignment plan @@ -495,6 +496,27 @@ DeepCode 已有的更强项要保留:plugin 的 ed25519 签名 + 吊销列表 合完就能回答"agent 改了什么"和"无人值守时会不会乱来"这两个最要紧的问题。 File Contract 接入(PR 2)是唯一需要谨慎评审的一步。 +### 4.1 实施后的偏差记录 + +写下与计划不符的地方,比宣称"照计划完成"有用。 + +| 项 | 计划 | 实际 | +| ---------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| PR 0 的问题陈述 | 称无人值守可能"静默放行" | **计划写错了**。`ask` 路径本来就 fail-closed(`runHeadless` 传 `approval: async () => false`)。真正缺的是"停下来"的能力和可见性,PR #237 按事实重写了范围 | +| 权限档位的钳制 | 放在 PR 0 | 推迟到 PR 7。没有 opt-in 的钳制只是破坏,等 `TriggerProfile` 落地才安全 | +| 契约 `deny` 与 `bypassPermissions` | 计划未明确 | 实施时决定 **`deny` 不可被 `bypassPermissions` 豁免**。它是关于路径的常驻声明,不是逐次提示;否则契约最强的一句话也最容易被关掉 | +| 四客户端一致性测试 | 计划要求 4 个客户端逐字段相等 | 实际只有 CLI 与 app-server **独立解析**策略;VS Code / LSP 是协议瘦客户端,逐字节消费 server 的答复,构造上即相等。测试断言前两者,并在文档里说明后两者的理由 —— 不宣称验证了 4 条独立路径 | +| Grep/Glob 结果过滤 | 列为 PR 1 的已知缺口 | 仍未做。契约对 Grep/Glob 只裁决搜索根,命中结果里混入 deny 路径的内容需要工具输出层二次过滤 | +| 制品 `provenance` | 列在 PR 8(P2) | 未做 | +| 触发源抽象(ICS / watch) | 列在 PR 8(P2) | 未做。`cron` 仍只有时间源 | + +**未解假设的最终结论**(§7 提的四个): + +- **契约默认值**:`recommended` 预设三轴全 `allow`,只 deny 秘密类路径 + 对 agent 指令 / CI / settings 用 `ask`。 +- **ledger 默认开**:是。写入极轻且不参与裁决。 +- **`/combo` 调不调模型**:结构化部分(`allowed-tools`、步骤、文件)不调;prose 可选。无模型时的产物是可用草稿而非占位符。 +- **PR 2 与 alignment plan 的先后**:alignment plan 的 runtime host 收敛已在 0.2.0 完成,因此 PR 2 直接接入既有 `dispatchToolCall`,未产生冲突。 + --- ## 5. 与现有架构的冲突与化解 diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 6b17410..0474043 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -52,12 +52,15 @@ new token → **Automation** (CI-friendly) → save as `NPM_TOKEN`. ```bash # 1. Make sure main is green and CHANGELOG.md has an entry for the new version. -# 2. Bump versions everywhere in lockstep: -# - apps/cli/package.json +# 2. Bump versions everywhere in lockstep — all SIX places: +# - packages/core/src/index.ts (VERSION — what `deepcode --version` prints) +# - apps/cli/package.json (what npm publishes) # - apps/desktop/package.json # - apps/desktop/src-tauri/tauri.conf.json # - apps/desktop/src-tauri/Cargo.toml -# (The CI workflow also re-syncs these from the tag.) +# - apps/desktop/src-tauri/Cargo.lock (CI runs `cargo check --locked`) +# (The CI workflow also re-syncs some of these from the tag.) +# `pnpm test` fails if any of them disagree — see scripts/version-consistency.test.ts. # 3. Tag + push: git tag v0.1.3 diff --git a/docs/security-model.md b/docs/security-model.md index 57cee2d..6858466 100644 --- a/docs/security-model.md +++ b/docs/security-model.md @@ -12,15 +12,40 @@ against the threat model here. DeepCode is an LLM-driven coding assistant. The threats we care about, in decreasing order of operator severity: -| # | Threat | Severity | Where mitigated | -| --- | ------------------------------------------------------------------------ | ---------------- | ----------------------------------------------------------------------- | -| 1 | Model exfiltrates DeepSeek API key (or other env secrets) via tool call | High | M3.5 sandbox + M5.1 env strip | -| 2 | Model writes arbitrary files outside the project (`/usr/bin`, `/etc`) | High | M3.5 sandbox + permissions | -| 3 | Plugin (third-party code) does either #1 or #2 | High | M5.1 subprocess + (M5.1-ext) OS sandbox | -| 4 | Hook script (third-party shell snippet) does either #1 or #2 | Medium | Exact-definition review + source trust; hook commands remain host code | -| 5 | Hostile `settings.json` field (e.g. allowRead path) injects sandbox rule | Medium | escapeSbpl() | -| 6 | Untrusted project's AGENTS.md drives the agent into harmful action | Low | Trust store (`/trust`) | -| 7 | DNS exfiltration of secrets from sandboxed Bash | Partly mitigated | M3.5-ext DNS allowlist (netns.ts) — names only; raw-IP dials still pass | +| # | Threat | Severity | Where mitigated | +| --- | ------------------------------------------------------------------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | +| 1 | Model exfiltrates DeepSeek API key (or other env secrets) via tool call | High | M3.5 sandbox + M5.1 env strip | +| 2 | Model writes arbitrary files outside the project (`/usr/bin`, `/etc`) | High | M3.5 sandbox + permissions | +| 3 | Plugin (third-party code) does either #1 or #2 | High | M5.1 subprocess + (M5.1-ext) OS sandbox | +| 4 | Hook script (third-party shell snippet) does either #1 or #2 | Medium | Exact-definition review + source trust; hook commands remain host code | +| 5 | Hostile `settings.json` field (e.g. allowRead path) injects sandbox rule | Medium | escapeSbpl() | +| 6 | Untrusted project's AGENTS.md drives the agent into harmful action | Low | Trust store (`/trust`); a file-contract `deny` is independent of model judgement, so prompt injection cannot argue past it | +| 7 | DNS exfiltration of secrets from sandboxed Bash | Partly mitigated | M3.5-ext DNS allowlist (netns.ts) — names only; raw-IP dials still pass | +| 8 | Model reads an in-project secret (`.env`, `*.pem`) through a read tool | Partly mitigated | File contract `read: deny` — covers Read/Grep/Glob, **not Bash** (see below) | +| 9 | Unattended job runs with a permissive mode inherited from interactive settings | Mitigated | Trigger profile clamp + `onApprovalRequired` | +| 10 | User cannot audit or undo what the agent wrote | Mitigated | Change ledger + `deepcode ledger rollback` through the apply ceremony | + +### Residual risk: the file contract is policy, not a boundary + +Threat #8 is **partly** mitigated, and the distinction matters more than the +mitigation. + +A [file contract](file-contract.md) constrains tool calls that pass through the +dispatcher — `Read`, `Grep`, `Glob`, `Write`, `Edit`, `NotebookEdit`. It has no +effect on what a shell command does once `Bash` has started. `cat .env` is a +string; statically parsing shell to decide otherwise would be guesswork +presented as enforcement, which is worse than not trying because it reads as a +guarantee. + +**Only the sandbox bounds Bash.** When a contract denies reads while the sandbox +resolves to `danger-full-access`, DeepCode warns at REPL start, on every headless +run, in `deepcode contract show`, and in `deepcode doctor`. Do not describe the +contract as secret protection in any user-facing text; describe it as reducing +the accidental-touch and prompt-injection surface. + +Path normalization is string math and does not call `realpath`, so a symlink +inside the workspace pointing outside still normalizes to an inside-looking +path. Same conclusion: the sandbox is the boundary. ## Defence layers diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c90c9a8..2df133b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,7 +6,7 @@ // Kept in lockstep with apps/cli/package.json by scripts/version-consistency.test.ts, // and rewritten from the tag by .github/workflows/release.yml at publish time — // before this it stayed at 0.1.0 while the CLI shipped as 0.1.6. -export const VERSION = '0.2.0'; +export const VERSION = '0.3.0'; export const PROJECT_NAME = 'DeepCode'; // Types