Skip to content

fix(gateway): harden CORS / Host header / startup host binding#39

Open
YOMXXX wants to merge 1 commit into
Tencent:mainfrom
YOMXXX:fix/gateway-cors-host
Open

fix(gateway): harden CORS / Host header / startup host binding#39
YOMXXX wants to merge 1 commit into
Tencent:mainfrom
YOMXXX:fix/gateway-cors-host

Conversation

@YOMXXX
Copy link
Copy Markdown

@YOMXXX YOMXXX commented May 18, 2026

Description | 描述

TDAI Gateway 的 HTTP 层和启动入口有三处独立于任何在飞 feature 的安全缺陷,在当前 main 上已经可利用。本 PR 全部修复。

Three security holes in the TDAI Gateway HTTP layer and startup path that exist independently on main today, all fixed here.

Bugs | 问题

1. CORS hardcoded Access-Control-Allow-Origin: * (src/gateway/server.ts:177-186)

handleRequest 无条件下发 Access-Control-Allow-Origin: * 并对所有 OPTIONS 返回 204。daemon 默认绑 127.0.0.1,但浏览器通过 DNS rebinding(攻击者域名 evil.com 短 TTL DNS → 解析为 127.0.0.1)发起的 fetch 直接通过 —— 加上 #2,daemon 数据端点对恶意浏览器页面开放。

handleRequest unconditionally emitted Access-Control-Allow-Origin: * and acked all OPTIONS with 204. Although the daemon binds loopback by default, a malicious page using DNS rebinding (short-TTL evil.com → 127.0.0.1) can issue fetch() and the daemon accepts — combined with #2 this exposes data endpoints to any browser tab.

2. Host header unvalidated (src/gateway/server.ts:172-174)

req.headers.host 不校验,配合 #1 形成 DNS rebinding 攻击面。

req.headers.host was not validated; pairs with #1 to enable DNS rebinding.

3. server.ts main() did not validate TDAI_GATEWAY_HOST (src/gateway/server.ts:500-512)

tsx src/gateway/server.ts 直接启动路径不校验 TDAI_GATEWAY_HOST。环境里若设了 TDAI_GATEWAY_HOST=0.0.0.0,daemon 静默绑全网卡,把记忆数据端口暴露到 LAN。

The auto-start path (tsx src/gateway/server.ts) bound whatever TDAI_GATEWAY_HOST was set in env — a stray 0.0.0.0 would silently expose the daemon to the LAN.

Fix | 修复

  • CORS opt-in: Access-Control-Allow-* 头仅在 TDAI_GATEWAY_CORS_ORIGIN 显式设置时下发;OPTIONS preflight 仅在该模式下返回 204。默认状态下 OPTIONS 落入正常路由 404,不再作为永久未鉴权 probe 端点。
  • Host header 白名单: handleRequest 在路由前校验 Host header,剥离端口/IPv6 括号后必须在 {127.0.0.1, localhost, ::1, ::ffff:127.0.0.1},否则 403。TDAI_GATEWAY_ALLOW_REMOTE=1 时跳过。
  • assertSafeHost() helper: server.ts main() 入口在构造 Gateway 前调用,非 loopback TDAI_GATEWAY_HOST 需要 TDAI_GATEWAY_ALLOW_REMOTE=1 opt-in 才允许绑定,否则 exit(2)。

Tests | 测试

新建 src/gateway/__tests__/cors-host.test.ts(17 个 case):

  • CORS opt-in: 默认不下发 / OPTIONS 不再 204 / 设置 env 后下发对应 Origin / OPTIONS 此时返回 204 (4 cases)
  • Host 白名单 accept: 127.0.0.1, 127.0.0.1:8421, localhost, localhost:8421, [::1], [::1]:8421 (6 cases)
  • Host 白名单 reject: evil.com, evil.com:8421, 10.0.0.1, example.com, 127.0.0.1.evil.com, localhost.evil.com → 403 (6 cases)
  • TDAI_GATEWAY_ALLOW_REMOTE=1 跳过 Host 校验 (1 case)
✓ npx vitest run src/gateway  → 17/17 passed

Out of scope | 范围外

不影响 main 上已有功能;与正在 review 的 cc 集成 PR (#7) 解耦。本 PR 修的是 main 上 server.ts 既有 bug,与 #7 无 commit 依赖。

Independent of the in-flight cc-integration PR (#7) — this PR fixes pre-existing server.ts bugs on main and does not touch any file introduced by #7.

DCO

Commit 2f5b919Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>

TDAI Gateway has three CORS/Host/startup-safety holes that pre-date any
cc-integration work and are independently exploitable on main today:

1. handleRequest unconditionally emitted Access-Control-Allow-Origin: *
   and acked OPTIONS preflight with 204. Combined with no Host-header
   validation, any browser page (via DNS rebinding evil.com -> 127.0.0.1)
   can talk to the daemon's data endpoints.
2. req.headers.host was never validated. Pairs with #1 to make DNS
   rebinding viable.
3. server.ts main() (the `tsx src/gateway/server.ts` auto-start path)
   never validated TDAI_GATEWAY_HOST, so an env-set
   `TDAI_GATEWAY_HOST=0.0.0.0` would silently bind the daemon to all
   interfaces.

Fixes:

- CORS is now opt-in: Access-Control-Allow-* headers are emitted only
  when TDAI_GATEWAY_CORS_ORIGIN is explicitly set, and OPTIONS preflight
  only returns 204 in that mode. Default state is no CORS at all — the
  daemon has no legitimate cross-origin browser use case.
- handleRequest now rejects requests whose Host header (after stripping
  port / IPv6 brackets) is not in
  {127.0.0.1, localhost, ::1, ::ffff:127.0.0.1}. Set
  TDAI_GATEWAY_ALLOW_REMOTE=1 to opt in (matching the new
  assertSafeHost() convention).
- server.ts main() now calls assertSafeHost() before constructing the
  gateway, refusing to bind a non-loopback TDAI_GATEWAY_HOST unless
  TDAI_GATEWAY_ALLOW_REMOTE=1 is set.

Tests: new src/gateway/__tests__/cors-host.test.ts — 17 cases covering
CORS opt-in (4), Host allowlist accept (6) / reject (6), and ALLOW_REMOTE
opt-in (1). All passing.

Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>
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.

1 participant