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