Skip to content

Commit 7ec4b8e

Browse files
Mateusz Krawieccopybara-github
authored andcommitted
fix(dev): allow only same-origin requests by default for CORS and /run_live
- A UI served from another origin now needs `adk.web.cors.origins`, for example `adk.web.cors.origins=http://localhost:4200`. - The server now applies `Forwarded` and `X-Forwarded-*` headers by default. Make sure your proxy strips them from untrusted clients, or set `server.forward-headers-strategy=none`. PiperOrigin-RevId: 997788515
1 parent 833cbd5 commit 7ec4b8e

7 files changed

Lines changed: 358 additions & 36 deletions

File tree

‎dev/README.md‎

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,33 @@ a relative path like `/my-app` cannot be used. If the application also sets
3535
instead of prepending the context path or `X-Forwarded-Prefix`.
3636

3737
When `adk.web.backend-url` is unset, `/` and `/dev-ui` redirect to `/dev-ui/`
38-
and the bundled `backendUrl` value is preserved. If you instead enable
39-
`server.forward-headers-strategy=framework`, make sure your reverse proxy strips
40-
or overwrites incoming `Forwarded` and `X-Forwarded-*` headers from untrusted
41-
clients.
38+
and the bundled `backendUrl` value is preserved.
39+
40+
The server applies `Forwarded` and `X-Forwarded-*` headers to the request URL by
41+
default, as with `server.forward-headers-strategy=framework`. Make sure your
42+
reverse proxy strips or overwrites these headers from untrusted clients. Set
43+
`server.forward-headers-strategy=none` to ignore them, or `native` to let the
44+
web server handle them instead. Behind a proxy that terminates TLS, keep them
45+
on: with `none`, a page served over plain HTTP under the same host name passes
46+
the same-origin check.
47+
48+
## Cross-origin requests
49+
50+
By default, the REST API and the `/run_live` WebSocket accept only same-origin
51+
requests. The dev UI served by the server itself is same-origin and needs no
52+
configuration.
53+
54+
To allow other origins, for example a UI served by a separate development
55+
server, set `adk.web.cors.origins` to a comma-separated list of origins:
56+
57+
```properties
58+
adk.web.cors.origins=http://localhost:4200
59+
```
60+
61+
Behind a reverse proxy, the same-origin check uses the URL that the proxy
62+
reports in forwarded headers, so the dev UI keeps working behind a proxy that
63+
terminates TLS. A proxy on a non-default port must include the port in its
64+
forwarded headers. List the external origin in `adk.web.cors.origins` if the
65+
proxy does not report the external URL, or if it reports `ws` or `wss` as the
66+
scheme of WebSocket requests (as Traefik does). Avoid `*`: it lets any website
67+
open in your browser call the server.

‎dev/src/main/java/com/google/adk/web/config/AdkWebCorsConfig.java‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616

1717
package com.google.adk.web.config;
1818

19+
import jakarta.servlet.DispatcherType;
1920
import org.slf4j.Logger;
2021
import org.slf4j.LoggerFactory;
22+
import org.springframework.beans.factory.ObjectProvider;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
24+
import org.springframework.boot.web.server.autoconfigure.servlet.ForwardedHeaderFilterCustomizer;
25+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
2126
import org.springframework.context.annotation.Bean;
2227
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.core.Ordered;
2329
import org.springframework.web.cors.CorsConfiguration;
2430
import org.springframework.web.cors.CorsConfigurationSource;
2531
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
2632
import org.springframework.web.filter.CorsFilter;
33+
import org.springframework.web.filter.ForwardedHeaderFilter;
2734

2835
/**
2936
* Configuration class for setting up Cross-Origin Resource Sharing (CORS) in the ADK Web
@@ -39,6 +46,8 @@
3946
* <li>{@link CorsConfigurationSource}: Configures CORS settings such as allowed origins, methods,
4047
* headers, credentials, and max age.
4148
* <li>{@link CorsFilter}: Applies the CORS configuration to incoming requests.
49+
* <li>{@link ForwardedHeaderFilter}: Applies reverse proxy headers to the URL of every request,
50+
* so the same-origin checks see the URL the browser used.
4251
* </ul>
4352
*/
4453
@Configuration
@@ -50,10 +59,10 @@ public class AdkWebCorsConfig {
5059
public CorsConfigurationSource corsConfigurationSource(AdkWebCorsProperties corsProperties) {
5160
if (corsProperties.origins().contains("*")) {
5261
logger.warn(
53-
"CORS is configured to allow all origins (\"*\"), which is insecure and intended for"
54-
+ " local development only. This also applies to the /run_live WebSocket endpoint."
55-
+ " Set 'adk.web.cors.origins' to an explicit allowlist to restrict which origins"
56-
+ " may call the server.");
62+
"CORS is configured to allow all origins (\"*\"), so any website open in a browser can"
63+
+ " call this server, including the /run_live WebSocket endpoint. Set"
64+
+ " 'adk.web.cors.origins' to an explicit allowlist to restrict which origins may"
65+
+ " call the server.");
5766
}
5867

5968
CorsConfiguration configuration = new CorsConfiguration();
@@ -74,4 +83,26 @@ public CorsConfigurationSource corsConfigurationSource(AdkWebCorsProperties cors
7483
public CorsFilter corsFilter(CorsConfigurationSource corsConfigurationSource) {
7584
return new CorsFilter(corsConfigurationSource);
7685
}
86+
87+
/**
88+
* Makes {@code server.forward-headers-strategy=framework} the default, so same-origin checks see
89+
* the URL the browser used behind a TLS-terminating proxy. Registered like Spring Boot's own
90+
* filter, which then backs off; set the property to {@code native} or {@code none} to opt out.
91+
*/
92+
@Bean
93+
@ConditionalOnProperty(
94+
name = "server.forward-headers-strategy",
95+
havingValue = "framework",
96+
matchIfMissing = true)
97+
FilterRegistrationBean<ForwardedHeaderFilter> adkForwardedHeaderFilter(
98+
ObjectProvider<ForwardedHeaderFilterCustomizer> customizers) {
99+
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
100+
customizers.orderedStream().forEach(customizer -> customizer.customize(filter));
101+
FilterRegistrationBean<ForwardedHeaderFilter> registration =
102+
new FilterRegistrationBean<>(filter);
103+
registration.setDispatcherTypes(
104+
DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
105+
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
106+
return registration;
107+
}
77108
}

‎dev/src/main/java/com/google/adk/web/config/AdkWebCorsProperties.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121

2222
/**
2323
* Properties for configuring CORS in ADK Web. This class is used to load CORS settings from
24-
* application properties.
24+
* application properties. {@code origins} defaults to an empty list, which allows only same-origin
25+
* requests.
2526
*/
2627
@ConfigurationProperties(prefix = "adk.web.cors")
2728
public record AdkWebCorsProperties(
@@ -34,7 +35,7 @@ public record AdkWebCorsProperties(
3435

3536
public AdkWebCorsProperties {
3637
mapping = mapping != null ? mapping : "/**";
37-
origins = origins != null && !origins.isEmpty() ? origins : List.of("*");
38+
origins = origins != null ? origins : List.of();
3839
methods =
3940
methods != null && !methods.isEmpty()
4041
? methods

0 commit comments

Comments
 (0)