一次多 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-123 、hooks/session-end.js:91-99 、hooks/session-end.js:170-179
机制
OpenCode 的 session.idle 是每个回合 agent 转入空闲就触发一次 (真正的会话结束事件是 session.deleted)。server.js 的 event 处理器无条件把 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.after 对 runHook(...) 都是裸调用 ,返回值不接收、不 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 标题;removeAgentsSection 用 content.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-219 、hooks/_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 宿主由"风险"降为"无危",理由见上。
建议修复优先级
代码评审:session-end 每回合灌入近重复记忆记录(High)及 OpenCode 集成/打包多处问题 #1 — 改 session.deleted 或加节流。每回合都在污染中央数据结构,修了它 #5 压力也消。
#2 — 补注入通道或改文档,关系到插件在 OpenCode 是否名副其实。
#3、#4 — 干净利落的小修。
其余 low 可一并清理(尤其 exports.config:{} 与把 hooks.json 排除出 OpenCode 包)。
评审基线 commit f31ed4d。严重性为对抗式复核后的校准值,非初评原值。本 issue 由多 agent 评审流程生成,人工复核后提交。
汇总
session-end在每个回合的session.idle都写记录,记忆图谱被近重复条目灌满session-start召回 +signal-detect输出,3 个宣称效果死了 2 个--uninstall不移除 AGENTS.md 段落,重装累积重复spawnSync阻塞事件循环,叠加 #1 后每回合最多阻塞 8s--终止符(选项注入硬化).opencode-plugin/plugin.json未进 npm 包(无害)、hooks.json为 Claude Code 残留、exports.config:{}冗余、verify脚本名误导1. [High / 功能] 记忆图谱每回合被灌入近重复记录
位置:
server.js:116-123、hooks/session-end.js:91-99、hooks/session-end.js:170-179机制
session.idle是每个回合 agent 转入空闲就触发一次(真正的会话结束事件是session.deleted)。server.js的event处理器无条件把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.after对runHook(...)都是裸调用,返回值不接收、不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-117appendAgentsSection写入的 marker 紧跟着就是该段自己的## Evolution Memory标题;removeAgentsSection用content.indexOf('\n## ', idx + marker.length)找"下一个标题",结果落在它自己的标题上,只删掉了 marker 注释行,段落正文被原样保留。于是 uninstall 后段落仍在,再次 install 又追加一份 → 累积。复核 agent 已实测复现。建议修复:从 marker 起算段落边界,或为段落加显式结束定界符。
4. [Medium / 安全] Hub API key 经 curl 命令行参数泄露
位置:
hooks/session-end.js:135-151Authorization: Bearer ${apiKey}作为 curl argv,在 curl 执行瞬间出现在进程参数中,本机其他用户/进程可经ps//proc读取。前提:已配置 Hub 上报(EVOMAP_API_KEY已设)且有本机旁观者。建议修复:改用
-H @file、stdin /-K配置文件,或用fetch传 header,避免密钥进 argv。5. [Medium / 健壮]
spawnSync阻塞事件循环位置:
server.js:49-71async 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-219、hooks/_paths.js:100-133formatSummary把条目的note/signals原样插入additionalContext,仅做长度截断,无转义 / 可信校验;findMemoryGraph只要<project>/memory/evolution/memory_graph.jsonl存在就采用它,filterRelevant的 status/score/时间门槛都是攻击者可控的 JSON 字段,不构成屏障。分宿主裁定(复核)
memory_graph.jsonl的仓库,注入内容是带框的过去式[Evolution Memory]notes 块、非实时指令,无工具执行 / 数据外泄原语,量级等同"恶意 README/CLAUDE.md 进上下文"的既有环境风险,不应夸大为 high。建议修复:对不可信记忆内容做转义 / 定界,或标注 provenance。
7–10(Low,简列)
writeFileSync直写,非 tmp+rename(插件文件本身是原子写),崩溃可截断用户文件。scripts/install.js:100-112--终止符,若EVOMAP_HUB_URL以-开头会被当选项。hooks/session-end.js:134proxy.url,无 scheme/host 校验——本机信任边界内。mcp/evolver-proxy.mjs:34-58.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在纯仓库根会按设计失败、脚本名易误读。被证伪 / 降级(透明起见)
复核刻意压制了初评的夸大,以下不计入待修问题:
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 自动接入。至多文档可加一句澄清。skills/列入files只是跨宿主复用同一份内容。建议修复优先级
session.deleted或加节流。每回合都在污染中央数据结构,修了它 #5 压力也消。exports.config:{}与把hooks.json排除出 OpenCode 包)。评审基线 commit
f31ed4d。严重性为对抗式复核后的校准值,非初评原值。本 issue 由多 agent 评审流程生成,人工复核后提交。