From a7827ba9869b97af81143f0fa781af0533678b8f Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Mon, 26 Oct 2026 01:01:32 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=A2=9E=E9=87=8F=20PRD=20/=20ARCH=20?= =?UTF-8?q?=E4=B8=8E=E8=AE=BE=E8=AE=A1=E5=9B=BE=E5=85=A5=E5=BA=93=EF=BC=88?= =?UTF-8?q?v2.1.0=20=E5=85=A8=E9=9D=A2=E5=AE=8C=E5=96=84=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本轮「全面完善」的四份设计产出此前后在 git 外(untracked)—— 阶段 3 实现所依据的需求/架构/类图/时序图随收口一并入库, 使「代码 ← 设计」的引用链在仓库内闭环(代码注释已按 ARCH 任务号引用)。 --- ...CH-2026-10-25-comprehensive-improvement.md | 570 ++++++++++++++++++ ...RD-2026-10-25-comprehensive-improvement.md | 194 ++++++ docs/class-diagram.mermaid | 68 +++ docs/sequence-diagram.mermaid | 21 + 4 files changed, 853 insertions(+) create mode 100644 docs/ARCH-2026-10-25-comprehensive-improvement.md create mode 100644 docs/PRD-2026-10-25-comprehensive-improvement.md create mode 100644 docs/class-diagram.mermaid create mode 100644 docs/sequence-diagram.mermaid diff --git a/docs/ARCH-2026-10-25-comprehensive-improvement.md b/docs/ARCH-2026-10-25-comprehensive-improvement.md new file mode 100644 index 0000000..df8c589 --- /dev/null +++ b/docs/ARCH-2026-10-25-comprehensive-improvement.md @@ -0,0 +1,570 @@ +# ARCH-2026-10-25:nodeagent「全面完善」—— 代码/架构审查 + 增量设计 + 任务分解 + +> **文档性质**:阶段 2 产出(架构师高见远)。基线 `3680218`(v2.0.3)。 +> 范围权威定义:`docs/PRD-2026-10-25-comprehensive-improvement.md`(需求方已批准:P0+P1 全做、P2 推后、版本 v2.1.0、selfupdate 出网走方案 A、L2 同期限 MCP+控制台)。 +> 工程铁律来源:项目记忆(契约三段 / 错误规范 / 边界 / Windows 陷阱 / 守卫铁律 / LKG 时机 / 审计哈希口径 / 建文件禁 git add -A / 播种兼容 / git push 代理)。 +> **所有结论均有 `文件:行号` 证据**;凡未找到证据的一律进「待确认」不写成事实。 + +--- + +## Part 0:审查方法与基线事实 + +### 0.1 审查范围(实读文件) + +| 区域 | 文件 | +|---|---| +| 被控端 | `apps/agent/src/index.ts`、`server.ts`、`audit.ts`、`lkg.ts`、`config.ts`、`events.ts`、`discovery.ts`、`hub-mode.ts`、`capabilities/{index,system,selfupdate,audit-l2,fs,task,monitor,input,window,screen,record,clipboard,app,network,audio,log,await}.ts`、`util/{exec,ps-helper,quote,dpi,platform}.ts` | +| 协议 | `packages/protocol/src/{validate,acl,errors,messages,methods,net,ed25519,hmac,hotkeys,ulid,audit-types,audit-anchor}.ts`、`capabilities/{names,schemas}.ts`、`capabilities/manifest/*.ts` | +| 控制端 | `apps/cli/src/{index,options,core,help,types}.ts`、`cmd/*.ts`;`apps/mcp/src/index.ts`、`tools/defs.ts`;`apps/console/src/**`、`src-tauri/src/agent.rs`(节选);`sidecar/{main,proxy}.mjs` | +| 脚本/测试 | `scripts/lib/version-write.mjs`、`scripts/uninstall.ps1`、`install.ps1`(节选);`tests/unit/*`(43 个)、`tests/e2e/{run,windows}.mjs`(节选) | + +### 0.2 基线实测(复扫确认,PRD §1.1 数据属实) + +| 项 | 值 | 取证 | +|---|---|---| +| 能力数 | 52 | manifest 实点(51 + `system.audit.l2`,`names.ts:53`) | +| MCP 工具 | 41 | `apps/mcp/src/tools/defs.ts` 实点(`mcp-tools.test.mjs:25` 断言 41) | +| 单测 | 357 | PRD 实跑记载(本轮未重跑,以 PRD 为基线) | +| e2e | 33 + Windows 13 | `tests/e2e/run.mjs` + `windows.mjs` | +| L2 在 MCP/控制台 | 0 命中 | `apps/mcp/src grep l2` = 0;`apps/console/src grep l2` = 0 | +| CLI rollback | 不存在 | `apps/cli/src grep rollback` 仅 net.ts 类型注释;`index.ts` 无 case | +| L2 returns_schema | 空壳 | `manifest/system.ts:321` `returns_schema: { type: 'object' }` | +| selfupdate 裸 fetch | 存在 | `selfupdate.ts:42` | + +--- + +## Part A:代码/架构审查报告(问题清单) + +> 严重度定义:**P0 = 安全/正确性缺陷**(含 fail-open 守卫缺陷、可无线利用的崩溃/误伤);**P1 = 重要缺口**(功能缺口、观测污染、性能热点、可维护性硬伤);**P2 = 优化**。 +> 「新」= PRD 需求池之外本轮新发现。 + +### A.1 P0(4 条新发现 + 2 条对 PRD 已知项的补充证据) + +#### A-P0-1(新)参数校验的 `additionalProperties:false` 可被原型键绕过 —— fail-open 方向 + +- **证据**:`packages/protocol/src/validate.ts:69` `if (!(key in schema.properties))`。 +- **机理**:`in` 运算符沿原型链查找。`schema.properties` 是普通对象字面量,继承 `Object.prototype`。调用方传入 `{"constructor": 1}`、`{"toString": 1}`、`{"valueOf": 1}` 等键时,`key in properties` 为 `true` → **不判 additional property** → 校验放行。 +- **触发条件**:任何已认证调用方对**任何**能力传上述键(manifest 大量能力声明了 `additionalProperties: false`,如 `manifest/system.ts:24`)。 +- **现状危害评估**:当前各 handler 均按名读取已知键,多余键被忽略,**暂无直接可利用路径**;但这是「fail-open 方向的守卫缺陷」—— 与本仓刚修过的 B-03 是**同一错误模式在不同文件复发**:`server.ts:228` 已用 `Object.prototype.hasOwnProperty.call(cfg.keys, clientId)` 修过「`client_id:"constructor"` 沿原型链取到函数当密钥打死进程」。 +- **修复方向**:改为 `Object.prototype.hasOwnProperty.call(schema.properties, key)`;补单测「传 `constructor` 键必须被判 additional property」(守卫要会红:注入故障 = 改回 `in`,测试必须失败)。 + +#### A-P0-2(新)限速桶 key 用「限速值」而非「能力名」—— 同上限值的能力互相挤占;桶只增不删 + +- **证据**:`apps/agent/src/server.ts:364` `allowByRate(state.clientId ?? '', rateLimit, String(rateLimit ?? ''))` —— 第三参 `bucket` 传的是 `String(rateLimit)`(限速值本身);`server.ts:101-113` 桶内只过滤当次 60s 窗口,**key 永久留存**(`rateBuckets` Map 无任何清扫)。 +- **机理一(正确性)**:ACL 支持按能力限速(`rateLimitFor(clientRule, name)`,精确 > glob > 全局)。当某 client 配了「screen.capture ≤ 5/min、app.install ≤ 5/min」两条同值规则时,两者 key 都是 `clientId|5` → **共用一个桶**:截图 5 次后装软件也被限速(误伤),且两者总量仍按 5/min(漏过)。 +- **机理二(可用性/内存)**:psk 模式下 `client_id` 由客户端自报(`server.ts:295` 注释自陈「配置级约束」)→ 外部可枚举任意 client_id 发起调用 → `rateBuckets` 键无界增长(每个键一个数组,长期运行即内存泄漏);且没有限速配置时 `rateLimit` 为 undefined → `String(undefined)` = `"undefined"` → 所有无差别调用共用一个 key(此时 `allowByRate` 第一行 `if (!limitPerMin) return true` 直接放行,不写桶,故只在前一种情况受害)。 +- **修复方向**:key 改 `${clientId}|${name}`(能力名);加桶清扫(每次 `allowByRate` 顺手删过期键,或 5 分钟定时);给桶总数设上限(如 10_000,超出淘汰最旧)。单测:同值两能力互不干扰;过期键被清。 + +#### A-P0-3(新,PRD P0-2 的前提缺口)LKG 只有指纹没有「内容指针」,且 selfupdate 备份永不清理 + +- **证据**:`apps/agent/src/lkg.ts:34-44` `LkgRecord` 只有 `hash`(12 位)/`bytes`/`script_path`/`recorded_at`/`node_version`/`platform` —— **没有任何字段指向「这个版本的内容在哪」**。而 `docs/DELIVERY-PIPELINE.md:233` 承诺「`nodeagent rollback` 读 LKG → 覆盖回入口 → 重启」却从未定义内容来源。 +- **备份现状(两套命名、一个不清理)**: + - 推送式 deploy:固定名 `.bak`,每次部署**覆盖**(`apps/cli/src/cmd/ops.ts:279`,注释记载「真机堆了 15+ 个 bak」后改覆盖); + - 拉取式 selfupdate:`