From d17418d5cb09773571349ae9541c0d2b24b08dda Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 17 Jul 2026 12:06:38 +0200 Subject: [PATCH] feat(spring): Apply incoming request body policy Use the Data Collection incoming request body decision for servlet request caching and event body extraction across all Spring variants. Keep existing body size, content length, and MIME type limits while preserving sendDefaultPii behavior when Data Collection is absent. Co-Authored-By: Claude --- .../io/sentry/spring7/SentrySpringFilter.java | 4 +- .../sentry/spring7/SentrySpringFilterTest.kt | 47 +++++++++++++++++++ .../spring/jakarta/SentrySpringFilter.java | 4 +- .../spring/jakarta/SentrySpringFilterTest.kt | 47 +++++++++++++++++++ .../io/sentry/spring/SentrySpringFilter.java | 4 +- .../sentry/spring/SentrySpringFilterTest.kt | 47 +++++++++++++++++++ 6 files changed, 147 insertions(+), 6 deletions(-) diff --git a/sentry-spring-7/src/main/java/io/sentry/spring7/SentrySpringFilter.java b/sentry-spring-7/src/main/java/io/sentry/spring7/SentrySpringFilter.java index 38bc4379088..bf2c431a179 100644 --- a/sentry-spring-7/src/main/java/io/sentry/spring7/SentrySpringFilter.java +++ b/sentry-spring-7/src/main/java/io/sentry/spring7/SentrySpringFilter.java @@ -110,7 +110,7 @@ private void configureScope( private @NotNull HttpServletRequest resolveHttpServletRequest( final @NotNull IScopes scopes, final @NotNull HttpServletRequest request) { - if (scopes.getOptions().isSendDefaultPii() + if (scopes.getOptions().getDataCollectionResolver().isIncomingRequestBody() && qualifiesForCaching(request, scopes.getOptions().getMaxRequestBodySize())) { return new ContentCachingRequestWrapper(request, 0); } @@ -155,7 +155,7 @@ public RequestBodyExtractingEventProcessor( @Override public @NotNull SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { if (event.getRequest() != null - && options.isSendDefaultPii() + && options.getDataCollectionResolver().isIncomingRequestBody() && qualifiesForCaching(request, options.getMaxRequestBodySize())) { event.getRequest().setData(requestPayloadExtractor.extract(request, options)); } diff --git a/sentry-spring-7/src/test/kotlin/io/sentry/spring7/SentrySpringFilterTest.kt b/sentry-spring-7/src/test/kotlin/io/sentry/spring7/SentrySpringFilterTest.kt index 5a83c9d72a4..532b3c686b2 100644 --- a/sentry-spring-7/src/test/kotlin/io/sentry/spring7/SentrySpringFilterTest.kt +++ b/sentry-spring-7/src/test/kotlin/io/sentry/spring7/SentrySpringFilterTest.kt @@ -1,6 +1,7 @@ package io.sentry.spring7 import io.sentry.Breadcrumb +import io.sentry.HttpBodyType import io.sentry.IScope import io.sentry.IScopes import io.sentry.ISentryLifecycleToken @@ -318,6 +319,52 @@ class SentrySpringFilterTest { } } + @Test + fun `data collection can enable request body when sendDefaultPii is false`() { + val options = + SentryOptions().apply { + isSendDefaultPii = false + maxRequestBodySize = SMALL + dataCollection.httpBodies = setOf(HttpBodyType.INCOMING_REQUEST) + } + val listener = + fixture.getSut( + request = + MockMvcRequestBuilders.post(URI.create("http://example.com")) + .content("xxx") + .contentType("application/json") + .buildRequest(MockServletContext()), + options = options, + ) + + listener.doFilter(fixture.request, fixture.response, fixture.chain) + + verify(fixture.chain).doFilter(check { assertTrue(it is ContentCachingRequestWrapper) }, any()) + } + + @Test + fun `data collection can disable request body when sendDefaultPii is true`() { + val options = + SentryOptions().apply { + isSendDefaultPii = true + maxRequestBodySize = SMALL + dataCollection.httpBodies = emptySet() + } + val listener = + fixture.getSut( + request = + MockMvcRequestBuilders.post(URI.create("http://example.com")) + .content("xxx") + .contentType("application/json") + .buildRequest(MockServletContext()), + options = options, + ) + + listener.doFilter(fixture.request, fixture.response, fixture.chain) + + verify(fixture.chain).doFilter(check { assertFalse(it is ContentCachingRequestWrapper) }, any()) + } + private fun servletContextWithCustomCookieName(name: String): ServletContext = MockServletContext().also { it.sessionCookieConfig.name = name } } diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentrySpringFilter.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentrySpringFilter.java index c51a2053b8d..c549223e559 100644 --- a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentrySpringFilter.java +++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentrySpringFilter.java @@ -110,7 +110,7 @@ private void configureScope( private @NotNull HttpServletRequest resolveHttpServletRequest( final @NotNull IScopes scopes, final @NotNull HttpServletRequest request) { - if (scopes.getOptions().isSendDefaultPii() + if (scopes.getOptions().getDataCollectionResolver().isIncomingRequestBody() && qualifiesForCaching(request, scopes.getOptions().getMaxRequestBodySize())) { return new ContentCachingRequestWrapper(request); } @@ -155,7 +155,7 @@ public RequestBodyExtractingEventProcessor( @Override public @NotNull SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { if (event.getRequest() != null - && options.isSendDefaultPii() + && options.getDataCollectionResolver().isIncomingRequestBody() && qualifiesForCaching(request, options.getMaxRequestBodySize())) { event.getRequest().setData(requestPayloadExtractor.extract(request, options)); } diff --git a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SentrySpringFilterTest.kt b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SentrySpringFilterTest.kt index 349839b5d15..ad6c01e99d1 100644 --- a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SentrySpringFilterTest.kt +++ b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SentrySpringFilterTest.kt @@ -1,6 +1,7 @@ package io.sentry.spring.jakarta import io.sentry.Breadcrumb +import io.sentry.HttpBodyType import io.sentry.IScope import io.sentry.IScopes import io.sentry.ISentryLifecycleToken @@ -318,6 +319,52 @@ class SentrySpringFilterTest { } } + @Test + fun `data collection can enable request body when sendDefaultPii is false`() { + val options = + SentryOptions().apply { + isSendDefaultPii = false + maxRequestBodySize = SMALL + dataCollection.httpBodies = setOf(HttpBodyType.INCOMING_REQUEST) + } + val listener = + fixture.getSut( + request = + MockMvcRequestBuilders.post(URI.create("http://example.com")) + .content("xxx") + .contentType("application/json") + .buildRequest(MockServletContext()), + options = options, + ) + + listener.doFilter(fixture.request, fixture.response, fixture.chain) + + verify(fixture.chain).doFilter(check { assertTrue(it is ContentCachingRequestWrapper) }, any()) + } + + @Test + fun `data collection can disable request body when sendDefaultPii is true`() { + val options = + SentryOptions().apply { + isSendDefaultPii = true + maxRequestBodySize = SMALL + dataCollection.httpBodies = emptySet() + } + val listener = + fixture.getSut( + request = + MockMvcRequestBuilders.post(URI.create("http://example.com")) + .content("xxx") + .contentType("application/json") + .buildRequest(MockServletContext()), + options = options, + ) + + listener.doFilter(fixture.request, fixture.response, fixture.chain) + + verify(fixture.chain).doFilter(check { assertFalse(it is ContentCachingRequestWrapper) }, any()) + } + private fun servletContextWithCustomCookieName(name: String): ServletContext = MockServletContext().also { it.sessionCookieConfig.name = name } } diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentrySpringFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentrySpringFilter.java index 69438c82617..3fe8ab9e13f 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentrySpringFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentrySpringFilter.java @@ -110,7 +110,7 @@ private void configureScope( private @NotNull HttpServletRequest resolveHttpServletRequest( final @NotNull IScopes scopes, final @NotNull HttpServletRequest request) { - if (scopes.getOptions().isSendDefaultPii() + if (scopes.getOptions().getDataCollectionResolver().isIncomingRequestBody() && qualifiesForCaching(request, scopes.getOptions().getMaxRequestBodySize())) { return new ContentCachingRequestWrapper(request); } @@ -155,7 +155,7 @@ public RequestBodyExtractingEventProcessor( @Override public @NotNull SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { if (event.getRequest() != null - && options.isSendDefaultPii() + && options.getDataCollectionResolver().isIncomingRequestBody() && qualifiesForCaching(request, options.getMaxRequestBodySize())) { event.getRequest().setData(requestPayloadExtractor.extract(request, options)); } diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringFilterTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringFilterTest.kt index eb145bcd8a1..cfc5042dc58 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringFilterTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringFilterTest.kt @@ -1,6 +1,7 @@ package io.sentry.spring import io.sentry.Breadcrumb +import io.sentry.HttpBodyType import io.sentry.IScope import io.sentry.IScopes import io.sentry.ISentryLifecycleToken @@ -318,6 +319,52 @@ class SentrySpringFilterTest { } } + @Test + fun `data collection can enable request body when sendDefaultPii is false`() { + val options = + SentryOptions().apply { + isSendDefaultPii = false + maxRequestBodySize = SMALL + dataCollection.httpBodies = setOf(HttpBodyType.INCOMING_REQUEST) + } + val listener = + fixture.getSut( + request = + MockMvcRequestBuilders.post(URI.create("http://example.com")) + .content("xxx") + .contentType("application/json") + .buildRequest(MockServletContext()), + options = options, + ) + + listener.doFilter(fixture.request, fixture.response, fixture.chain) + + verify(fixture.chain).doFilter(check { assertTrue(it is ContentCachingRequestWrapper) }, any()) + } + + @Test + fun `data collection can disable request body when sendDefaultPii is true`() { + val options = + SentryOptions().apply { + isSendDefaultPii = true + maxRequestBodySize = SMALL + dataCollection.httpBodies = emptySet() + } + val listener = + fixture.getSut( + request = + MockMvcRequestBuilders.post(URI.create("http://example.com")) + .content("xxx") + .contentType("application/json") + .buildRequest(MockServletContext()), + options = options, + ) + + listener.doFilter(fixture.request, fixture.response, fixture.chain) + + verify(fixture.chain).doFilter(check { assertFalse(it is ContentCachingRequestWrapper) }, any()) + } + private fun servletContextWithCustomCookieName(name: String): ServletContext = MockServletContext().also { it.sessionCookieConfig.name = name } }