问题与核验版本
LmmHttp.request() 只在成功响应分支检查 maxBytes;非 2xx 且 Content-Type 为 application/json 时,为读取错误码直接调用 await response.text(),没有大小限制。
核验:main@bd51fe1f001b2528b04d80fb7d09e9e243aacadd。
这里是 OAuth/目录/余额 HTTP 客户端的资源边界问题,不是 #1 的模型 SSE 错误分类,也不是 #3 的模型请求重试。已查看现有 issues 和开放 PR,未发现同一缺陷;核查时没有开放 PR。
已执行的离线复现
在 Node.js v22.16.0 下直接执行原始 src/http.ts 与 src/protocol.ts,仅用 --experimental-strip-types 去掉类型;没有改写方法或替换校验函数。注入原生 Response 和计数的 ReadableStream 作为 fetch 返回值,完全没有建立网络连接。
本地源文件已按 Git blob 算法核对:
http.ts ef3d18b61f6f69bbaaddecf634bb79c3d9e8a9f3
protocol.ts d0d422622eaeae3539d5aee0fadece7f64c6830a
四组响应均为有效 JSON、同一个 4 MiB 内容,按 16 KiB 分块提供,stream highWaterMark=0;调用 form(),上限应为 64,000 字节。
| HTTP 状态 |
实际读取字节 |
主动取消剩余 body |
返回类别 |
| 200 |
65,536 |
是 |
invalid_response |
| 401 |
4,194,304 |
否,已读到 EOF |
unauthorized |
| 429 |
4,194,304 |
否,已读到 EOF |
rate_limited |
| 500 |
4,194,304 |
否,已读到 EOF |
upstream_unavailable |
成功分支在跨过阈值的那一个 chunk 后停止是正常的;错误分支则消费全部 4 MiB。4 MiB 仅是本次有意控制的小样本,不是代码的实际上限,也没有进行 OOM/线上压测。
可将以下脚本放在仓库根目录,以 node --experimental-strip-types probe-http.mjs 复现:
import { LmmHttp } from './src/http.ts';
const payload = new TextEncoder().encode(JSON.stringify({
padding: 'x'.repeat(4 * 1024 * 1024 - 14),
}));
for (const status of [200, 401, 429, 500]) {
let offset = 0, cancelled = false;
const http = new LmmHttp({fetch: async () => new Response(
new ReadableStream({
pull(controller) {
if (offset === payload.length) { controller.close(); return; }
const end = Math.min(offset + 16384, payload.length);
controller.enqueue(payload.subarray(offset, end));
offset = end;
},
cancel() { cancelled = true; },
}, {highWaterMark: 0}),
{status, headers: {'content-type': 'application/json'}},
)});
let code;
try { await http.form('/api/oauth2/token', {grant_type: 'fixture'}); }
catch (error) { code = error.code; }
console.log({status, maxBytes: 64000, readBytes: offset, cancelled, code});
}
影响
登录、刷新授权等失败处理,反而绕过成功路径已有的内存保护。服务端或反向代理返回体积较大的 JSON 错误时,客户端会先完整读取并解析它,才报告 HTTP 错误。已有超时限制不能代替字节上限。
这里只证明客户端资源边界缺失;未证明普通用户能够控制正式 LMM 服务的错误体,未观察线上内存故障或泄露凭据。
修复建议与验收
- 抽出一个有上限且会取消 reader 的 JSON/body 读取函数,成功与错误路径复用;错误诊断可使用更小的独立预算,但不能是无界
text()。
- 超限立即取消剩余响应;不要依赖 Content-Length,必须覆盖分块响应和缺失/不可信长度。
- 保留有效且小体积的
IP_ACCESS_ROUTE_REJECTED 专用类别,以及 401/403、429、5xx 的安全状态分类;不把错误正文或凭据输出给用户。
- 内层解析 catch 不应吞掉请求取消/超时后继续将其伪装为授权失效;补取消期间读取错误体的测试。
- 添加刚好达到上限、超过上限、畸形 JSON、小体积 IP 策略错误、401/429/500、读取中取消等回归;断言读取量和 body 释放,而不只断言最终异常文字。
验证边界:已运行上述原方法离线测试;未运行完整插件测试套件、真实 OAuth 登录或生产环境测试。此次仅提交问题,不修改代码、不部署。
问题与核验版本
LmmHttp.request()只在成功响应分支检查maxBytes;非 2xx 且 Content-Type 为application/json时,为读取错误码直接调用await response.text(),没有大小限制。核验:
main@bd51fe1f001b2528b04d80fb7d09e9e243aacadd。bytes <= maxBytes;form()明确传入64_000,普通请求默认2_000_000。text()读取整个响应体,不自动执行调用者自定义的字节上限。这里是 OAuth/目录/余额 HTTP 客户端的资源边界问题,不是 #1 的模型 SSE 错误分类,也不是 #3 的模型请求重试。已查看现有 issues 和开放 PR,未发现同一缺陷;核查时没有开放 PR。
已执行的离线复现
在 Node.js
v22.16.0下直接执行原始src/http.ts与src/protocol.ts,仅用--experimental-strip-types去掉类型;没有改写方法或替换校验函数。注入原生Response和计数的ReadableStream作为 fetch 返回值,完全没有建立网络连接。本地源文件已按 Git blob 算法核对:
四组响应均为有效 JSON、同一个 4 MiB 内容,按 16 KiB 分块提供,stream highWaterMark=0;调用
form(),上限应为 64,000 字节。成功分支在跨过阈值的那一个 chunk 后停止是正常的;错误分支则消费全部 4 MiB。4 MiB 仅是本次有意控制的小样本,不是代码的实际上限,也没有进行 OOM/线上压测。
可将以下脚本放在仓库根目录,以
node --experimental-strip-types probe-http.mjs复现:影响
登录、刷新授权等失败处理,反而绕过成功路径已有的内存保护。服务端或反向代理返回体积较大的 JSON 错误时,客户端会先完整读取并解析它,才报告 HTTP 错误。已有超时限制不能代替字节上限。
这里只证明客户端资源边界缺失;未证明普通用户能够控制正式 LMM 服务的错误体,未观察线上内存故障或泄露凭据。
修复建议与验收
text()。IP_ACCESS_ROUTE_REJECTED专用类别,以及 401/403、429、5xx 的安全状态分类;不把错误正文或凭据输出给用户。验证边界:已运行上述原方法离线测试;未运行完整插件测试套件、真实 OAuth 登录或生产环境测试。此次仅提交问题,不修改代码、不部署。