docs(changelog): 定版 0.3.15
This commit is contained in:
1 parent
fd86f3ceca
commit
e6ec569991
1 file changed
+59
-30
+59
-30
@@ -4,75 +4,104 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.15] - 2026-10-07
|
||||
|
||||
### 拦截
|
||||
|
||||
#### 新增的功能
|
||||
|
||||
- **新增内置拦截规则「删除类接口路径」**:此前内置的 HTTP 破坏性规则只认 DELETE **方法**(`curl -X DELETE`、`requests.delete(`、`method:'DELETE'`),路径类规则的词表又只有 `/clear /wipe /flush /purge /truncate /drop /destroy /factory-reset /reset-all`——而多数应用的删除接口用 GET/POST 就能触发,于是 `curl 'http://t/api/user/delete?id=1'` 这类调用不命中任何内置规则,会真实删掉目标数据。现补一条 `deny` 规则覆盖 `/delete /del /remove /unlink /erase /destroy`(允许 `/deleteAll`、`/delete_user`、`/delete-user` 这类后缀形式),动词后必须跟分隔符,`/delivery`、`/details`、`/delta`、`/delegate` 不会被误拦。规则走独立的种子标记位,**已有实例升级后也会拿到**;和其余内置规则一样可在「系统 → 命令拦截」里停用或删除。
|
||||
- **新增内置拦截规则「删除类接口路径」**:此前内置的 HTTP 破坏性规则只认 DELETE **方法**,而多数应用的删除接口用 GET/POST 就能触发,`curl 'http://t/api/user/delete?id=1'` 这类调用会真实删掉目标数据。现补一条 `deny` 规则覆盖 `/delete /del /remove /unlink /erase /destroy`(动词后必须跟分隔符,`/delivery`、`/details`、`/delta` 不会误拦)。已有实例升级后也会拿到,可在「系统 → 命令拦截」里停用或删除。
|
||||
- **模型兜底审批的 token 消耗可单独查看**:审批调用此前没有独立的计量归属,花了多少只能靠猜。现在它单独计量,「系统 → 命令拦截」的「模型兜底审批」开关下方新增用量卡片——调用次数、输入、输出、缓存读写五项累计统计,加一张近 30 天每日消耗的迷你柱图。
|
||||
|
||||
### 漏洞推送
|
||||
|
||||
#### 新增的功能
|
||||
|
||||
- **漏洞发现支持推送到 IM**:新增「系统 → 通知推送」页,可把扫描发现推送到**钉钉、飞书、企业微信、通用 Webhook、Telegram、邮件**六种渠道。同一类型可配任意多个机器人实例(如「应急响应群」「日常播报群」各一个钉钉机器人),每个实例独立设启停、限流与过滤规则。
|
||||
- **推送时机分实时与汇总两种模式**:实时模式命中即逐条发送;汇总模式按全局周期(默认 30 分钟)把一批漏洞合并成一条消息,开头给出「近 N 分钟新增 M 个漏洞」及级别分布,便于一眼判断是否需要立刻处理。想做「高危实时、其余汇总」就建两个渠道分别配置,策略不写死在代码里。
|
||||
- **过滤规则支持四个维度**:最低严重级别、限定任务 / 资产范围、漏洞类型关键词包含与排除(排除优先)、以及是否接收处置状态变更(默认关,因为多数人说的「推送」指发现新漏洞,而不是状态流水账)。单条消息带回「查看详情」按钮跳转漏洞详情,目标地址由全局「回链地址」配置,留空则不带按钮。
|
||||
- **投递历史与手动重发**:通知页列出每条投递的状态、尝试次数、失败原因与所属渠道,可按渠道与状态筛选;失败项可一键重发(重发会清零重试计数,因为人工点重发意味着失败原因已被处理)。
|
||||
- **渠道凭据掩码回显**:Webhook 地址、加签密钥、Bot Token、SMTP 密码等由各渠道自行声明(`SecretKeys()`),接口只回显带尾号提示的掩码值;提交时原样回传即表示「不修改」,清空输入框则删除该字段。
|
||||
- **漏洞发现支持推送到 IM**:新增「系统 → 通知推送」页,可把扫描发现推送到**钉钉、飞书、企业微信、通用 Webhook、Telegram、邮件**六种渠道。同一类型可配任意多个实例,每个实例独立设启停、限流与过滤规则。
|
||||
- **推送时机分实时与汇总两种模式**:实时模式命中即逐条发送;汇总模式按全局周期(默认 30 分钟)合并成一条消息,开头给出新增数量与级别分布。想做「高危实时、其余汇总」就建两个渠道分别配置。
|
||||
- **过滤规则支持四个维度**:最低严重级别、限定任务 / 资产范围、漏洞类型关键词包含与排除(排除优先)、是否接收处置状态变更(默认关)。消息带「查看详情」按钮,目标地址由全局「回链地址」配置,留空则不带按钮。
|
||||
- **投递历史与手动重发**:列出每条投递的状态、尝试次数、失败原因与所属渠道,可按渠道与状态筛选;失败项可一键重发(重发会清零重试计数)。
|
||||
- **渠道凭据掩码回显**:Webhook 地址、加签密钥、Bot Token、SMTP 密码等只回显带尾号提示的掩码值;提交时原样回传表示「不修改」,清空输入框则删除该字段。
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
> 本节记录的是功能初版完成后一轮安全审计的结论。每条都是先复现、再修、再补回归测试。
|
||||
> 本节是功能初版完成后一轮安全审计的结论,每条都先复现、再修、再补回归测试。
|
||||
|
||||
- **修复掩码可被「改目标地址、保留凭据」绕过(严重)**:掩码机制的目的是「凭据不回显给浏览器」,但目标地址与凭据是两套独立字段,而配置合并对「未提及的键」一律保留库中原值。于是**只改地址、对凭据避而不谈**就能让服务端把库里的真凭据发到任意地址——通用 Webhook 的 `Authorization` 头、Telegram 的 Bot Token(进请求路径)、邮件渠道的 SMTP 密码(STARTTLS 后交给对端)全部泄露,且完全静默、不依赖重定向。这条路径实测可行,四个渠道逐一复现过。现在规定:**只要目标地址发生变化,调用方就必须对每一个凭据字段显式表态**(给新值,或显式留空表示不再需要)——原样回传掩码值等于「沿用旧凭据」,恰恰是攻击形状,一并拒绝。刻意不做「自动丢弃凭据」,因为对可选凭据字段(如 `headers`)那会变成「鉴权静默没了但接口返回成功」,比报错更难排查。
|
||||
- **修复 markdown 系渠道对不可信内容零转义**:钉钉、企业微信、飞书三个渠道此前完全不做转义(Telegram 与邮件都做了)。漏洞标题与摘要来自模型输出(模型读的是被测目标的响应),资产的 `url` 则是扫描得到的、含目标可控查询串的完整 URL。一条标题为 `登录口 SQL 注入\n[紧急:点此验证账号](http://attacker.tld)` 的漏洞,会在安全工程师的钉钉/飞书里渲染成**可点击外链**;`` 则会在渲染时被客户端拉取——等于通报「这条漏洞已被看过」并泄露阅读者 IP。现统一做单行化 + markdown 元字符转义。同时修正了一处相关的设计错误:转义必须发生在各自的渲染出口,不能放进被四种语境共用的标题函数(markdown 转义泄漏到 Telegram 的 HTML 里会留下可见反斜杠)。
|
||||
- **修复投递地址的 SSRF 面**:此前只校验 scheme 与 host,`169.254.169.254`(云元数据,可读出实例凭据)、环回地址、内网地址一律可投递;而投递失败时响应体前 200 字节会进 `last_error` 并被投递历史接口回显,构成一条半盲读原语(可读任意内网 HTTP 端点响应的前 200 字节)。现在在**拨号阶段**设防(而非只在保存配置时校验)——那里才是最终生效点,同时覆盖 DNS 重绑定与同主机重定向,并拒绝跨主机重定向(这几家的凭据就在 URL 里,跟随跳转等于交给跳转目标)。环回与链路本地地址需要显式设置 `ARTEX_NOTIFY_ALLOW_LOCAL=1` 才放行(本机 SMTP 中继是合法配置,不能一刀切);**RFC1918 私网刻意放行**,因为内网自建 Mattermost / SMTP 中继很常见,把防护做到那个程度会把正常部署一起废掉。
|
||||
- **修复推送凭据经错误信息外泄**:投递失败时 `http.Client.Do` 返回的 `*url.Error` 会把**完整 URL** 打进错误文本,而钉钉的 `access_token`、企业微信的 `key`、飞书的 hook id、Telegram 的 `/bot<token>/` 都**就在 URL 里**。该串因此流入四个出口:`notification_deliveries.last_error` 明文落库、投递历史接口的原样回显(绕过了渠道配置的掩码)、服务端日志、以及测试发送接口回给前端的错误提示。现统一脱敏——错误信息只保留 `scheme://host` 与底层原因(足够定位 DNS / 连通性 / 证书问题),路径与查询一律丢弃。地址校验自身(`url.Parse` 失败)的错误文本同样带完整地址,与上面同一处理;**上一轮只修了前者、漏了后者,且当时的回归用例全都走的是 scheme 分支、根本没覆盖到解析失败路径,属于假保证**——现已补上真正覆盖该分支的用例。
|
||||
- **修复汇总消息被截断后整批标记已送达导致的静默丢失**:渠道都有长度上限(企微 4096 字节最紧),一批装不下时消息会被截断,而发送成功后整批投递都被标记为已送达——被截掉的那些**既不在消息里、也不在失败列表里**,投递历史还显示成功,漏洞就这么消失。现在改为按**整条**打包:装进本条的那些才标记已送达,其余回到队列等下一条继续发,且消息头部如实写明「本条显示前 N 条,其余 M 条将在下一条消息继续」。被推迟的条目**不消耗重试次数**(领取时乐观 +1 的那次会减回去),否则一个 500 条的积压会在第三段就把尾部条目判成失败——而它们从未出过任何错。
|
||||
- **修复最低级别门槛打错字会让过滤器静默失效**:`min_severity` 写成 `hgih` 这类笔误时,未知级别在序数表里为 0,判定退化成 `rank >= 0` 恒真——用户以为限制了「仅高危」,实际把全部漏洞灌进群里,且界面上与配置正确完全无法区分。现于写入路径校验取值,错误信息列出可选值(读取路径仍保持宽容:库里已有的坏值不会让渠道整个读不出来)。
|
||||
- **修复 `rate_per_min = 0`(不限流)不可达**:文档、界面提示与令牌桶都把 0 解释为「不限流」,唯独写库这一层写了 `if RatePerMin <= 0 { 取默认值 }`,把显式 0 悄悄改成 20(钉钉/企微/Telegram)或 100(飞书)——操作者以为放开了限流、实际被卡着且没有任何提示。「未指定」与「显式 0」的区别只有请求体能表达,默认值因此改到接口层在字段缺省时填。
|
||||
- **修复汇总渠道完全绕过令牌桶**:`allow` 被 `takeTokens` 扣掉却没人用,`rate_per_min` 对 digest 模式没有任何作用。现在领取条数同时受「本轮额度」与内存上界两个约束。
|
||||
- **修复失败处置按整批最大尝试次数判断,让老投递连坐新投递**:批次内各条的尝试次数并不相同,一个已重试两次的老投递会把同批里全新的投递一起拖进 failed——新漏洞一次重试都没用上就永久丢失,与「不让老行拖新行下水」的初衷正好相反。现在逐条决定:永久失败立即判死、各自重试次数耗尽的判死、其余按各自的退避档位重排。
|
||||
- **修复汇总批次中快照无法解析的投递被静默标记成功**:这类条目会被渲染阶段跳过(不拖垮整批),但随后整批标记已送达把它们一起算作成功。现在它们被显式判失败并给出原因,投递历史里能查到。
|
||||
- **修复单渠道每轮投递条数可能超出租约时长**:租约 3 分钟,而一轮可串行投递的条数若多到最坏耗时超过租约,多实例部署时对端会把租约过期的行重新领走、重复发送并双份递增尝试次数。现在按「租约 / 单次超时」倒推出每轮上限,并有一条断言把这三个常量的关系钉死(写这条断言时立刻发现原取值 6 恰好用满租约、零余量,已调整为 5)。
|
||||
- **修复 Telegram 截断可能切断 HTML 实体**:截断只避开了半截标签,没避开被切断的 `&` 之类的实体残片,而解析器可能因此拒收**整条**消息——超长汇总消息本来就常见,代价太大。现在同时回避未闭合标签与实体残片。
|
||||
- **修复邮件渠道把临时性 SMTP 失败判成永久失败**:SMTP 的 4xx(如灰名单 `450`)是临时拒绝、正规做法是稍后重试,而此前一律判永久失败——一个启用灰名单的邮件服务器会让**每条**推送都在第一次尝试后落入失败,而这恰恰是自动重试最该起作用的场景。现按应答码首位区分:4xx 可重试、5xx 永久失败,取不到码时按可重试处理。
|
||||
- **修复提交嵌套结构时会真的把掩码字面量写进库**:像 `webhook.headers` 这种对象字段只能整体掩码或整体提交;把掩码哨兵塞进对象内部既表达不了「保持不变」,又会被当真实值存下去,导致后续鉴权静默失效且没有任何报错。现在这种提交被显式拒绝。
|
||||
- **修复掩码可被「改目标地址、保留凭据」绕过(严重)**:配置合并对「未提及的键」一律保留库中原值,于是只改地址、对凭据避而不谈,就能让服务端把库里的真凭据发到任意地址,且完全静默。现规定目标地址一旦变化,每个凭据字段都必须显式表态——给新值,或显式留空表示不再需要。
|
||||
- **修复 markdown 系渠道对不可信内容零转义**:漏洞标题与摘要来自模型输出、资产 URL 来自扫描结果,构造的标题会在钉钉 / 企业微信 / 飞书里渲染成可点击外链,图片语法则会在渲染时被客户端拉取并泄露阅读者 IP。现统一做单行化 + markdown 元字符转义,且转义发生在各渠道的渲染出口而非共用的标题函数。
|
||||
- **修复投递地址的 SSRF 面**:此前环回、内网与云元数据地址一律可投递,而失败响应体前 200 字节会进 `last_error` 并被投递历史回显,构成一条半盲读原语。现改在**拨号阶段**设防,覆盖 DNS 重绑定,并拒绝跨主机重定向;环回与链路本地地址需显式设置 `ARTEX_NOTIFY_ALLOW_LOCAL=1`,RFC1918 私网仍放行(内网自建 SMTP 中继很常见)。
|
||||
- **修复推送凭据经错误信息外泄**:钉钉的 `access_token`、企业微信的 `key`、Telegram 的 bot token 都在 URL 里,而投递失败时完整 URL 会流入 `last_error`、投递历史、服务端日志与前端提示四处。现统一脱敏,只保留 `scheme://host` 与底层原因。
|
||||
- **修复汇总消息被截断后整批标记已送达导致的静默丢失**:被截掉的漏洞既不在消息里、也不在失败列表里,投递历史却显示成功。现改为按**整条**打包,只标记装进本条的那些,其余回队列等下一条续发且不消耗重试次数,消息头部也写明还有多少条将在下一条继续。
|
||||
- **修复最低级别门槛打错字会让过滤器静默失效**:`min_severity` 写成 `hgih` 这类笔误时未知级别序数为 0,判定退化成恒真,用户以为只收高危、实际全部漏洞都被灌进群里。现于写入路径校验取值并列出可选值。
|
||||
- **修复 `rate_per_min = 0`(不限流)不可达**:写库这一层把显式 0 悄悄改成了默认值,操作者以为放开了限流、实际仍被卡着。默认值改到接口层在字段缺省时填。
|
||||
- **修复汇总渠道完全绕过令牌桶**:`rate_per_min` 对 digest 模式此前没有任何作用。现在领取条数同时受本轮额度与内存上界两个约束。
|
||||
- **修复失败处置按整批最大尝试次数判断,让老投递连坐新投递**:一个已重试两次的老投递会把同批里全新的投递一起拖进 failed,新漏洞一次重试都没用上就永久丢失。现改为逐条决定。
|
||||
- **修复汇总批次中快照无法解析的投递被静默标记成功**:这类条目会被渲染阶段跳过,却随整批一起算作成功。现在显式判失败并给出原因。
|
||||
- **修复单渠道每轮投递条数可能超出租约时长**:多实例部署下,租约过期的行会被重新领走、重复发送并双份递增尝试次数。现按「租约 / 单次超时」倒推每轮上限,并有断言把这几个常量的关系钉死。
|
||||
- **修复 Telegram 截断可能切断 HTML 实体**:截断只避开了半截标签,没避开 `&` 这类实体残片,而解析器可能因此拒收**整条**消息。现在两者都回避。
|
||||
- **修复邮件渠道把临时性 SMTP 失败判成永久失败**:灰名单 `450` 这类 4xx 是临时拒绝、本该稍后重试,此前一律判死,启用灰名单的服务器会让每条推送都在首次尝试后失败。现按应答码首位区分,4xx 可重试、5xx 永久失败。
|
||||
- **修复提交嵌套结构时会真的把掩码字面量写进库**:像 `webhook.headers` 这种对象字段只能整体掩码或整体提交,把掩码哨兵塞进对象内部会被当真实值存下来、导致鉴权静默失效。现在这种提交被显式拒绝。
|
||||
|
||||
#### 设计说明
|
||||
|
||||
- **写漏洞的事务只做一次盲 INSERT**:`notification_events` 由 `RecordFindingTx` 在**同一事务**内写入,提交即保证「漏洞落库」与「推送任务存在」原子一致。这条 INSERT 刻意不读渠道表、不跑用户的过滤规则——否则一条配错的过滤条件就能污染甚至中止事务,让高危漏洞存不进库。为把失败隔离在这一条语句上(PostgreSQL 中事务内任一语句报错会让整个事务作废、连 `COMMIT` 都失败),它被 `SAVEPOINT` 包住,失败时只记日志、不影响漏洞写入。
|
||||
- **投递用租约领取而非长事务**:`FOR UPDATE SKIP LOCKED` 领取后把行置为 `sending` 并把 `next_attempt_at` 推到未来作为租约,提交事务后再做网络投递,因此投递期间不持有数据库锁;进程崩溃留下的 `sending` 行会在租约到期后被下一轮重新领取,自愈且不会造成无限重试。
|
||||
- **限流不消耗重试预算**:引擎先按渠道令牌桶算出本轮还能发几条,再按这个数量去领取。顺序反过来(先领后弃)会让被限流挡下的投递白计一次尝试次数,三次预算被纯粹的等待耗光后落入失败。超限不会丢消息,只把投递推迟到下一个 tick。
|
||||
- **写漏洞的事务只做一次盲 INSERT**:`notification_events` 与漏洞在同一事务内写入,提交即保证两者原子一致。这条 INSERT 刻意不读渠道表、不跑用户的过滤规则,并由 `SAVEPOINT` 包住——否则一条配错的过滤条件就能中止事务,让高危漏洞存不进库。
|
||||
- **投递用租约领取而非长事务**:`FOR UPDATE SKIP LOCKED` 领取后把行置为 `sending` 并把 `next_attempt_at` 推到未来作为租约,投递期间不持有数据库锁;进程崩溃留下的行会在租约到期后被重新领取,自愈且不会无限重试。
|
||||
- **限流不消耗重试预算**:先按令牌桶算出本轮额度,再按这个数量去领取。顺序反过来会让被限流挡下的投递白计一次尝试次数,三次预算被纯等待耗光后落入失败。
|
||||
|
||||
### 测试基础设施
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
- **修复公司 ICP 归属用例的清理顺序导致的资产永久残留**:该用例的清理注册在 `t.Cleanup` 里,但连接关闭用的是 `defer d.Close()`——`defer` 在函数返回时先执行、`t.Cleanup` 在其后,于是清理语句全部落在**已关闭的连接**上,错误又被 `_, _ =` 丢弃,测试资产与公司永久残留在库里。它用 `MAX(companies.id)+1` 当假 TaskID 给资产打标,一旦该数字与其他用例的任务 id 相撞,那个按「恰好 N 个资产」断言的用例就会莫名失败且极难定位。现改为关闭连接也走 `t.Cleanup` 并注册在前(后进先出,保证清理先跑),同时让清理失败显形而不是被吞掉。同一模式(`defer d.Close()` + 在 `t.Cleanup` 里写库)在 `db` 包另有十余处,本次只修了被实证触发的那一处。
|
||||
- **修复公司 ICP 归属用例的清理顺序导致的资产永久残留**:`defer d.Close()` 在函数返回时先执行、`t.Cleanup` 在其后,于是清理语句全部落在已关闭的连接上,错误又被丢弃,测试资产与公司永久残留在库里,还会让其他按资产计数断言的用例莫名失败。现改为关闭连接也走 `t.Cleanup` 并注册在前,同时让清理失败显形。
|
||||
|
||||
### 流量
|
||||
|
||||
#### 新增的功能
|
||||
|
||||
- **流量列表新增「清空全部」**:一次删除全部流量记录,忽略当前筛选条件,并额外清理索引已不再记录的历史 host 目录,不留残余。清空后顺带做一次全量压实(`optimize` + `VACUUM` + `wal_checkpoint(TRUNCATE)`),把索引占用的磁盘空间还给系统,完成后提示实际释放了多少。已绑定到漏洞的流量证据保存在独立的证据库中,不受影响。空库上的 `VACUUM` 几乎没有成本,因此这同时是**已有实例把索引库转成增量回收模式的途径**——清空一次之后,日常按 host 删除就能自行回收空间了。
|
||||
- **流量列表支持内容检索、路径 / 状态码 / 长度筛选与列排序**:筛选行支持按**响应内容**(走全文索引)、**路径**、**状态码**(精确值或 `2xx` 这类分桶)、**响应长度范围**过滤,表头可按时间、状态码、响应长度排序,排序偏好记在本地。新增两个索引,已有实例升级后自动生效;Agent 工具的 query 契约未改动。
|
||||
- **流量列表新增「清空全部」**:一次删除全部流量记录(忽略当前筛选),并清理索引已不再记录的历史 host 目录,清空后顺带做一次全量压实并提示实际释放了多少空间。已绑定到漏洞的流量证据存在独立的证据库中,不受影响。
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
- **修复删除流量后磁盘空间不被释放**:删除只让数据不可见,空间一直留在索引文件里。SQLite 删行仅把页挂到 freelist,而索引库建库时没有启用 `auto_vacuum`,文件永不收缩;同时 `ex_fts` 是 `contentless_delete` 全文索引,`DELETE` 只写 tombstone 而不回收原 postings,不合并就永久累积——删流量反而让索引变大。由于 256KB 以下的正文全部内联在这个库里,加上 trigram 索引约为正文体积的 2 倍,抓包量大的实例会为早已删掉的流量长期占用数倍磁盘(实测抓 6MB 正文 → 索引 16MB,删光后仍是 16MB)。现在新建索引库直接启用 `auto_vacuum=incremental`,每次删除提交后在后台分块执行「全文索引增量合并 + `incremental_vacuum` + `wal_checkpoint(TRUNCATE)`」,逐步把空间还给操作系统(同一场景删除后回落到 104KB)。回收分块进行并在块间释放写锁,不阻塞流量录制;进程退出时立即让出,剩余工作在下次删除时续做。
|
||||
- **修复删除流量后磁盘空间不被释放**:索引库建库时没有启用 `auto_vacuum`,文件永不收缩;全文索引又是 `contentless_delete`,`DELETE` 只写 tombstone 而不回收原 postings——删流量反而让索引变大(实测抓 6MB 正文 → 索引 16MB,删光后仍是 16MB)。现在新建索引库启用 `auto_vacuum=incremental`,每次删除后在后台分块执行全文索引合并与增量回收,逐步把空间还给系统(同一场景回落到 104KB);回收在块间释放写锁,不阻塞流量录制。
|
||||
|
||||
> 升级说明:`auto_vacuum` 只能在建库时设定,因此**已有实例的索引库仍是旧模式**,`incremental_vacuum` 在其上是空操作——启动时会打印一行提示。这类库升级后 tombstone 的持续增长已经止住(全文索引合并照常执行),而已占用的体积用流量列表的「清空全部」回收一次即可——那一步会把库转成增量回收模式,此后日常删除自行生效。
|
||||
> 升级说明:`auto_vacuum` 只能在建库时设定,因此**已有实例的索引库仍是旧模式**,增量回收在其上是空操作(启动时会打印一行提示)。这类库升级后 tombstone 的增长已经止住,而已占用的体积用「清空全部」回收一次即可——那一步会把库转成增量回收模式,此后日常删除自行生效。
|
||||
|
||||
### LLM
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
- **修复自定义会话头在「一次性 LLM 调用」路径上不生效**:会话头的头值取自请求 context 上的 session id,而该 id 由 agentcore 仅在挂载了 transcript store 时才写入 context。目标拆解(第 0 轮)与冷节点压缩(§4 body 调用)都是不挂 store 的一次性调用,context 上没有 session id,网关侧读不到该头——对 opencode zen 这类「缺 `x-opencode-session` 直接 400 MissingSessionID」的端点,表现为「第 0 轮目标拆解 400 失败、后续 planner 轮次却完全正常」,且压缩失败只留一行日志、极难关联。现为这两条路径显式挂上按探索稳定的 session id(`exp<N>-goals` / `exp<N>-compactor`):既能正常带上该头,也让 llmrec 能把这两条路径的 token 用量正确归因到对应探索(此前完全记不到)。
|
||||
- **修复自定义会话头在「一次性 LLM 调用」路径上不生效**:目标拆解(第 0 轮)与冷节点压缩都是不挂 transcript store 的一次性调用,context 上没有 session id,网关侧因此读不到该头——对 opencode zen 这类缺头直接 400 的端点,表现为「第 0 轮失败、后续 planner 轮次却完全正常」。现为这两条路径显式挂上按探索稳定的 session id,顺带让 token 用量能正确归因到对应探索(此前完全记不到)。
|
||||
|
||||
### 工具
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
- **修复 MCP 与自定义工具的输出截断不生效,大结果整段灌爆上下文**:MCP 工具的返回此前完全不经截断,HTTP 类自定义工具则因传入空的工具上下文,一直用写死的 6000 字符而非会话配置的上限。现两条路径统一走 `actool.Capture`:按会话 `MaxOutputChars`(默认 30000)截断,配了 `ToolOutputDir` 时把全量输出溢写到磁盘、消息里只留开头片段与文件指针。
|
||||
- **升级 norma 到 v0.4.3**:harness 层统一执行 `CaptureOnce`,任何工具(含今后新增的)都不会再因为漏接截断而溢爆上下文。
|
||||
|
||||
### 部署
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
- **修复反向代理部署下活动流连不上**:SSE 地址此前无条件拼成 `${hostname}:8787`,反代到 443 的部署里页面从 `https://域名/` 加载、SSE 却被指向未对公网开放的 8787 端口,表现为活动流反复重连。现按 `NODE_ENV` 区分:生产默认同源,仅 `next dev` 保留 `:8787` 以绕过 Next.js 对 SSE 的缓冲;`NEXT_PUBLIC_SSE_BASE` 覆盖口保留。README 补充了反向代理部署说明。
|
||||
|
||||
### 发现
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
- **修复「按资产」视图资产列表溢出后没有滚动条**:左侧资产树本已套了滚动容器,但外层卡片只给了 `max-height` 而没有确定高度,滚动视口靠 `height:100%` 解析不出高度(CSS 中只设 `max-height`、`height` 仍为 `auto` 时百分比高度不生效),于是资产多时列表要么撑破卡片、要么被截断且无法滚动。现将高度上限直接落到资产树的原生滚动容器上(`overflow-y-auto` + `max-h`,随窗口高度自适应),资产少时卡片随内容收缩、资产多时封顶并出滚动条。
|
||||
- **修复「按资产」视图资产列表溢出后没有滚动条**:外层卡片只给了 `max-height` 而没有确定高度,滚动视口的百分比高度解析不出来,于是资产多时列表要么撑破卡片、要么被截断且无法滚动。现把高度上限直接落到资产树的原生滚动容器上,随窗口高度自适应。
|
||||
|
||||
### 数据库
|
||||
|
||||
#### 修复的问题
|
||||
|
||||
- **为连接池设置上限**:`database/sql` 默认不限制应用自身可打开的连接数,池里没有空闲连接时会无条件新建,一路顶到 PostgreSQL 的 `max_connections`(默认 100)才被拒。现封顶 32 条,超额查询改为等待空闲连接,同样的负载下表现为变慢而不是报错;若 `max_connections` 调低过需同步下调。
|
||||
- **提升连接复用率**:`MaxIdleConns` 的默认值是 2,并发超过 2 的连接用完即关、下次请求要重做 TCP 与 PostgreSQL 认证握手。现将空闲上限提到与总上限一致,并补上 30 分钟连接寿命与 5 分钟空闲淘汰,让池能自然换掉长期存活的坏连接。
|
||||
|
||||
### 贡献者
|
||||
|
||||
- [@Autumn-27](https://github.com/Autumn-27)
|
||||
|
||||
## [0.3.14] - 2026-09-24
|
||||
|
||||
|
||||
Reference in new issue
Block a user