Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions dev/src/main/java/com/google/adk/web/config/AdkWebCorsConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package com.google.adk.web.config;

import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since we no longer need to inject the server port when using origin patterns, this import can be removed.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
Expand All @@ -30,35 +32,44 @@
* application. This class defines beans for configuring CORS settings based on properties defined
* in {@link AdkWebCorsProperties}.
*
* <p>CORS allows the application to handle requests from different origins, enabling secure
* communication between the frontend and backend services.
*
* <p>Beans provided:
*
* <ul>
* <li>{@link CorsConfigurationSource}: Configures CORS settings such as allowed origins, methods,
* headers, credentials, and max age.
* <li>{@link CorsFilter}: Applies the CORS configuration to incoming requests.
* </ul>
* <p>When no explicit origin allowlist is configured, this defaults to the server's own loopback
* origins (localhost / 127.0.0.1 / [::1] on the configured port) instead of allowing every origin,
* since allowing "*" by default lets any website a developer's browser visits call this server
* while it is running. Set 'adk.web.cors.origins' explicitly to widen this.
*/
@Configuration
public class AdkWebCorsConfig {

private static final Logger logger = LoggerFactory.getLogger(AdkWebCorsConfig.class);

@Value("${server.port:8000}")
private int serverPort;
Comment on lines +45 to +46

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With the transition to using setAllowedOriginPatterns with wildcards for the port, the serverPort field is no longer needed. Removing it avoids potential issues with dynamic ports (e.g., port 0 in test environments).


@Bean
public CorsConfigurationSource corsConfigurationSource(AdkWebCorsProperties corsProperties) {
if (corsProperties.origins().contains("*")) {
List<String> origins = corsProperties.origins();

if (origins.isEmpty()) {
origins =
List.of(
"http://localhost:" + serverPort,
"http://127.0.0.1:" + serverPort,
"http://[::1]:" + serverPort);
logger.info(
"No 'adk.web.cors.origins' configured; defaulting to this server's own loopback"
+ " origins on port {}. Set 'adk.web.cors.origins' explicitly to allow other"
+ " origins.",
serverPort);
} else if (origins.contains("*")) {
logger.warn(
"CORS is configured to allow all origins (\"*\"), which is insecure and intended for"
+ " local development only. This also applies to the /run_live WebSocket endpoint."
+ " Set 'adk.web.cors.origins' to an explicit allowlist to restrict which origins"
+ " may call the server.");
"CORS is configured to allow all origins (\"*\"), which is insecure and lets any"
+ " website call this server, including the /run_live WebSocket endpoint. Only"
+ " set this if you understand the risk.");
}

CorsConfiguration configuration = new CorsConfiguration();

configuration.setAllowedOrigins(corsProperties.origins());
configuration.setAllowedOrigins(origins);
Comment on lines +50 to +72

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of hardcoding the server port by injecting ${server.port}, we can use setAllowedOriginPatterns with wildcards for the port (e.g., http://localhost:*).

This approach has several advantages:

  1. Robustness: It works seamlessly even if the server is configured to run on a dynamic port (such as port 0 in test environments).
  2. Developer Experience: It automatically supports local frontend development servers running on different ports (e.g., React on 3000, Vite on 5173) without requiring manual CORS configuration.
  3. Security: It still restricts access strictly to the local machine (loopback), maintaining the security fix.
    List<String> origins = corsProperties.origins();
    CorsConfiguration configuration = new CorsConfiguration();

    if (origins.isEmpty()) {
      configuration.setAllowedOriginPatterns(
          List.of(
              "http://localhost:*",
              "http://127.0.0.1:*",
              "http://[::1]:*"));
      logger.info(
          "No 'adk.web.cors.origins' configured; defaulting to loopback origins on any port"
              + " (localhost / 127.0.0.1 / [::1]). Set 'adk.web.cors.origins' explicitly to"
              + " allow other origins.");
    } else {
      configuration.setAllowedOrigins(origins);
      if (origins.contains("*")) {
        logger.warn(
            "CORS is configured to allow all origins (\"*\"), which is insecure and lets any"
                + " website call this server, including the /run_live WebSocket endpoint. Only"
                + " set this if you understand the risk.");
      }
    }

configuration.setAllowedMethods(corsProperties.methods());
configuration.setAllowedHeaders(corsProperties.headers());
configuration.setAllowCredentials(corsProperties.allowCredentials());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ public record AdkWebCorsProperties(

public AdkWebCorsProperties {
mapping = mapping != null ? mapping : "/**";
origins = origins != null && !origins.isEmpty() ? origins : List.of("*");
// Leave unset origins empty rather than defaulting to "*": AdkWebCorsConfig computes a
// same-machine loopback default from the server's own port when this is empty, so every
// origin on the internet is not allowed to call the server out of the box.
Comment on lines +37 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the comment to reflect that the default loopback origins are allowed on any port using origin patterns, rather than being bound to the server's own port.

Suggested change
// Leave unset origins empty rather than defaulting to "*": AdkWebCorsConfig computes a
// same-machine loopback default from the server's own port when this is empty, so every
// origin on the internet is not allowed to call the server out of the box.
// Leave unset origins empty rather than defaulting to "*": AdkWebCorsConfig computes a
// same-machine loopback default on any port when this is empty, so every
// origin on the internet is not allowed to call the server out of the box.

origins = origins != null ? origins : List.of();
methods =
methods != null && !methods.isEmpty()
? methods
Expand Down
Loading