diff --git a/dev/src/main/java/com/google/adk/web/config/AdkWebCorsProperties.java b/dev/src/main/java/com/google/adk/web/config/AdkWebCorsProperties.java index c96dce91e..0343f7d58 100644 --- a/dev/src/main/java/com/google/adk/web/config/AdkWebCorsProperties.java +++ b/dev/src/main/java/com/google/adk/web/config/AdkWebCorsProperties.java @@ -34,7 +34,7 @@ public record AdkWebCorsProperties( public AdkWebCorsProperties { mapping = mapping != null ? mapping : "/**"; - origins = origins != null && !origins.isEmpty() ? origins : List.of("*"); + origins = origins != null ? origins : List.of(); methods = methods != null && !methods.isEmpty() ? methods diff --git a/dev/src/main/java/com/google/adk/web/websocket/WebSocketConfig.java b/dev/src/main/java/com/google/adk/web/websocket/WebSocketConfig.java index a17d72aff..a5b9043fc 100644 --- a/dev/src/main/java/com/google/adk/web/websocket/WebSocketConfig.java +++ b/dev/src/main/java/com/google/adk/web/websocket/WebSocketConfig.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistration; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** Configuration class for WebSocket handling. */ @@ -40,8 +41,10 @@ public WebSocketConfig( @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry - .addHandler(liveWebSocketHandler, "/run_live") - .setAllowedOrigins(corsProperties.origins().toArray(new String[0])); + WebSocketHandlerRegistration registration = + registry.addHandler(liveWebSocketHandler, "/run_live"); + if (!corsProperties.origins().isEmpty()) { + registration.setAllowedOrigins(corsProperties.origins().toArray(String[]::new)); + } } } diff --git a/dev/src/test/java/com/google/adk/web/config/AdkWebCorsConfigTest.java b/dev/src/test/java/com/google/adk/web/config/AdkWebCorsConfigTest.java new file mode 100644 index 000000000..8b1e26170 --- /dev/null +++ b/dev/src/test/java/com/google/adk/web/config/AdkWebCorsConfigTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.web.config; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; + +class AdkWebCorsConfigTest { + + private final AdkWebCorsConfig config = new AdkWebCorsConfig(); + + @Test + void defaultCorsConfigurationDoesNotAllowArbitraryOrigins() { + CorsConfiguration corsConfiguration = + getCorsConfiguration(new AdkWebCorsProperties(null, null, null, null, false, 0)); + + assertThat(corsConfiguration.checkOrigin("https://attacker.example")).isNull(); + } + + @Test + void explicitCorsOriginsAreStillAllowed() { + CorsConfiguration corsConfiguration = + getCorsConfiguration( + new AdkWebCorsProperties(null, List.of("http://localhost:3000"), null, null, false, 0)); + + assertThat(corsConfiguration.checkOrigin("http://localhost:3000")) + .isEqualTo("http://localhost:3000"); + assertThat(corsConfiguration.checkOrigin("https://attacker.example")).isNull(); + } + + private CorsConfiguration getCorsConfiguration(AdkWebCorsProperties properties) { + CorsConfigurationSource source = config.corsConfigurationSource(properties); + CorsConfiguration corsConfiguration = + source.getCorsConfiguration(new MockHttpServletRequest("POST", "/run")); + assertThat(corsConfiguration).isNotNull(); + return corsConfiguration; + } +} diff --git a/dev/src/test/java/com/google/adk/web/websocket/WebSocketConfigTest.java b/dev/src/test/java/com/google/adk/web/websocket/WebSocketConfigTest.java new file mode 100644 index 000000000..6fa3482bf --- /dev/null +++ b/dev/src/test/java/com/google/adk/web/websocket/WebSocketConfigTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.adk.web.websocket; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.adk.web.config.AdkWebCorsProperties; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistration; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +class WebSocketConfigTest { + + @Test + void defaultWebSocketConfigurationDoesNotAllowArbitraryOrigins() { + LiveWebSocketHandler handler = mock(LiveWebSocketHandler.class); + WebSocketHandlerRegistry registry = mock(WebSocketHandlerRegistry.class); + WebSocketHandlerRegistration registration = mock(WebSocketHandlerRegistration.class); + when(registry.addHandler(handler, "/run_live")).thenReturn(registration); + + new WebSocketConfig(handler, new AdkWebCorsProperties(null, null, null, null, false, 0)) + .registerWebSocketHandlers(registry); + + verify(registration, never()).setAllowedOrigins("*"); + } + + @Test + void explicitCorsOriginsAreAppliedToWebSocketEndpoint() { + LiveWebSocketHandler handler = mock(LiveWebSocketHandler.class); + WebSocketHandlerRegistry registry = mock(WebSocketHandlerRegistry.class); + WebSocketHandlerRegistration registration = mock(WebSocketHandlerRegistration.class); + when(registry.addHandler(handler, "/run_live")).thenReturn(registration); + + new WebSocketConfig( + handler, + new AdkWebCorsProperties(null, List.of("http://localhost:3000"), null, null, false, 0)) + .registerWebSocketHandlers(registry); + + verify(registration).setAllowedOrigins("http://localhost:3000"); + } +}