diff --git a/CHANGELOG.md b/CHANGELOG.md index fd3c791..829d461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,70 @@ ## [未发布] +### 修复 —— selfupdate 在境内**必然**失败(P0-1:裸 `fetch` 不读代理) + +真机症状:被控端自更新下载 `fetch failed`。根因不是网络差 —— Node 的 `fetch`(undici) +**不读** `HTTPS_PROXY` 等环境变量,而境内访问 GitHub 必经代理。 + +- 新增 `apps/agent/src/net/proxy.ts` + `net/index.ts`:sidecar 已验证出网方案的 TS 移植。 + 代理来源优先级 `NA_PROXY` → 目标协议环境变量 → 系统代理(Windows reg query WinINET / + macOS `scutil --proxy`);PAC/socks5 **明确报 note 不静默降级**;重定向链**共享超时预算** + (防 5 跳×30s 穿过 RPC 超时);CONNECT 隧道 abort 自管(外层 request 建立前 signal 无人 + 受理 —— 真机 75s 配置挂 109s 的教训);TLS 默认校验证书。只依赖 node 内置模块, + 不 import 项目内任何模块。 +- `httpGet` 增 `maxBytes` 双闸(content-length 预检 + 流式中断),保住「64MB 上限」。 +- 错误分类同款移植 sidecar `main.mjs:146-165`:`ECONNRESET` 不得报成「请求超时」 + (真机教训 —— 报错类型错会把人带去排查完全不相干的方向)。 +- **回环目标恒直连**(有意的设计偏离):代理另一侧的 localhost 不是本机 —— CI/dev 机 + 设了 `HTTP_PROXY` 时,发往 `127.0.0.1` 自建镜像的请求会被送进远程代理而 404。 +- 备份保留最近 3 个(`planBackupCleanup` 纯函数,最旧先删)。 +- 安全属性逐条不变:sha256 强制校验 / 先校验后落盘 / 写后校验失败回滚 / 就地覆写不用 + rename(Windows)/ 只接受 http(s)。 + +### 变更 —— LKG 补内容指针(A-P0-3),rollback 有确定目标 + +原 LKG 只有 12 位指纹,没有「备份文件长什么样」的记述,rollback 时只能猜。 + +- `LkgRecord` 只增两字段:`hash_full`(sha256 全量 64 位)与 `backup_glob`(`.bak*`); + 老记录无字段时原样通过(向后兼容)。 +- 新增 `lkgHashMatches`:有 `hash_full` 按全量比,老记录回落 12 位前缀,两侧都无值 + **不猜**(返回 false)。 +- `system.audit.l2` 的 `returns_schema` 从空壳补全为四 action 属性并集; + `system.agent.update` returns 增 `via/proxy/proxy_source/outbound_notes` 四个只增字段 + (失败时能看出「走了代理还是直连、用的哪个代理源」)。 + +### 修复 —— 架构审查三个 P0(静态审查发现,均补「注入即红」守卫) + +- **A-P0-1 validate 原型链绕过**:`key in schema.properties` 沿原型链,JSON 里写 + `"constructor"`/`"__proto__"` 会被判「schema 已声明」从而跳过拒绝(fail-open, + 与已修的 ACL B-03 同源复发)。改 `Object.prototype.hasOwnProperty.call`。 +- **A-P0-2 限速桶 key 用错值**:原实现把**限速值**当桶名(`clientId|5`)—— 两条同值 + 规则的能力共用一个桶,互相挤占/漏过;且桶只增不删 + psk 模式 client_id 自报 → + 可枚举造键 → 内存无界增长。改为 `${认证身份 ?? 远端地址}:${能力名}` + 过期键清扫 + + 容量上限淘汰最旧(滑动窗口边界与旧实现逐字节一致)。 +- **A-P0-4 describeArgs 大参数阻塞事件循环**:原实现每次 invoke 全量 + `JSON.stringify` + sha256,`fs.write` 的 data 可达数 MB —— 同步序列化阻塞期间 + GUI 注入、事件推送、保活 ping 全部延迟。改为超过 64KB 只哈希前 64KB(按**字节**切), + 总长记 `args_bytes`、截断记 `args_truncated`。口径变化只影响审计摘要, + 不影响任何安全属性(摘要用途是比对与防泄密,不是完整性证明)。 + +### 新增 —— L2 三端暴露(MCP `na_audit_l2` + 控制台 L2 分区 + `agent.l2` 审计类型) + +决策「L2 同时暴露 MCP+控制台」。此前 `system.audit.l2` 只存在于被控端实现里: +MCP 无工具(模型看不到)、控制台无入口无类型(用户点不到)、审计事件写成 +`agent.start`(与进程生命周期混为一类,筛不出来)。 + +- **MCP**(41 → 42 个工具):`na_audit_l2` 紧随 `na_audit`,四 action + (status/enable/collect/verify)+ since/limit,映射 `system.audit.l2`。 +- **控制台**:AuditView 新增 L2 卡片(查状态/拉取采集/校验 L2 链/启用策略 —— + enable 有二次确认并写明需管理员权限);非 Windows 被控端按钮禁用并明示原因; + 信任边界如实说明(采集器与 agent 同权限,防篡改依赖**转发出机**,不夸大本地副本)。 +- **`agent.l2` 审计类型四端同源**:protocol `AUDIT_TYPES`(真源)→ agent 联合 + (AssertExact 编译期守卫)→ 写入点 → 控制台联合。 +- 顺手修了两个守卫盲区:MCP 工具名正则 `na_[a-z_]+` 匹配不了带数字的名字 + (`na_audit_l2` 会整个漏扫);平台门控守卫原只扫 app.ts/system.ts 两种写法, + audit-l2.ts 的裸 throw 扫不到 —— 已改用统一 `assertWindows` 并纳入扫描范围。 + ### 新增 —— L2 系统级审计兜底(`7d781d1`,`docs/AUDIT-L2.md`) L1 是 agent 自己记的,只看得见经过自己 RPC 的调用;攻击者**绕过 agent 直接操作机器、 @@ -25,7 +89,8 @@ L1 是 agent 自己记的,只看得见经过自己 RPC 的调用;攻击者** `verify`(校验 L2 链,与 L1 同构的「对含 prev 的记录求哈希」)。 - **三端现状如实记录**:Linux(auditd)需预装、macOS(ESF)受 Apple entitlement 限制,均**未实现** —— 非 Windows 被控端明确报 `E_UNSUPPORTED_PLATFORM`, - 不伪装成空结果。CLI 已有 `nodeagent audit l2` 入口。 + 不伪装成空结果。CLI 已有 `nodeagent audit l2` 入口;v2.1.0 起 MCP 与 + 控制台亦暴露(见上方「L2 三端暴露」)。 ### 新增 —— 交付流水线规范落地(`2bf45fd`,`docs/DELIVERY-PIPELINE.md`) @@ -42,7 +107,11 @@ Prometheus → `system.metrics` + 发布后复核清单。 - 覆盖率门禁 `scripts/ci/check-coverage.mjs`:全量**只观测不阻断**(实测基线 66.9%), 变更文件行覆盖 ≥70% 阻断。 - **LKG 回滚锚点**:被控端「能启动」后记最后已知可用版本(`agent-lkg.json`), - 给回滚提供确定目标;**刻意不做自动回滚**(无法归因、回滚循环、失控 —— 决策权留给控制端)。 + 给回滚提供确定目标。**回滚策略分两档,边界写死**: + **启动期自动恢复已做**(看门狗:启动连续失败 N 次 → 从 LKG 恢复, + `scripts/startup-guard.mjs`,Windows 计划任务 / Linux systemd / macOS launchd + 三端 supervisor 已接入);**运行期自动回滚仍刻意不做**(更新后健康检查失败即回滚 + 无法归因、有回滚循环风险、失控 —— 决策权留给控制端,`nodeagent rollback`)。 ### 新增 —— Linux 被控端适配(`00992e5`,`docs/DEPLOY-LINUX.md`)