From 0ca2c392447bcc25b1562c45effb62c31082cc74 Mon Sep 17 00:00:00 2001 From: Robert Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:12:56 -0500 Subject: [PATCH] Ignore Chrome DevTools well-known probe in default RequestCache Chrome DevTools itself (not the loaded page, and not the user) requests /.well-known/appspecific/com.chrome.devtools.json when DevTools is open against a page, as part of the Automatic Workspace Folders feature, to map a local project folder for live source editing. This request is tooling-driven, not user navigation, so it should never be able to become the URL a user is redirected to after authenticating. Closes gh-19694 --- .../web/configurers/RequestCacheConfigurer.java | 5 ++++- .../configurers/RequestCacheConfigurerTests.java | 14 ++++++++++++++ .../savedrequest/CookieServerRequestCache.java | 5 ++++- .../savedrequest/WebSessionServerRequestCache.java | 5 ++++- .../CookieServerRequestCacheTests.java | 10 ++++++++++ .../WebSessionServerRequestCacheTests.java | 10 ++++++++++ 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java index 4002a3b9f4..da949c8830 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java @@ -188,7 +188,10 @@ public final class RequestCacheConfigurer> "/manifest.json", "/manifest.webmanifest", // Legacy IE11/Edge fetch browserconfig.xml in the background to // configure pinned-site tiles - "/browserconfig.xml" }; + "/browserconfig.xml", + // Chrome DevTools itself (not the page, not the user) requests this to + // discover an Automatic Workspace Folder mapping for local source editing + "/.well-known/appspecific/com.chrome.devtools.json" }; private RequestMatcher getIgnoredBackgroundRequestMatcher() { List matchers = new ArrayList<>(); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java index 48c7304ea3..905bb7f99d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java @@ -200,6 +200,20 @@ public class RequestCacheConfigurerTests { this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } + @Test + public void getWhenBookmarkedUrlIsChromeDevtoolsJsonThenPostAuthenticationRedirectsToRoot() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + // @formatter:off + MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/.well-known/appspecific/com.chrome.devtools.json")) + .andExpect(redirectedUrl("/login")) + .andReturn() + .getRequest() + .getSession(); + // @formatter:on + // ignores /.well-known/appspecific/com.chrome.devtools.json + this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); + } + // SEC-2321 @Test public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception { diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java index 4361bf7dc3..e52a50ee49 100644 --- a/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java @@ -174,7 +174,10 @@ public class CookieServerRequestCache implements ServerRequestCache { "/manifest.json", "/manifest.webmanifest", // Legacy IE11/Edge fetch browserconfig.xml in the background to // configure pinned-site tiles - "/browserconfig.xml" }; + "/browserconfig.xml", + // Chrome DevTools itself (not the page, not the user) requests this to + // discover an Automatic Workspace Folder mapping for local source editing + "/.well-known/appspecific/com.chrome.devtools.json" }; private static ServerWebExchangeMatcher createDefaultRequestMatcher() { ServerWebExchangeMatcher get = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/**"); diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java index b3f72559ae..9944d2cb07 100644 --- a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java @@ -174,7 +174,10 @@ public class WebSessionServerRequestCache implements ServerRequestCache { "/manifest.json", "/manifest.webmanifest", // Legacy IE11/Edge fetch browserconfig.xml in the background to // configure pinned-site tiles - "/browserconfig.xml" }; + "/browserconfig.xml", + // Chrome DevTools itself (not the page, not the user) requests this to + // discover an Automatic Workspace Folder mapping for local source editing + "/.well-known/appspecific/com.chrome.devtools.json" }; private static ServerWebExchangeMatcher createDefaultRequestMatcher() { ServerWebExchangeMatcher get = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/**"); diff --git a/web/src/test/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCacheTests.java b/web/src/test/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCacheTests.java index efe80eb252..ea045d1acb 100644 --- a/web/src/test/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCacheTests.java +++ b/web/src/test/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCacheTests.java @@ -131,6 +131,16 @@ public class CookieServerRequestCacheTests { assertThat(cookies).isEmpty(); } + @Test + public void saveRequestWhenGetRequestChromeDevtoolsJsonThenNoCookie() { + MockServerWebExchange exchange = MockServerWebExchange + .from(MockServerHttpRequest.get("/.well-known/appspecific/com.chrome.devtools.json") + .accept(MediaType.TEXT_HTML)); + this.cache.saveRequest(exchange).block(); + MultiValueMap cookies = exchange.getResponse().getCookies(); + assertThat(cookies).isEmpty(); + } + @Test public void saveRequestWhenPostRequestThenNoCookie() { MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/secured/")); diff --git a/web/src/test/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCacheTests.java b/web/src/test/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCacheTests.java index 7acb99943a..d2b7c5446d 100644 --- a/web/src/test/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCacheTests.java +++ b/web/src/test/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCacheTests.java @@ -122,6 +122,16 @@ public class WebSessionServerRequestCacheTests { assertThat(saved).isNull(); } + @Test + public void saveRequestGetRequestWhenChromeDevtoolsJsonThenNotFound() { + MockServerWebExchange exchange = MockServerWebExchange + .from(MockServerHttpRequest.get("/.well-known/appspecific/com.chrome.devtools.json") + .accept(MediaType.TEXT_HTML)); + this.cache.saveRequest(exchange).block(); + URI saved = this.cache.getRedirectUri(exchange).block(); + assertThat(saved).isNull(); + } + @Test public void saveRequestGetRequestWhenPostThenNotFound() { MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/secured/"));