Skip to content

代码评审:session-end 每回合灌入近重复记忆记录(High)及 OpenCode 集成/打包多处问题 #1

Description

@bjw9808

一次多 agent 代码评审的汇总。方法:按 安全 / 正确性与功能完整 / 健壮性 / 打包 四个维度并行评审,再对每条功能性 bug 与 critical/high 级问题单独开 agent 做对抗式复核(刻意压制夸大,允许把初评降级或证伪)。代码基线 commit f31ed4d

总体:无 critical、无可被远程利用的安全洞;核心 hooks(hooks/_paths.js 的 workspace-id 处理:O_EXCL|O_NOFOLLOW / 0600 / 防符号链接 / race 处理)防御性较强。问题集中在 OpenCode 桥接层的功能完整性记忆图谱的数据质量

汇总

# 级别 类型 问题
1 High 功能 session-end 在每个回合的 session.idle 都写记录,记忆图谱被近重复条目灌满
2 Medium 功能 OpenCode 丢弃 session-start 召回 + signal-detect 输出,3 个宣称效果死了 2 个
3 Medium 功能 --uninstall 不移除 AGENTS.md 段落,重装累积重复
4 Medium 安全 Hub API key 作为 curl 命令行参数,本机进程表可见
5 Medium 健壮 async handler 内 spawnSync 阻塞事件循环,叠加 #1 后每回合最多阻塞 8s
6 Low–Med 安全 记忆图谱内容未转义即注入(OpenCode 下因 #2 失活;Claude Code/Cursor 下成立)
7 Low 健壮 AGENTS.md 非原子改写,崩溃可截断用户文件
8 Low 安全 curl 缺 -- 终止符(选项注入硬化)
9 Low 安全 proxy token 发往 settings.json 指定 url,无 scheme/host 校验
10 Low 打包 .opencode-plugin/plugin.json 未进 npm 包(无害)、hooks.json 为 Claude Code 残留、exports.config:{} 冗余、verify 脚本名误导

1. [High / 功能] 记忆图谱每回合被灌入近重复记录

位置:server.js:116-123hooks/session-end.js:91-99hooks/session-end.js:170-179

机制

  • OpenCode 的 session.idle每个回合 agent 转入空闲就触发一次(真正的会话结束事件是 session.deleted)。server.jsevent 处理器无条件把 session.idle 映射到 runHook('session-end.js')
  • collectDiff 比对的是 git diff --stat HEAD~1(对上一个 commit),不是未提交工作区 diff。只要工作区相对上个 commit 有差异(活跃会话常态),hasChanges 每回合恒为 true → 每回合 appendFileSync 一条 gene_id:'ad_hoc'、score 恒 0.8、summary 近乎一致的记录。
  • session-end 无任何去重/节流:alreadyEmitted 只在单进程内有效,而每次触发都是新 node 进程;recordToLocal 无条件追加。(对比 session-start 有基于文件的 throttled()。)

复核:确认,并由初评 medium 上调为 high——复核员本地实验证实,无新 commit 时 git diff HEAD~1 连续多次输出恒为非空且相同,故"diff 为空跳过"几乎不起缓解作用。

危害(克制评估):不崩溃、不涉及安全/数据丢失;是中央数据结构被系统性灌水——文件无界增长,score 聚合与跨宿主召回(Claude Code/Cursor 及 @evomap/evolver 引擎读同一张图)被偏置。

建议修复:改用 session.deleted,或在 session-end 内加按 session_id 的一次性/节流(参照 session-start 已有的 throttled())。


2. [Medium / 功能] OpenCode 桥接层丢弃 hook 输出

位置:server.js:105-138

确定事实(不依赖 OpenCode 内部实现):event(session.created)与 tool.execute.afterrunHook(...) 都是裸调用,返回值不接收、不 return、不回写 output。而 session-start.js / signal-detect.js 的有用产出正是供注入的 additionalContext(Claude Code 的 SessionStart / PostToolUse 契约)。因此 README "What it does" 表三行里:

  • session.created → Surfaces recent successful outcomes(记忆召回)— 死的
  • tool.execute.after → Detects evolution signals(信号提示)— 死的
  • session.idle → Records ... to local memory(记录)— 生效(写盘副作用)

复核:确认。由初评 high 下调为 medium——失效的是建议性上下文注入,非数据/安全;记录链路仍工作;失败是静默劣化而非崩溃。但这恰是产品主卖点("recall what worked")在 OpenCode 下不可见。(一处需修正的措辞:session-start 并非"无副作用",其 throttled() 会写 state 文件;不影响结论。)

为何漏过:test/server.test.js:54-112 用 recorder 替身只断言"三个脚本被正确调用",未断言任何输出被注入/呈现——测试止于"调用",未及"效果"。

建议修复:把 hook 的 additionalContext 接到 OpenCode 实际的注入通道,或修正 README 措辞使其与 OpenCode 实际行为一致。


3. [Medium / 功能] --uninstall 残留 AGENTS.md 段落,重装累积

位置:scripts/install.js:104-117

appendAgentsSection 写入的 marker 紧跟着就是该段自己的 ## Evolution Memory 标题;removeAgentsSectioncontent.indexOf('\n## ', idx + marker.length) 找"下一个标题",结果落在它自己的标题上,只删掉了 marker 注释行,段落正文被原样保留。于是 uninstall 后段落仍在,再次 install 又追加一份 → 累积。复核 agent 已实测复现。

建议修复:从 marker 起算段落边界,或为段落加显式结束定界符。


4. [Medium / 安全] Hub API key 经 curl 命令行参数泄露

位置:hooks/session-end.js:135-151

Authorization: Bearer ${apiKey} 作为 curl argv,在 curl 执行瞬间出现在进程参数中,本机其他用户/进程可经 ps / /proc 读取。前提:已配置 Hub 上报(EVOMAP_API_KEY 已设)且有本机旁观者。

建议修复:改用 -H @file、stdin / -K 配置文件,或用 fetch 传 header,避免密钥进 argv。


5. [Medium / 健壮] spawnSync 阻塞事件循环

位置:server.js:49-71

async handler 内用同步 spawnSync。单看有 timeout 兜底、fail-open;但叠加 #1(每回合触发 session.idle),意味着每个回合都同步阻塞最多 8s(EVOLVER_OPENCODE_SESSION_END_TIMEOUT_MS 默认 8000)。修复 #1 后压力自然缓解;也可改异步 spawn。


6. [Low–Med / 安全] 记忆图谱内容未转义即注入(分宿主)

位置:hooks/session-start.js:193-219hooks/_paths.js:100-133

formatSummary 把条目的 note / signals 原样插入 additionalContext,仅做长度截断,无转义 / 可信校验;findMemoryGraph 只要 <project>/memory/evolution/memory_graph.jsonl 存在就采用它,filterRelevant 的 status/score/时间门槛都是攻击者可控的 JSON 字段,不构成屏障。

分宿主裁定(复核)

  • OpenCode:证伪 / 无危——输出被 #2 丢弃,反被那个 bug 屏蔽。
  • Claude Code / Cursor:确认 Low–Med——需受害者克隆并打开自带恶意 memory_graph.jsonl 的仓库,注入内容是带框的过去式 [Evolution Memory] notes 块、非实时指令,无工具执行 / 数据外泄原语,量级等同"恶意 README/CLAUDE.md 进上下文"的既有环境风险,不应夸大为 high。

建议修复:对不可信记忆内容做转义 / 定界,或标注 provenance。


7–10(Low,简列)

  • 7 [健壮] AGENTS.md 用 writeFileSync 直写,非 tmp+rename(插件文件本身是原子写),崩溃可截断用户文件。scripts/install.js:100-112
  • 8 [安全] curl 参数缺 -- 终止符,若 EVOMAP_HUB_URL- 开头会被当选项。hooks/session-end.js:134
  • 9 [安全] proxy bearer token 发往 settings.json 指定的 proxy.url,无 scheme/host 校验——本机信任边界内。mcp/evolver-proxy.mjs:34-58
  • 10 [打包,均 low] .opencode-plugin/plugin.json 未列入 files、npm 发布时不打包(实测 npm pack --dry-run 确认;但 OpenCode 经 exports["./server"] 加载、不依赖它,无害)/ hooks/hooks.json 是 Claude Code 钩子格式(${CLAUDE_PLUGIN_ROOT})对 OpenCode 是死文件 / exports["./server"].config:{} 非标准冗余、建议删 / npm run verify 在纯仓库根会按设计失败、脚本名易误读。

被证伪 / 降级(透明起见)

复核刻意压制了初评的夸大,以下不计入待修问题:

  • "自带 MCP 桥(evolver_search_assets 等)在 OpenCode 不可用" — 证伪。README 明确把 mcp/evolver-proxy.mjs 限定为 "ships ... for MCP clients that can consume it",SKILL.md 用 "bundles" / "add it to your MCP config if you want it" — 从未宣称本插件在 OpenCode 自动接入。至多文档可加一句澄清。
  • "自带 skill 未被 OpenCode 加载" — 证伪。客观事实对,但从未宣称会加载;skills/ 列入 files 只是跨宿主复用同一份内容。
  • #2 由 high 降 medium、#6 在 OpenCode 宿主由"风险"降为"无危",理由见上。

建议修复优先级

  1. 代码评审:session-end 每回合灌入近重复记忆记录(High)及 OpenCode 集成/打包多处问题 #1 — 改 session.deleted 或加节流。每回合都在污染中央数据结构,修了它 #5 压力也消。
  2. #2 — 补注入通道或改文档,关系到插件在 OpenCode 是否名副其实。
  3. #3、#4 — 干净利落的小修。
  4. 其余 low 可一并清理(尤其 exports.config:{} 与把 hooks.json 排除出 OpenCode 包)。

评审基线 commit f31ed4d。严重性为对抗式复核后的校准值,非初评原值。本 issue 由多 agent 评审流程生成,人工复核后提交。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions