Skip to content

[PR] fix: stop leaking x-bili-plugin* host headers to the model endpoint (#3905) - #3915

Closed
ranxianglei wants to merge 2 commits into
masterfrom
2026-10-20_egress-strip-bili-plugin-headers
Closed

ranxianglei wants to merge 2 commits into
masterfrom
2026-10-20_egress-strip-bili-plugin-headers

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What & why

The main HTTP forward path (buildForwardTarget) copied every inbound request header to the upstream except content-length/host, so all host-private x-bili-plugin* convention headers leaked to the real model endpoint: the agent marker, conversation id, window/model declarations, and — once #3906/#3907 lands — the new x-bili-plugin-exposed-tools declared-tools channel. The owner's constraint on that new header is "don't forward upstream"; this closes the pre-existing gap so the new header does not ship inside it.

Reproduced locally before fixing: a mock upstream captured the full x-bili-plugin* family arriving on both the proxy lane and the plugin lane (4/5 regression assertions failed pre-fix).

Change

Behavior change (old → new)

Header family Before After Why
x-bili-plugin* (marker / conversation / window / model / exposed-tools) forwarded to the model endpoint stripped at egress host-private convention metadata, consumed once at ingress; meaningless to providers; owner constraint on the new exposed-tools header
x-bili-hop / x-bili-tunnel / x-bili-ws-lane forwarded unchanged (still forwarded) bili→bili protocol markers; the consumer is a downstream bili's ingress decision (#300/#1086); they must survive transit. Their leak onto a terminal provider is harmless (providers ignore unknown headers; none carries a secret).
x-session-id forwarded unchanged intentional sticky-routing signal (#2218/#1931), out of scope here.

Why scoped to the plugin family (not all x-bili-*)

A namespace-wide strip was implemented first and broke x-bili-ws-lane transit (codex-responses-ws #3073: the shared probe/turn handshake lost the lane marker a chained bili keys off for chain-skip/self-loop detection). The plugin family is precisely the host-private convention metadata; the remaining x-bili-* are protocol/transit markers that must reach the next hop. Scoping to x-bili-plugin* closes the gap without touching chain/WS-lane detection.

Tests

New tests/issue3905-egress-bili-strip.test.ts pins both directions:

Verified RED→GREEN around the fix. Targeted local suites green: this file + hop-by-hop + issue1683-chain-egress-off + issue1086-chain-self-exemption + issue1542-chain-embedded-strip + codex-responses-ws + responses-ws-http-fallback; typecheck clean. Full-suite verdict is remote CI.

Fixes #3905

…3905)

The main HTTP forward path copied every inbound header to the upstream except
content-length/host, so all host-private x-bili-plugin* convention headers
(agent marker, conversation id, window/model declarations, and the declared-
tools channel from #3906/#3907) rode through to the model provider.

Add stripBiliPluginHeaders (src/server/headers.ts) and apply it at both outbound
model-endpoint derivations: the HTTP forward choke point (buildForwardTarget,
which every retry/refetch reuses) and the WS lane final egress
(finalizeResponsesWsUpstreamHeaders, non-Azure). The Azure WS branch keeps
stripping every x-bili-* for the documented custom-header cap (#2359).

The bili->bili protocol markers (x-bili-hop / x-bili-tunnel / x-bili-ws-lane)
are deliberately left untouched: their consumer is a downstream bili's ingress
decision (#300/#1086), so they must survive transit even though they leak
harmlessly onto a terminal provider. A namespace-wide strip was tried first and
broke x-bili-ws-lane transit (codex-responses-ws #3073), so the fix is scoped to
the plugin family only.

Regression-pinned both directions: host-private x-bili-plugin* no longer reach
the model endpoint (proxy + plugin lanes, incl. the new exposed-tools channel);
the chain markers still propagate; the WS lane presents the same surface.

Fixes #3905
@ranxianglei ranxianglei changed the title fix: stop leaking x-bili-plugin* host headers to the model endpoint (#3905) [PR] fix: stop leaking x-bili-plugin* host headers to the model endpoint (#3905) Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-20_egress-strip-bili-plugin-headers (9c17df1)

Option A — Install this exact build (recommended)

npm install -g [email protected]

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr3915.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm/vllm-qwen/qwen3.8-27b)

查重: 不重复。 本 PR 是 #3905(x-bili-plugin* 泄漏到模型端点)的唯一修复实现;open 的 #3906/#3907 是同族但不同机制(exposed-tools 新通道本身),#3920 等无关。无并列的 egress-strip PR。

标签: bug scope/wiring p3(一句话理由:host 私有约定元数据泄漏到第三方 provider,纯元数据卫生问题,无任何功能降级——provider 忽略未知头;修复面在对外 API 边界的出向头处理)。客户端标签不打:修复对所有 plugin lane 一视同仁,无特定客户端证据。

初审结论(进行中,最终八问随后):

  1. 方向正确:strip 打在两个出向模型端点派生点(HTTP buildForwardTarget + WS finalizeResponsesWsUpstreamHeaders),我核对了全部 4 个 buildForwardTarget 调用方(main forward / nudge decision / relay / reforge)与 WS 两条腿(handshake + HTTP fallback)都收敛到这两个函数;其余 req.headers 拷贝点(incoming dump、headerValue 查询)非出向。链式 bili 场景核对过:peer 带 x-bili-hop 进来直接 passthrough 不处理,不消费 plugin 头,stripping 无副作用。scope 限定在 plugin 家族(而非全 x-bili-*)的理由(codex WS 转发:每个 turn 都新开一条上游 WebSocket 连接(probe 与 turn 的连接键不一致) #3073 ws-lane 教训)成立。
  2. CI 红,且是本 PR 自己造成的:6 个 test shard 中 3 个失败,3 个 shard 失败的是同样的 3 个既有测试(不是 flake):
    • tests/responses-websocket.test.ts:92 — "azure strips consumed x-bili-* … other hosts unchanged":断言 non-Azure egress 保留 x-bili-plugin*(钉死旧泄漏行为);
    • tests/responses-websocket.test.ts:294 — incremental continuation:断言 upstream mock 收到 x-bili-plugin: opencode;
    • tests/plugin-bypass.test.ts:106 — 断言 x-bili-plugin-bypass 到达模型端点。
      这三处都是 PR 有意改变的行为的旧锚点,PR 更新了行为却没更新它们(作者本地跑的定向清单里恰好没有这两个文件)。修法明确:把断言翻到新契约(plugin 家族在 egress 被剥离)。
  3. 处置:按「小问题直接修」我直接在 PR 分支补一个标注 commit 修这 3 处断言,推上去看 CI;分支对当前 master merge-tree 干净(291 commits behind 但无冲突,不需要 rebase)。

稍后给完整八问。

#3915 intentionally stops forwarding x-bili-plugin* to model endpoints, but three
pre-existing tests still pinned the old leak behavior and turned CI red:

- responses-websocket.test.ts "egress headers": non-Azure egress asserted the
  x-bili-plugin* family arrives verbatim -> now asserts it is stripped (Azure's
  full x-bili-* strip #2359 unchanged); test name updated to match.
- responses-websocket.test.ts "incremental tool continuation": the #2359 control
  line asserted x-bili-plugin reaches a non-Azure mock -> now asserts it does not.
- plugin-bypass.test.ts "forwarded verbatim": asserted the bypass marker header
  reaches upstream; the marker is host-private convention metadata and is stripped
  at egress like the rest of the family (the body stays verbatim - tools/messages
  assertions unchanged).

Targeted local: these files + issue3905 + hop-by-hop + chain suites green,
typecheck clean; full-suite verdict = remote CI.
@ranxianglei ranxianglei added bug Something isn't working p3 scope/wiring labels Oct 20, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm/vllm-qwen/qwen3.8-27b)

审核结论:方向正确、实现完整、未发现回归;但 PR 漏更新了 3 个既有测试导致 CI 红——已按「小问题直接修」就地修复并推送追加 commit,等新轮 CI。

本轮做了什么

原 head 9326946fc 的 CI 红(3 个 shard × 相同 3 个失败,确定性而非 flake)全部是本 PR 的有意行为变更打穿了既有 pin、而 PR 未同步更新:

测试 旧断言(钉的是旧泄漏) #3905 之后
tests/responses-websocket.test.ts:92(egress headers) 非 Azure egress 仍逐字携带 x-bili-plugin* egress 剥离
tests/responses-websocket.test.ts:294(incremental continuation) #2359 control:非 Azure mock 收到 x-bili-plugin 收不到
tests/plugin-bypass.test.ts:106(bypass verbatim) bypass marker 头到达上游 marker 同属 plugin 家族被剥离(body 仍逐字,tools/messages 断言不动)

修法 = 追加 commit 9c17df1f4(test: 前缀,2 文件 +9/-8):断言翻转为新行为、测试标题同步改对(原标题写 "other hosts unchanged" 已与事实不符)、翻转处各留一行带 #3905 出处的注释。源码一行未动——PR 本身代码不需要改。

本地定向验证(全量交给 CI):上述 2 文件 + issue3905-egress-bili-strip = 48/48 绿;作者声称绿的 chain/WS 集合(hop-by-hop / issue1683-chain-egress-off / issue1086-chain-self-exemption / issue1542-chain-embedded-strip / codex-responses-ws / responses-ws-http-fallback)= 59/59 绿(本 head 复跑确认);typecheck 干净。push 已触发新一轮 CI,结果出来我补一条。

八问

  1. 是什么问题:所有 x-bili-plugin* 宿主私有约定头(agent marker、conversation id、window/model 声明,以及即将落地的 exposed-tools 通道)被原样转发到真实模型端点——对 provider 无意义,且新通道有 owner「不转发上游」约束。
  2. 什么原因导致的:buildForwardTarget 的拷贝语义是「除 content-length/host 外全转」,对宿主私有约定没有 egress 过滤;WS lane 非 Azure 分支同样照转。
  3. 影响范围与严重程度:p3——零功能影响(provider 忽略未知头、不带机密),但把内部约定元数据泄给第三方,且会在 Plugin lanes: host declares exposed bili tools per request via x-bili-plugin-exposed-tools (structural fix for #3884/#3600 name-guessing) | 插件车道:宿主按请求声明暴露的 bili 工具(根治 #3884/#3600 认名字脆弱性) #3906/[PR] feat: plugin lanes accept host declaration of exposed bili tools (#3906) #3907 落地时把新 exposed-tools 通道一并带上泄漏面,所以赶在它们之前修是必要的。
  4. 怎么修复的:单一收口过滤 stripBiliPluginHeaders()(前缀匹配 x-bili-plugin)打在两个 egress 推导点上——HTTP 主路 buildForwardTarget(覆盖全部 4 个调用点:主转发/nudge/relay/reforge)+ WS lane finalizeResponsesWsUpstreamHeaders 非 Azure 分支(Azure 保持 OpenCode V2 Azure Responses WebSocket bypasses native ACP interception; status can show stale compressed context #2359 的全量 x-bili-* 剥离不变)。范围刻意限定 plugin 家族而非整个命名空间——先做过全量剥离会断 x-bili-ws-lane 传递(codex WS 转发:每个 turn 都新开一条上游 WebSocket 连接(probe 与 turn 的连接键不一致) #3073 probe/turn 握手分歧)。
  5. 是否有回归问题:无。逐项清点既有行为:① bili→bili 链标记 hop/tunnel/ws-lane——传递保留,且核过 handle.ts 透传逻辑(带 hop 的入站请求本就不走本地压缩处理,下游 bili 消费不到 plugin 头,egress 剥离对链安全);② x-session-id 粘滞路由——affinity 从原始请求头读取,不受 egress 剥离影响;③ Azure WS OpenCode V2 Azure Responses WebSocket bypasses native ACP interception; status can show stale compressed context #2359——未动;④ 自环检测、embedded-chain strip(plugin-mode sessions: #1421 outbound chain stamps accumulate in the model-visible transcript (+1 user message per request) #1542)、chain egress-off([Bug]Chain-integrity checkpoint tag <bili-chain …/> is injected as a visible trailing user-role message — models read it as phantom user input and burn tokens commenting on it #1683)、self-exemption([Bug] 链检测把 bili 自己注入的 ACP 产物判成 bili→bili 链 —— 压缩内核永久跳过,长会话撞 1,048,576 上限 400(v0.1.133 回归) #1086)——复跑全绿;⑤ 高危模式(fail-fast 变吞错、阈值/时序漂移、输出格式变化)——均不涉及。
  6. 是否破坏缓存:不涉及——只删请求头,不碰 body/会话状态/持久化。
  7. 是否改了设计方向:无——纯 egress 卫生,plugin/proxy 两种压缩模式都已分别推演,边界未动。
  8. 是否引入新配置:无。

判定

代码质量达标(命名达意、双向测试纪律齐全、diff 干净每行都服务于目的);三处 stale pin 已就地修复并推送(9c17df1f4,additive commit,原作者 commit 与署名保留)。新一轮 CI 绿后即可合入——合并是人的事。一个小提醒:PR 描述 Tests 一节列的本地绿清单里没有 responses-websocket / plugin-bypass,正是漏掉它们的原因,合并时可补一句。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm/vllm-qwen/qwen3.8-27b)

CI 结果:head 9c17df1f4 全绿。 主 CI(6 个 test shard + static-gates 等门禁)success,ci-image / e2e-local-registry / e2e-pi-codex-ws / Theorem Lane / E2E (real codex) / e2e-fake-codex / e2e-dsh-plugin-lane / e2e-native-{opencode,dsh,pi} / e2e-codex-persona / Windows codex overlay 全部 success。

三处 stale pin 修复后全量回归通过,无其他旧测试钉住旧泄漏行为。PR 已就绪,等人工合并。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working p3 scope/wiring

Projects

None yet

1 participant