Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ We actively maintain the latest minor version. Security fixes are backported to

| Version | Supported | Status |
|---|---|---|
| 0.9.x | ✅ Yes | Active development |
| 0.8.x | ⚠️ Best-effort | Critical fixes only |
| < 0.8.0 | ❌ No | Please upgrade to 0.9.x |
| 0.10.x | ✅ Yes | Active development |
| 0.9.x | ⚠️ Best-effort | Critical fixes only |
| < 0.9.0 | ❌ No | Please upgrade to 0.10.x |

**End-of-life notice**: 0.8.x is in best-effort maintenance (critical fixes only). Its end-of-life date will be announced in advance; after that date no security fixes will be provided for 0.8.x. Security support for 0.7.x and below is no longer provided — please upgrade to 0.9.x.
**End-of-life notice**: 0.9.x is in best-effort maintenance (critical fixes only). Its end-of-life date will be announced in advance; after that date no security fixes will be provided for 0.9.x. Security support for 0.8.x and below is no longer provided — please upgrade to 0.10.x.

---

Expand Down Expand Up @@ -86,7 +86,7 @@ The plugin can optionally start its own HTTP server (`externalApiEnabled`, defau
**Out of scope** — we will not act on:
- Social engineering or phishing against maintainers or users
- DoS claims that require unrealistic local resource exhaustion with no product defect
- Vulnerabilities present only in unsupported versions (`< 0.8.0`)
- Vulnerabilities present only in unsupported versions (`< 0.9.0`)
- Best-practice hardening suggestions with no demonstrable security impact

Not sure whether your finding is in scope? Report it anyway via the private channel — we will triage and respond.
Expand Down Expand Up @@ -396,11 +396,11 @@ This project is licensed under the **MIT License**. See [LICENSE](https://github

| 版本 | 支持状态 | 说明 |
|---|---|---|
| 0.9.x | ✅ 支持 | 活跃开发中 |
| 0.8.x | ⚠️ 尽力维护 | 仅关键修复 |
| < 0.8.0 | ❌ 不支持 | 请升级至 0.9.x |
| 0.10.x | ✅ 支持 | 活跃开发中 |
| 0.9.x | ⚠️ 尽力维护 | 仅关键修复 |
| < 0.9.0 | ❌ 不支持 | 请升级至 0.10.x |

**停止维护通知**:0.8.x 处于尽力维护阶段(仅关键修复)。停止维护的具体日期将提前公布;该日期之后将不再提供 0.8.x 的安全修复。0.7.x 及更早版本的安全支持已终止——请升级至 0.9.x。
**停止维护通知**:0.9.x 处于尽力维护阶段(仅关键修复)。停止维护的具体日期将提前公布;该日期之后将不再提供 0.9.x 的安全修复。0.8.x 及更早版本的安全支持已终止——请升级至 0.10.x。

---

Expand Down Expand Up @@ -472,7 +472,7 @@ This project is licensed under the **MIT License**. See [LICENSE](https://github
**不受理范围**——以下问题我们不予处理:
- 针对维护者或用户的社工 / 钓鱼类问题
- 仅通过不现实的本地资源耗尽触发、且无产品缺陷支撑的 DoS 声明
- 仅存在于不支持版本(`< 0.8.0`)的问题
- 仅存在于不支持版本(`< 0.9.0`)的问题
- 无可证明安全影响的"最佳实践建议"类问题

不确定你的发现是否在受理范围内?仍请通过私有渠道提交——我们会评估并回复。
Expand Down
11 changes: 10 additions & 1 deletion dsh-mneme/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [Unreleased]

## [0.10.0] - 2026-11-02

## 🆕 新增

- **溯源读出口补上反方向:`GET /memory/provenance?sessionId=<sid>`——「这个会话蒸出了哪几个窗口」第一次可直接查,此前只能「从一条记忆往回找」(#524,从 #421 拆出方向 2)**:#421 当初拆成两个方向,方向 1(记忆 → 窗口 → `distill_records` 原文)由 #509 交付,方向 2(会话 → 它产出的蒸馏窗口)一直没做。底下其实早就齐了——`memory_provenance` 自 #399 就在写、`idx_memory_provenance_session` 自那时就建着——缺的只是入参方向:`parseProvenanceQuery` 只认 `?id=<memoryId>`,`session_id` 只在响应里躺着。**复用同一条路径而不是新开一条**:多一条路径就多四处要登记的地方(`SECURITY.md` 两张表、包 README 路由表、`test/security-doc.test.js` 的清单锁),而两个方向读的是同一批数据、只是入口不同;`SECURITY.md` 只登记路径不登记 query 形状,所以方向 2 在那两张表里一行都不用加。两个主键**互斥**:都缺 `400 missing-id`、都给 `400 ambiguous-id`——**刻意不做「都给则择一」**,择一意味着调用方可以发一个自己也不知道会被读哪个主键的请求,而两个方向的答复形状不同,那种静默选择只会在出事之后才被发现。
Expand Down Expand Up @@ -32,7 +34,7 @@

回归测试 +9(`test/session-gate.test.js`:默认关 / `parentSession` 单独不判 / 手动名单与子代理开关互相独立且配置活读 / 无 id 子代理照判 / 记账有界淘汰 / 四个注入面为空 / 全部工具在副作用前拒绝 / 热重载后旧执行器跟随新闸门 / 蒸馏早退)与 +2(`test/pipeline-nudge.test.js`:fork 主会话的观测不许被滤掉、非 session 首行不许当会话头)。变异自证:把 `parentSession` 判据加回去 → fork 那两条红;把 `if (!id) return false` 加回去 → 无 id 那条红;把 `?? row?.data ?? row` 加回去 → 非 session 首行那条红。

- **蒸馏与总览的提示词第一次对「谄媚」开口:写入侧补「立场纪律」与「叙述纪律」(#530,承接 #380)**:`preInjectGate` 把防谄媚做在**注入面**(E12 实测谄媚率 26.9% → 2.5%),代价是它只是最后一道门、而且默认关;而落到库里**之前**的两个写作环节——蒸馏(`src/lang.js` 的 `summary` / `codingSummary`)与总览(`dreamSummary`)——对谄媚一字未提。这两处写出的内容**完全不过** `writeAdmission`(`src/write-admission.js:285` 的 `if (!sessionKey) return verdict;`,而 `sessionKey` 唯一来源是 `src/tools.js:187` 的 `memory_save` 路径,蒸馏与 dream 写入都不带),所以提示词是唯一能施加约束的地方。**① 立场纪律**(`summary` 与 `codingSummary` 中英各一份)四条:只记事实/偏好/决策/约束、不记对用户本人性格能力情绪品行的评价(无论褒贬);转录三种角色可信度不同——`用户:` 是用户陈述,`助手:` 与 `子会话交付:` 的结论是推断不是事实,除非用户明确采纳否则不写成记忆,确需保留要在 content 里注明是推断;用户陈述与助手推断冲突时以用户陈述为准、冲突如实写进 content 而不替用户裁决;`importance` 只看事实对后续工作的影响面,用户的满意/赞许/情绪都不是抬高它的理由。开头那句「**与上条原则冲突时以本条为准**」不是修辞:「偏好照常记录」与「不记评价」表面有张力,没有优先级条款模型会靠丢弃合法偏好来解张力。**② 叙述纪律**(`dreamSummary` 中英各一份):只写记忆里已写明的事实与偏好,不给用户添性格/能力/情绪评价,不把助手自己的判断写成「用户认为」,某条记忆本身带褒贬口气时只取事实内核。放它的理由是总览是个**放大器**——`src/dream.js:1562` 的落库判据只有 `summaryText.trim()`(无长度、形状、内容判据),`:1568` 固定 `importance:5` + `type:"summary"`,于是 `src/service.js:1696-1706` 进 priority 0 最高注入档、且 `type=summary` 在 `service.js:1665-1671` 的池过滤里**跳过 importanceThreshold**:池里一条谄媚条目会被复述进总览、常驻注入、每轮都见。**两处都必须插在字面量内部且排在 JSON 输出形状之前**——`src/summarize.js:825` 在提示词之后拼 `STR.distillTimeAnchor`(「据此换算 occurred_at」),规则接在锚点之后就变成「先按时间换算、再谈记什么」,指令优先级反了。**规则 b 里那半句「工具输出、报错、命令结果直接给出的技术结论豁免」不能删**:`pitfall` / `constraint` 的内容本来就是工具给出的技术结论,没有豁免就等于把编码回溯功能一并否掉,这是全文最容易被后人顺手删掉的高危句,已单独立锁。**不加配置开关**:仓库既有的 opt-in 开关没有一个是否提示词文本键(全是「要不要跑某条路径」),本批零新增 LLM 调用、零新增配置键,加开关要连带白名单 + schema + 旗标计数锁,而默认关 = 默认无保护,恰好放弃要防的东西——先例是 v0.9.3 的转录围栏(#493)无条件上线。**`preInjectGate` 的池刻意不套围栏**(本轮只补一条「为什么」注释):它的 system 契约把 user 形状写死成「你将收到一个 JSON 对象」,套围栏会让提示词与载荷互相矛盾,而判定池靠 `JSON.parse` 取 `m#i` 局部序号;`JSON.stringify` 本就结构性转义引号与花括号(实测 roundtrip 一致),围栏反而让伪造闭合标记从 1 次变 2 次——不增加防护只增加攻击面,该路径的真防线是整池 wipeout fail-open 加「判定结果绝不进模型上下文」。**明确不在本批**(写进 issue 与本文,别读成「防谄媚已闭环」):巩固 merge 会整段重写正文(`src/dream/decisions.js:627-629` 走 `service.update`),而护栏类型表 `ARCHIVE_GUARDED_TYPES` 不含 `project` / `decision` / `history` / `summary`——正是「对用户的评价」最常落的类型,谄媚在那里从「搬运」升级成「改写放大」且源条目随即归档;`consolidation` 的「合并后取最高」、`conflict` 的「更可信」判据、`pattern` 从夸赞炼人格三处同类;`importance` 单调不减是代码保证(`src/service.js:1443` 的 `Math.max`),规则 d 救不回已被抬到 5 的存量行;给 `quality-filter` 加确定性立场信号这条路是死的(`service.js:1598` 的 `importanceExempt >= 4` 正好豁免谄媚最可能落的那一档);`codingSummary` 缺 type 枚举锁是既有缺陷(`summarize.js:100` 的 `VALID` 只认 7 个 type,自造分类会被静默丢弃且失败路径不推进游标导致同窗口重放),建议独立小 PR。**提示词测试只锁形状不锁行为**:`test/prompt-stance.test.js` 能断言「文本里有这条约束、位置对」,锁不了「模型是否遵守」——后者要 LLM 在环加预注册判据(E12 那类),本轮不做,**不得把本批写成「已验证降低谄媚率」**。新用例 6 条:中英成对、位置锁、工具豁免条款在场、散文契约不破(`doesNotMatch(/JSON 数组|JSON array/)`,总览要的是散文)、总览落库判据不放宽(空串仍 `summary:false` 且旧总览存活)。改动只碰提示词字面量与一条注释,既有锁一条未改。
- **蒸馏与总览的提示词第一次对「谄媚」开口:写入侧补「立场纪律」与「叙述纪律」(#530,承接 #380)**:`preInjectGate` 把防谄媚做在**注入面**(实测谄媚率 26.9% → 2.5%),代价是它只是最后一道门、而且默认关;而落到库里**之前**的两个写作环节——蒸馏(`src/lang.js` 的 `summary` / `codingSummary`)与总览(`dreamSummary`)——对谄媚一字未提。这两处写出的内容**完全不过** `writeAdmission`(`src/write-admission.js:285` 的 `if (!sessionKey) return verdict;`,而 `sessionKey` 唯一来源是 `src/tools.js:187` 的 `memory_save` 路径,蒸馏与 dream 写入都不带),所以提示词是唯一能施加约束的地方。**① 立场纪律**(`summary` 与 `codingSummary` 中英各一份)四条:只记事实/偏好/决策/约束、不记对用户本人性格能力情绪品行的评价(无论褒贬);转录三种角色可信度不同——`用户:` 是用户陈述,`助手:` 与 `子会话交付:` 的结论是推断不是事实,除非用户明确采纳否则不写成记忆,确需保留要在 content 里注明是推断;用户陈述与助手推断冲突时以用户陈述为准、冲突如实写进 content 而不替用户裁决;`importance` 只看事实对后续工作的影响面,用户的满意/赞许/情绪都不是抬高它的理由。开头那句「**与上条原则冲突时以本条为准**」不是修辞:「偏好照常记录」与「不记评价」表面有张力,没有优先级条款模型会靠丢弃合法偏好来解张力。**② 叙述纪律**(`dreamSummary` 中英各一份):只写记忆里已写明的事实与偏好,不给用户添性格/能力/情绪评价,不把助手自己的判断写成「用户认为」,某条记忆本身带褒贬口气时只取事实内核。放它的理由是总览是个**放大器**——`src/dream.js:1562` 的落库判据只有 `summaryText.trim()`(无长度、形状、内容判据),`:1568` 固定 `importance:5` + `type:"summary"`,于是 `src/service.js:1696-1706` 进 priority 0 最高注入档、且 `type=summary` 在 `service.js:1665-1671` 的池过滤里**跳过 importanceThreshold**:池里一条谄媚条目会被复述进总览、常驻注入、每轮都见。**两处都必须插在字面量内部且排在 JSON 输出形状之前**——`src/summarize.js:825` 在提示词之后拼 `STR.distillTimeAnchor`(「据此换算 occurred_at」),规则接在锚点之后就变成「先按时间换算、再谈记什么」,指令优先级反了。**规则 b 里那半句「工具输出、报错、命令结果直接给出的技术结论豁免」不能删**:`pitfall` / `constraint` 的内容本来就是工具给出的技术结论,没有豁免就等于把编码回溯功能一并否掉,这是全文最容易被后人顺手删掉的高危句,已单独立锁。**不加配置开关**:仓库既有的 opt-in 开关没有一个是否提示词文本键(全是「要不要跑某条路径」),本批零新增 LLM 调用、零新增配置键,加开关要连带白名单 + schema + 旗标计数锁,而默认关 = 默认无保护,恰好放弃要防的东西——先例是 v0.9.3 的转录围栏(#493)无条件上线。**`preInjectGate` 的池刻意不套围栏**(本轮只补一条「为什么」注释):它的 system 契约把 user 形状写死成「你将收到一个 JSON 对象」,套围栏会让提示词与载荷互相矛盾,而判定池靠 `JSON.parse` 取 `m#i` 局部序号;`JSON.stringify` 本就结构性转义引号与花括号(实测 roundtrip 一致),围栏反而让伪造闭合标记从 1 次变 2 次——不增加防护只增加攻击面,该路径的真防线是整池 wipeout fail-open 加「判定结果绝不进模型上下文」。**明确不在本批**(写进 issue 与本文,别读成「防谄媚已闭环」):巩固 merge 会整段重写正文(`src/dream/decisions.js:627-629` 走 `service.update`),而护栏类型表 `ARCHIVE_GUARDED_TYPES` 不含 `project` / `decision` / `history` / `summary`——正是「对用户的评价」最常落的类型,谄媚在那里从「搬运」升级成「改写放大」且源条目随即归档;`consolidation` 的「合并后取最高」、`conflict` 的「更可信」判据、`pattern` 从夸赞炼人格三处同类;`importance` 单调不减是代码保证(`src/service.js:1443` 的 `Math.max`),规则 d 救不回已被抬到 5 的存量行;给 `quality-filter` 加确定性立场信号这条路是死的(`service.js:1598` 的 `importanceExempt >= 4` 正好豁免谄媚最可能落的那一档);`codingSummary` 缺 type 枚举锁是既有缺陷(`summarize.js:100` 的 `VALID` 只认 7 个 type,自造分类会被静默丢弃且失败路径不推进游标导致同窗口重放),建议独立小 PR。**提示词测试只锁形状不锁行为**:`test/prompt-stance.test.js` 能断言「文本里有这条约束、位置对」,锁不了「模型是否遵守」——后者要 LLM 在环加预注册判据,本轮不做,**不得把本批写成「已验证降低谄媚率」**。新用例 6 条:中英成对、位置锁、工具豁免条款在场、散文契约不破(`doesNotMatch(/JSON 数组|JSON array/)`,总览要的是散文)、总览落库判据不放宽(空串仍 `summary:false` 且旧总览存活)。改动只碰提示词字面量与一条注释,既有锁一条未改。

## 🎨 面板

Expand Down Expand Up @@ -63,6 +65,13 @@

- **main 的 Security 变红跟代码无关,是上游两小时前才发的两条公告:`glob@13.0.6` 与 `minimatch@10.2.6` 都落在 devDependency 链上(#533/#534 合并之后暴露)**:2026-10-31 04:42 的 `277779f` 与 03:29 的 `1931508` 两次 push 的 Security workflow 都红,而 CI 四条腿全绿——红的不是测试,是 osv-scanner 那一步:`Total 2 packages affected by 2 known vulnerabilities (0 Critical, 0 High, 2 Medium …)`,`GHSA-3g3m-2fjr-6pw4`(CVSS 6.3,glob 的 AbortSignal 取消绕过,超时后仍继续遍历文件系统,别名 CVE-2026-118773)与 `GHSA-g3hv-75wg-m249`(CVSS 6.9,minimatch 的 globparts 放大 DoS,别名 CVE-2026-118764)。两条公告的 `published` 都是 **2026-10-30T23:09Z**,而 `2139237` 的 Security 是当天 08:36 跑的、当时还是 success;`git diff 2139237 277779f -- dsh-mneme/package-lock.json` **完全为空**——同一个 lockfile 隔了 15 小时从未绿变红,所以这不是回归,是「规则变了」。来源链只有一条:`c8@12.0.0`(devDep)→ `test-exclude@8.0.0` → `glob@13.0.6` + `minimatch@10.2.6`(deduped);`c8` 与 `test-exclude` 都已是 npm 上的最新版,其 deps 仍写 `glob: ^13.0.6` / `minimatch: ^10.2.2`,**上游还没跟上**,所以只能在本仓库 overrides 抬 pin 号——与 `adm-zip@0.6.1`、`brace-expansion@5.0.12` 同一手法(PR #345 那次就是这样清零的)。glob 抬到 14.0.3、minimatch 抬到 11.0.3,`npm audit` 双口径 0 vulnerabilities。**为什么钉死而不是写 `^`**:overrides 的职责是「这个闭包就装这一版」,写范围等于把选择权交回解析器,下一次上游发新版时 lockfile 会在某个「只更新 lockfile」的 PR 里悄悄漂走,而 osv-scanner 只看 lockfile——红绿灯本该由人决定。**代价与验证**:glob 14 把 `path-scurry` 从 2.x 提到 3.x、minimatch 11 多带一个 `@isaacs/nonrecursive-glob-match`(都是 dev-only,不进发布包、不参与运行时);这条链唯一的生产用途是 `npm run test:coverage` 的 `c8` 文件筛选,全量 `npm test` **2569 / 2568 pass / 0 fail / 1 skipped**,另在临时工程里跑过 `c8 --include/--exclude/--all` 三种走 glob 的入口,覆盖率表正常。**没有 `ignore`/allowlist**:把公告静音等于让下一条真公告也一起静音,而 `security.yml` 里 osv-scanner 的 `fail-on-vuln: true` 正是为了不让它只当提醒。

### 贡献者 / Thanks

- **@wuchichiumike** — 会话级无痕闸门的实现(#540,#539):三个 opt-in 键、子代理判据只认宿主自己的委派标记、拿不到 id 也照判、记账表有界。本版唯一的外部代码贡献。
- **@chengxinshengglj-png** — #529 的两轮实测:先用一份 3,400 条活跃的库回填「封顶 24h 够不够、60 对默认值准不准」,再指出上一批修复「预算造 backlog、退避没接住」——续排这一批就是从这条反馈展开的。
- **@xueyue33** — #533 的现场数据:37 轮 sleep 之后活跃记忆里 100 条被重复追加同一段差异注记,最狠的一条 19 段、正文从 150 字膨胀到 2200+ 字。
- **@strange2000** — #493 的复核:指出失败计数只活在进程里、围栏断链只长在一处,本版两条补充都来自这份复核。

## [0.9.5] - 2026-10-30

## 🆕 新增
Expand Down
Loading
Loading