Google 开源的 Java 智能体框架 ADK Java 在 1.13.0(2026-10-21)里收紧了 dev 服务器:跨域请求默认只放同源,还新增了一道 DNS rebinding 防护。用同一份启动代码分别跑 1.12.0 和 1.13.0,带陌生 Origin 的请求从 200 加 Access-Control-Allow-Origin: * 变成了 403。dev README 同时写明,AdkWebServer.start() 和 main() 不设 server.address,这时 Host 检查不开启:实测进程监听在所有地址上,只伪造 Host、不带 Origin 的 GET 仍然回 200,按 README 设上 server.address=127.0.0.1 后才被拦下。

项目仓库:github.com/google/adk-java,许可证 Apache License 2.0。dev 模块是 com.google.adk:google-adk-dev,1.13.0 依赖 Spring Boot 4.0.2。上一个版本的工作流图改动见之前的 ADK Java 1.12.0 介绍。

主源:

安全公告状态:这两条列在 release notes 的 Bug Fixes 下,没有对应 CVE 编号;GitHub 仓库的 security advisories 接口返回 0 条,官方也没有给严重级别。

改了什么

1.12.0 的 AdkWebCorsProperties 在没配 adk.web.cors.origins 时回落成 *。5667d38 把默认值改成空列表,也就是只放同源,REST API 和 /run_live WebSocket 都一样。dev UI 由服务自己托管,属于同源,不用额外配置。另起端口跑的前端要显式列出来:

adk.web.cors.origins=http://localhost:4200

5667d38 还把 Forwarded / X-Forwarded-* 默认按 server.forward-headers-strategy=framework 处理。提交说明和 README 都提醒,反向代理要剥掉或覆盖不可信客户端带来的这些头,也可以设成 none 忽略它们。

1ae5078 新增了 OriginGuardFilter。README 里写了两条规则:server.address 是回环地址时,拒绝非回环的 Host;请求经回环到达时,拒绝非回环的 Origin。adk.web.cors.origins 里列出的来源及其主机不受限,配成 * 则两条都关掉。mvn google-adk:web 默认绑回环;AdkWebServer.start() 和 main() 不设 server.address,README 让这种情况自己设 server.address=127.0.0.1 来打开 Host 检查。

对照环境

JDK 21.0.12.1,Maven 项目只依赖 google-adk 和 google-adk-dev,用 -Dadk.version 切 1.12.0 / 1.13.0。启动方式是 README 提到的 AdkWebServer.start(),不调用模型,只请求 /list-apps:

LlmAgent agent = LlmAgent.builder()
        .name("hello_agent")
        .description("Demo agent for the dev server")
        .model("gemini-2.5-flash")
        .instruction("Reply briefly.")
        .build();
AdkWebServer.start(agent);

端口固定 18085,五种请求依次记为 A 到 E:

# A 不带 Origin
curl -s -D - http://127.0.0.1:18085/list-apps
# B 跨域 Origin
curl -s -D - -H 'Origin: http://evil.example' http://127.0.0.1:18085/list-apps
# C 伪造 Host,Origin 与之一致
curl -s -D - -H 'Host: rebind.example:18085' -H 'Origin: http://rebind.example:18085' http://127.0.0.1:18085/list-apps
# D 只伪造 Host,不带 Origin
curl -s -D - -H 'Host: rebind.example:18085' http://127.0.0.1:18085/list-apps
# E 另起前端的来源
curl -s -D - -H 'Origin: http://localhost:4200' http://127.0.0.1:18085/list-apps

C、D 模拟 DNS rebinding:恶意域名被解析到本机后,浏览器发出的 Host 是那个域名,页面和服务算「同源」,CORS 本身拦不住。下面的输出由脚本整理,每条只保留状态行、Access-Control-Allow-Origin 头和响应体。

1.12.0:五种请求全部放行

摘录:

--- B cross-origin Origin
HTTP/1.1 200
Access-Control-Allow-Origin: *
body: ["hello_agent"]
--- C rebinding Host+Origin
HTTP/1.1 200
body: ["hello_agent"]

五种请求全部 200,跨域请求直接拿到 Access-Control-Allow-Origin: *。

1.13.0 默认:带 Origin 的被拦,不带 Origin 的取决于监听地址

摘录:

--- B cross-origin Origin
HTTP/1.1 403
body: Invalid CORS request
--- C rebinding Host+Origin
HTTP/1.1 403
body: Forbidden: origin not allowed
--- D rebinding Host, no Origin
HTTP/1.1 200
body: ["hello_agent"]

B 的响应体 Invalid CORS request 来自 Spring 的 CORS 处理。C 的 Host 和 Origin 一致,CORS 认为是同源,是新加的 OriginGuardFilter 按「经回环到达、Origin 非回环」这条规则拦下的。D 只改了 Host、没带 Origin,第二条规则管不到,仍是 200。按 MDN 对 Origin 请求头的说明,浏览器对同源的 GET、HEAD 请求一般不附加 Origin,D 对应的就是这种请求形态。

此时进程的监听地址:

LISTEN 0      100                *:18085            *:*

没有设 server.address,Tomcat 绑在所有地址上,Host 检查按设计不开启,这和 README 对 AdkWebServer.start() 的描述一致。

1.13.0 加 server.address=127.0.0.1

摘录:

--- C rebinding Host+Origin
HTTP/1.1 403
body: Forbidden: host not allowed
--- D rebinding Host, no Origin
HTTP/1.1 403
body: Forbidden: host not allowed

监听地址变成:

LISTEN 0      100    [::ffff:127.0.0.1]:18085            *:*

C 和 D 都在 Host 这一步被拦,正常的 127.0.0.1 请求(A)仍然 200。

另起前端要配白名单

1.13.0 默认下,Origin: http://localhost:4200 也是 403 Invalid CORS request。加上 -Dadk.web.cors.origins=http://localhost:4200 之后:

--- E allowed dev UI origin
HTTP/1.1 200
Access-Control-Allow-Origin: http://localhost:4200
body: ["hello_agent"]

白名单只放行列出的来源,evil.example 照样 403。

升级时要改的地方

  • 代码里用 AdkWebServer.start() 起 dev 服务的,按 README 加 server.address=127.0.0.1(实测用的是系统属性 -Dserver.address=127.0.0.1),Host 检查才会打开。
  • 前端单独跑在 4200 之类端口的,把来源写进 adk.web.cors.origins。README 建议避免 *,配了两道检查都会关掉。
  • 用 -Dhost=0.0.0.0 启动的,1ae5078 的提交说明要求改用 http://localhost:{port} 打开 UI,http://0.0.0.0 这个 origin 现在会被拒。
  • 放在反向代理后面的,确认代理会剥掉或覆盖客户端带来的 Forwarded / X-Forwarded-*,因为 5667d38 之后默认会应用这些头。
— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。