Skip to content

side request(max_tokens<=200)绕过 preflight 且不检查载荷体积,携带全量历史直转上游导致 400 #554

Closed

Description

@shengbuding

问题

代理将 max_tokens <= 200 的请求判定为 side request(如会话标题生成、探测类),直接 passthrough 透传,不经过 preflight 压缩管线(dist/index.js L57035-57041 SIDE_REQUEST_MAX_TOKENS = 200;L57432-57445)。

透传判断只看 max_tokens 预算,不检查请求携带的 payload 体积。这类请求同样会带上完整会话历史——实测一个 19.8 万+ token 历史的会话,其侧请求被原样转发给 150,528 窗口的上游,连续两次得到 400:

exceed_context_size_error (198,277 / 198,661 > 150,528)

且该类 400 不携带窗口超限信号,代理的 learned 自适应无法从中学到任何东西——卡死会话连「探测还能不能继续」都做不到。

复现步骤

  1. 构造一个 payload 超过上游窗口的会话;
  2. 由客户端发送任意 max_tokens <= 200 的请求(带全量历史);
  3. 观察:请求绕过 preflight,上游 400。

影响

  • 「小额请求」并不等于「小额载荷」,preflight 护栏被绕过;
  • 大上下文会话将彻底死锁:正式请求被压缩护栏保护,但探测/标题类请求持续 400;
  • 上游可能被大载荷反复打满,浪费 prefill 算力。

建议修复

  1. side request 转发前同样做体积检查:payload 估算超过上游窗口 → 拦截并返回业务错误码(或截断历史),而不是直转;
  2. 溢出 400 接入现有 learned 通道,或明确透传「窗口超限」信号供客户端处理;
  3. [可选] 增加配置开关允许关闭 side request 透传。

关联

#551 相关观察 2。

Activity

  1. ranxianglei commented on Sep 5, 2026

    @ranxianglei
    Owner

    🤖 ework agent · qwen3.8-27b

    Triage 完成:问题已核实(全部在代码中确认)、无重复条目,开始实现。

    查重:无重复项。相关条目已逐条比对:#388/#398(引入侧请求透传隔离——本 gap 的源头)、#546/#548(修的是反方向误判「主请求被降级为 side」)、#551 相关观察 1(本 issue 即该观察的独立拆分)、#553(fork 会话估算,机制不同)。均为不同问题,#554 保持打开。

    核实结论:

    1. 透传确实无任何载荷体积检查。 isSideRequest(src/server.ts:626-632)只看输出预算(max_tokens|max_completion_tokens|max_output_tokens ∈ (0,200] 且无 tools);side gate(src/server.ts:1346-1360)把 bodyBuffer 原样转发,且按设计跳过 preflight(标题生成等侧请求污染主会话状态:prepareAnthropic 缺 isTitleGen 守卫、快照无条件覆盖、survivedCount 每请求空转 #388)。超窗的全历史侧请求必然上游 400,且此类请求会反复发起(标题生成/探测),形成持续 400。

    2. 「learned 自适应学不到」成立,是三重失效:

      • exceed_context_size_error 不在 CONTEXT_OVERFLOW_PATTERNS(src/util.ts:107-123)里 → inspectContextOverflow 判 isOverflow=false,根本不进学习路径;
      • 即便识别,兜底估算用的是 estimateCoreMessages(prepared.processedMessages)(src/server.ts:2847),而 side 路径的 processedMessages 恒为 [] → 估算 ≈0 <1000,学习再次被跳过;
      • 即便学到,side gate(L1346)在 self-heal 读取(L1371+)之前触发、且从不引用 reqConfig.modelContextLimit → 后续侧请求照样裸转,死锁持续。

    层次判断:根因不是「客户端带全量历史」(历史归客户端自己的 view,代理不可控),而是 side gate 把「小输出预算」与「可跳过所有输入侧护栏」混为一谈;上面三层是阻止自纠的次级缺陷。

    方案(实现建议 1+2,建议 3 不采纳):

    1. 转发前体积检查:估算 raw body(复用 preflight 的 CJK-aware 计数 + image-tokens 的图片成本),对比有效窗口 = min(resolved 窗口, learned 窗口)− 输出预留(非 Anthropic wire);估算 ≥ 有效窗口 → 本地 fail-fast 413(结构化错误 side_request_payload_too_large,retryable=false),不转发、不截断历史。选 fail-fast 而非截断:截断策略没有可验证语义(丢哪段无从判断),且客户端拥有自己的历史;fix: honor anthropic-beta context-1m to size the context window #301 的 preflight fail-fast 已确立「不转发 guaranteed-400 载荷」先例,保持一致。
    2. 溢出 400 接入 learned 通道:新增 exceed[_ ]context[_ ]size 模式;parseOverflowWindow 识别 (A / B > C) 形式并以 C 为窗口(本 issue 示例可提取出 150,528);forward() 兜底估算在 processedMessages 为空时(side 路径)改用 raw body 估算。这样即使估算偏低漏过一次 400,下一次也会被本地拦截。
    3. 不加配置开关:护栏落地后透传行为已经安全,开关徒增配置面,无收益。

    实现完成后提交 PR 并在此回帖。

  2. ranxianglei commented on Sep 5, 2026

    @ranxianglei
    Owner

    🤖 ework agent · qwen3.8-27b

    实现完成,PR 已提交:#557

    变更(分支 2026-09-05_side-request-payload-guard,commit 2d78bf2,4 files +256/−11):

    1. 转发前体积检查(src/server.ts 新增 sideRequestGuard + side gate 内调用):raw body 估算(CJK-aware 文本计数 + 图片成本)对比有效窗口 = min(resolved 窗口, learned 窗口)− 输出预留(非 Anthropic wire);≥ 有效窗口 → 本地 fail-fast 413(side_request_payload_too_large,retryable=false),不转发、不截断历史;窗口未知(limit≤0)时行为与现在一致。
    2. 溢出 400 接入 learned 通道(src/util.ts + forward()):新增 exceed_context_size_error 模式;识别 (A / B > W) 形式并以 W 为窗口(本 issue 示例可提取出 150,528);kernel view 为空时(side 路径)兜底估算改用 raw body——即使估算偏低漏过一次,上游报出真实窗口后,下一次相同请求即被本地拦截。
    3. 建议 3(配置开关)未采纳,理由见 PR body。

    测试(tests/side-request-isolation.test.ts,+5 个):

    • unit:estimateRawBodyTokens(字符串叶计数 / data·url·b64_json 排除 / CJK 逐字)、inspectContextOverflow(新模式 + (A/B>W) 窗口解析 + status gate + 既有模式回归)、sideRequestGuard(边界值、learned 只缩不放、headroom 预留、图片成本);
    • e2e:超窗 side 请求 → 本地 413、结构化错误码、upstream 零命中、kernel state 不变;尺寸合适的 side 请求仍原样透传;upstream 对 side 请求回 exceed_context_size_error (… > 150,528) 400 → 学到 150,528 并 arm emergency shrink,第二次相同请求被本地 413 拦截、upstream 命中数保持 1。

    Pre-flight:typecheck ✅;全量测试 1051/1052(唯一失败为 launcher.test.ts 环境性问题——本沙箱 PATH 里存在 /usr/bin/codex,已在干净 master 上验证同样失败,与本变更无关);build ✅。E2E codex 套件需要可达的真实上游,本环境无法运行,已在 PR body 注明。

    请 review。

  3. ranxianglei commented on Sep 6, 2026

    @ranxianglei
    Owner

    修复已随 PR#557 合并 (merge commit e992d5e): side request (max_tokens<=200) 现在同样接受载荷体积检查, 携带全量历史直转上游导致 400 的路径已被 guard。PR 标题里的 (#554) 不是 closing 关键字, 所以没有自动关, 手动关闭。

  4. added a commit that references this issue on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions