Skip to content

[PR] fix: stop the welcome 302 and favicon routes hijacking MITM-vhost requests (#5024) - #5035

Open
ranxianglei wants to merge 4 commits into
masterfrom
2026-10-28_welcome-host-gate-5024
Open

ranxianglei wants to merge 4 commits into
masterfrom
2026-10-28_welcome-host-gate-5024

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #5024

What was wrong

The gateway's local convenience routes matched on path alone, with no check on which host the request was addressed to. In MITM mode a decrypted vhost request re-enters the same HTTP server carrying the UPSTREAM vhost's Host header, so:

  • GET https://chatgpt.com/ (bare root, browser HTML Accept) → 302 /__bili/ — real browser navigation hijacked onto the local admin UI;
  • GET / through the tunnel with Accept: */* → the gateway's {ok:true, upstream} health JSON instead of the site;
  • (same mechanism, found during analysis) GET /favicon.ico from a vhost → answered with bili's bundled icon bytes instead of forwarding to the vhost's own favicon.

Fix

Gate both routes (GET / welcome branch + GET /favicon.ico) in src/server/admin.ts on the request being addressed to the gateway itself, reusing the existing identity judgment — isTrustedAdminOrigin(req.headers.origin, req.headers.host, adminTrustedHostnames(opts.host)) — i.e. the same trusted-hostname discipline as the /__bili/ origin gate (#1537): loopback names (localhost / 127.0.0.1 / ::1) + the concrete bind host, port-insensitive so SSH port forwards keep working. Foreign-Host requests fall through to the normal forward path, exactly like every other path.

The issue's suggested regex was the alternative considered; reusing the helper keeps one definition of "addressed to us" and inherits the #1537 SSH-forward / IPv6-bracket semantics for free.

Behavior changes (old → new)

Request Old New
MITM vhost GET /, HTML Accept 302 → /__bili/ (admin UI rendered on the vhost) forwarded upstream (the fix)
MITM vhost GET /, other Accept {ok:true, upstream} JSON forwarded upstream (the fix)
MITM vhost GET /favicon.ico bili's bundled icon bytes forwarded upstream (same mechanism)
Non-self Host at gateway root (e.g. --host 0.0.0.0 LAN peer browsing http://<lan-ip>:8787/, or a loopback hostname alias like http://mybox.local:8787/) 302 → /__bili/ → 403 page (the alias fails the admin origin gate) falls through to the forward path like every other path
No Host header at all (raw clients) JSON ok:true at / falls through to the forward path
Self-addressed requests (localhost / 127.0.0.1 / [::1] / concrete bind host, any local port incl. SSH forwards) 302 or JSON health / favicon icon unchanged — pinned by tests in both directions

No config surface change, no kernel change, no wire/message-shape change, no new env vars.

Verification

@ranxianglei ranxianglei changed the title fix: stop the welcome 302 and favicon routes hijacking MITM-vhost requests (#5024) [PR] fix: stop the welcome 302 and favicon routes hijacking MITM-vhost requests (#5024) Oct 28, 2026
…uests (#5024)

The GET / welcome branch and the /favicon.ico local answer matched on path
alone, so a MITM-decrypted vhost request re-entering the same handler with
the upstream vhost's Host header (GET https://chatgpt.com/ => Host:
chatgpt.com) was 302'd onto the local admin UI, answered with the {ok:true}
health JSON, or served bili's bundled icon instead of being forwarded.

Gate both routes on the request being addressed to the gateway itself via
isTrustedAdminOrigin + adminTrustedHostnames - the same identity judgment as
the /__bili/ origin gate (#1537): loopback names + concrete bind host,
port-insensitive so SSH forwards keep working. Foreign-Host requests fall
through to the normal forward path like every other path.

Regression: tests/issue5024-welcome-host-gate.test.ts - 7 cases over the
full proxy on real TCP; all three fixed facets fail on the unfixed code.
@ranxianglei
ranxianglei force-pushed the 2026-10-28_welcome-host-gate-5024 branch from 4432c5e to a130185 Compare October 28, 2026 11:03
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-28_welcome-host-gate-5024 (5d36cf8)

Latest build — install this exact version

npm install -g billion-context@0.0.7402-pr.5035.653bd8b

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

Recent builds (commit → version)

commit version build
5d36cf8 billion-context@0.0.7402-pr.5035.653bd8b build
50a7bae billion-context@0.0.7174-pr.5035.4d77c47 build
dcd6ad7 billion-context@0.0.6889-pr.5035.0635f90 build
a130185 billion-context@0.0.6641-pr.5035.d98545d build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr5035.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-qwen/qwen3.8-27b)

查重:不重复。 本 PR 即 #5024 的修复本身(Closes #5024);检索 open/closed issue 与近期 PR,无其他针对「welcome/favicon 路由缺 Host 判定」的在途修复。
标签: bug scope/wiring p2(请求管道路由缺陷;有绕行、触发面窄——仅 MITM 模式裸根路径浏览器导航)。注:本环境镜像未实现 labels API,标签在此记录备查。

独立审核结论:代码面通过。 我没有照抄 PR 描述与 #5024 triage 的结论,以下各项均自行重验:

① 方向关(原 issue):根因判断成立且修到了根机制——两个本地便利路由只按 path 匹配、不识别请求寻址对象;MITM 解密 socket 经 src/mitm.ts:302 server.emit("connection", tlsSocket) 重新注入同一个 HTTP server,内层请求携上游 vhost 的 Host 原样进入 handleAdminRoute。修法是在决策点补上「寻址到网关自身」这一维度(复用 #1537 的 isTrustedAdminOrigin + adminTrustedHostnames),不是在某层过滤症状——换一种外部 Host 形态进来,这道门依然成立。helper 语义我逐行核过:无 Origin 的自寻址请求(curl/浏览器导航)放行 ✓;外部 Host 拒绝 ✓;端口无关保留 SSH 转发语义 ✓。

② 回归审查(既有行为逐项清点):

  • 自寻址请求(localhost / 127.0.0.1 / [::1] / 具体 bind host,任意本地端口含 SSH 转发):302 welcome、JSON health、favicon 本地应答——全部不变,测试双向钉住;
  • /__bili/* 管理面三道门(tunnel marker / loopback / trusted-origin,admin.ts:213-230)未触碰;
  • 落穿路径核实:gate 不命中 → handle.ts:285 之后 res.writableEnded 为假 → 正常 forward pipeline,与其他所有 path 一致;
  • 有意变更(PR 已披露旧值→新值,逐项审过均合理):MITM vhost 根路径 HTML-Accept 由 302→改转发(本修复);同根路径 Accept:/ 由 JSON→改转发(本修复);vhost favicon 由 bili 图标字节→改转发(同机制);非 loopback 名访问网关根(LAN peer/主机名别名)由 302→403 死胡同页→落穿转发(旧行为本就是坏体验,新行为对齐其余 path);无 Host 头裸客户端由 JSON ok→落穿转发(边缘情形,已披露)。无 fail-fast 变吞错、无阈值/时序/输出格式漂移。

③ 代码审查:复用既有身份判定而非新增机制;命名达意(addressedToGateway);注释讲为什么(#5024/#1537 背景);无死代码、无新依赖、无魔法值。通过。

④ 测试有效性:新套件 7 case 断言有牙(具体状态码 + content-type + upstream 命中计数 + body 内容),不是恒真断言。红→绿我亲自复验:临时摘掉两处 gate 后恰好 3 个「fixed」case 失败(302 劫持 / JSON 兜底 / favicon 泄漏)、4 个「preserved」case 仍过,恢复后 7/7——该 bug 回归时它抓得住。相邻定向套件本地 39/39 通过(issue3536-favicon-local · mitm · issue3343-marker-divert-gate · issue3245-zcode-self-tunnel · dsh-panel-embed-cors)。

⑤ diff 整洁度:2 文件 1 commit,每行都与本修复相关,无无关改动/锁文件/生成物。通过。

结构化八问:

  1. 是什么问题:MITM 模式下浏览器访问被拦截域名的裸根路径会被 302 劫持到本机管理 UI(Accept:/ 时拿到网关健康 JSON 而非站点页面;favicon 探测被本机图标应答)。
  2. 什么原因导致的:网关的两个本地便利路由只按 path 匹配、不识别请求寻址到谁;MITM 内层请求携带上游 vhost 的 Host 头进入同一 handler,path 撞上而 Host 无人问津。
  3. 影响范围与严重程度:仅 MITM 模式 + 白名单域名裸根路径的浏览器导航及 favicon 探测;用户看到错误页面而非报错,模型流量不受影响,加任意 path/query 可绕行。p2。
  4. 怎么修复的:给两个分支补上「寻址到网关自身」判定,复用 /__bili/ origin 门已有的 trusted-hostname 纪律(loopback 名 + 具体 bind host、端口无关兼容 SSH 转发);外部 Host 落回正常 forward 路径。
  5. 是否有回归问题:无。自寻址语义严格保持(双向测试钉住);管理面 gate 未动;有意变更均已披露且合理(见上表)。
  6. 是否破坏缓存:不涉及。
  7. 是否改了设计方向:没有——给既有本地路由补缺失的寻址维度,复用现有判定,不新增机制。
  8. 是否引入了新的配置:没有。

合并前待办(唯一拦点,非代码问题):当前 PR head(4432c5e50)上的 CI runs 全部 cancelled,且 GitHub 显示分支 behind master(rebase 基 b08fbf3 之后 master 又前进了)——没有一份有效绿 CI。按 §7.2 需 rebase 到最新 master 后以新 head 的 CI 为准。我正尝试 rebase+push 触发新一轮 CI,但此刻出网代理不稳定(连接重置/CONNECT aborted),成功后回帖更新结果;若持续不通,请 owner 侧知悉此状态后再行合并。

判定:代码审核通过,可合入条件 = rebase 后 CI 绿。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

已同步 origin/master(09a5b5b)进本分支,无冲突,新 head dcd6ad792;本地 typecheck/build 绿、本 PR 定向测试 7/7 绿;CI 11/12 lane 绿,3 个红分片均为 master 新测试 #5140 的单用例继承 flake(plugin-fork.test.ts:1473,409!==200,含剔除本 PR 改动后的复现证据,详见 #4599),失败分片已重跑。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

再次同步 origin/master(44067a5,+124 commits),无冲突,新 head 50a7bae32;本地 build 绿、本 PR 定向测试 7/7 绿,且随本次同步带入 master 的 #5188 round-3(snapshot/fork 409 路径修复)后,此前 3 个红分片的唯一失败用例 #5140 本地 3/3 绿。CI 已触发但整队处于 fleet 级 runner 排队(12/12 run queued),等队列消化后出结果。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)

收工摘要:已两次把 origin/master 同步进本分支(最终含 44067a5,+124 commits),均无冲突,新 head 50a7bae32,分支与 master 零落后。本地验证:build 绿、本 PR 定向测试 7/7 绿;此前 3 个红分片的唯一失败(master 新测试 #5140 的 409!==200 flake)随本次同步带入的 #5188 round-3 修复后本地 3/3 绿。CI 已触发但整队处于 fleet runner 排队中,矩阵结果待队列消化后确认;过程证据见 #4599 两条评论。

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GET / welcome 302 hijacks MITM-vhost browser navigation to /__bili/ (no Host check) | MITM 域名根路径被劫持到管理 UI

1 participant