Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
268920f
feat(http): Apply cookie collection policy
adinauer Jul 22, 2026
1987b1e
fix(core): Filter malformed cookie pairs
adinauer Jul 23, 2026
9da9baa
test(okhttp): Return headers from response mocks
adinauer Jul 24, 2026
ccfe195
Merge branch 'feat/data-collection-query-params' into feat/data-colle…
adinauer Aug 10, 2026
d7a900d
Merge branch 'feat/data-collection-query-params' into feat/data-colle…
adinauer Aug 10, 2026
501c268
merge: Update Data Collection stack
adinauer Aug 24, 2026
988579b
Merge branch 'feat/data-collection-query-params' into feat/data-colle…
adinauer Aug 27, 2026
c4e29eb
Merge branch 'feat/data-collection-query-params' into feat/data-colle…
adinauer Aug 27, 2026
b9eeeb5
Merge branch 'feat/data-collection-query-params' into feat/data-colle…
adinauer Aug 28, 2026
71619ec
Merge branch 'feat/data-collection-query-params' into feat/data-colle…
adinauer Aug 31, 2026
a81ffe7
fix(core): Reject malformed cookie pairs
adinauer Sep 2, 2026
6cdd40e
chore: Merge data collection base into cookie filtering
adinauer Sep 2, 2026
489982d
fix(core): Skip null filtered cookie headers
adinauer Sep 7, 2026
6900fba
fix(core): Preserve blank cookie segments
adinauer Sep 7, 2026
3315b18
ref(core): Remove broad cookie filtering catches
adinauer Sep 7, 2026
b11dcdd
ref(core): Extract cookie utilities from HTTP utilities
adinauer Sep 7, 2026
6a03c1f
chore: Merge Data Collection branch updates
adinauer Sep 7, 2026
33078e4
test(okhttp): Use valid Set-Cookie fixture
adinauer Sep 7, 2026
069d598
chore: Merge Data Collection stack updates
adinauer Sep 8, 2026
2434ba0
ref(core): Centralize HTTP client cookie filtering
adinauer Sep 8, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import io.sentry.exception.ExceptionMechanismException
import io.sentry.protocol.Mechanism
import io.sentry.protocol.Request
import io.sentry.protocol.Response
import io.sentry.util.CookieUtils
import io.sentry.util.GraphqlUtils
import io.sentry.util.HttpUtils
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
Expand Down Expand Up @@ -391,9 +392,7 @@ constructor(
val sentryRequest =
Request().apply {
urlDetails.applyToRequest(this)
// Cookie is only sent if isSendDefaultPii is enabled
cookies =
if (scopes.options.isSendDefaultPii) getHeader("Cookie", request.headers) else null
cookies = CookieUtils.filterCookies(getHeader("Cookie", request.headers), scopes.options)
method = request.method.name
headers = getRequestHeaders(request.headers)
apiTarget = "graphql"
Expand All @@ -419,13 +418,8 @@ constructor(

val sentryResponse =
Response().apply {
// Set-Cookie is only sent if isSendDefaultPii is enabled due to PII
cookies =
if (scopes.options.isSendDefaultPii) {
getHeader("Set-Cookie", response.headers)
} else {
null
}
CookieUtils.filterSetCookie(getHeader("Set-Cookie", response.headers), scopes.options)
headers = getResponseHeaders(response.headers)
statusCode = response.statusCode

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SentryApollo3InterceptorClientErrors {
httpStatusCode: Int = 200,
responseBody: String = responseBodyOk,
sendDefaultPii: Boolean = false,
includeCookies: Boolean = sendDefaultPii,
socketPolicy: SocketPolicy = SocketPolicy.KEEP_OPEN,
configureOptions: SentryOptions.() -> Unit = {},
): ApolloClient {
Expand All @@ -98,8 +99,8 @@ class SentryApollo3InterceptorClientErrors {
.setSocketPolicy(socketPolicy)
.setResponseCode(httpStatusCode)

if (sendDefaultPii) {
response.addHeader("Set-Cookie", "Test")
if (includeCookies) {
response.addHeader("Set-Cookie", "theme=dark; Path=/")
}

server.enqueue(response)
Expand All @@ -112,8 +113,8 @@ class SentryApollo3InterceptorClientErrors {
captureFailedRequests = captureFailedRequests,
failedRequestTargets = failedRequestTargets,
)
if (sendDefaultPii) {
builder.addHttpHeader("Cookie", "Test")
if (includeCookies) {
builder.addHttpHeader("Cookie", "theme=dark; sessionId=secret")
}

return builder.build()
Expand Down Expand Up @@ -362,6 +363,46 @@ class SentryApollo3InterceptorClientErrors {
)
}

@Test
fun `data collection filters cookies`() {
val sut =
fixture.getSut(responseBody = fixture.responseBodyNotOk, includeCookies = true) {
dataCollection.cookies = KeyValueCollectionBehavior.denyList("theme")
}
executeQuery(sut)

verify(fixture.scopes)
.captureEvent(
check {
assertEquals("theme=[Filtered]; sessionId=[Filtered]", it.request!!.cookies)
assertEquals("theme=[Filtered]; Path=/", it.contexts.response!!.cookies)
},
any<Hint>(),
)
}

@Test
fun `data collection can disable cookies`() {
val sut =
fixture.getSut(
responseBody = fixture.responseBodyNotOk,
sendDefaultPii = true,
includeCookies = true,
) {
dataCollection.cookies = KeyValueCollectionBehavior.off()
}
executeQuery(sut)

verify(fixture.scopes)
.captureEvent(
check {
assertNull(it.request!!.cookies)
assertNull(it.contexts.response!!.cookies)
},
any<Hint>(),
)
}

@Test
fun `data collection can disable request headers`() {
val sut =
Expand All @@ -387,7 +428,7 @@ class SentryApollo3InterceptorClientErrors {
check {
val request = it.request!!

assertEquals("Test", request.cookies)
assertEquals("theme=dark; sessionId=secret", request.cookies)
assertNotNull(request.headers)
assertEquals("LaunchDetails", request.headers?.get("X-APOLLO-OPERATION-NAME"))
},
Expand Down Expand Up @@ -477,7 +518,7 @@ class SentryApollo3InterceptorClientErrors {
check {
val response = it.contexts.response!!

assertEquals("Test", response.cookies)
assertEquals("theme=dark; Path=/", response.cookies)
assertNotNull(response.headers)
assertEquals(200, response.headers?.get("Content-Length")?.toInt())
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import io.sentry.exception.ExceptionMechanismException
import io.sentry.protocol.Mechanism
import io.sentry.protocol.Request
import io.sentry.protocol.Response
import io.sentry.util.CookieUtils
import io.sentry.util.GraphqlUtils
import io.sentry.util.HttpUtils
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
Expand Down Expand Up @@ -390,9 +391,7 @@ constructor(
val sentryRequest =
Request().apply {
urlDetails.applyToRequest(this)
// Cookie is only sent if isSendDefaultPii is enabled
cookies =
if (scopes.options.isSendDefaultPii) getHeader("Cookie", request.headers) else null
cookies = CookieUtils.filterCookies(getHeader("Cookie", request.headers), scopes.options)
method = request.method.name
headers = getRequestHeaders(request.headers)
apiTarget = "graphql"
Expand All @@ -418,13 +417,8 @@ constructor(

val sentryResponse =
Response().apply {
// Set-Cookie is only sent if isSendDefaultPii is enabled due to PII
cookies =
if (scopes.options.isSendDefaultPii) {
getHeader("Set-Cookie", response.headers)
} else {
null
}
CookieUtils.filterSetCookie(getHeader("Set-Cookie", response.headers), scopes.options)
headers = getResponseHeaders(response.headers)
statusCode = response.statusCode

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ abstract class SentryApollo4BuilderExtensionsClientErrorsTest(
httpStatusCode: Int = 200,
responseBody: String = responseBodyOk,
sendDefaultPii: Boolean = false,
includeCookies: Boolean = sendDefaultPii,
socketPolicy: SocketPolicy = SocketPolicy.KEEP_OPEN,
configureOptions: SentryOptions.() -> Unit = {},
): ApolloClient {
Expand All @@ -112,8 +113,8 @@ abstract class SentryApollo4BuilderExtensionsClientErrorsTest(
.setSocketPolicy(socketPolicy)
.setResponseCode(httpStatusCode)

if (sendDefaultPii) {
response.addHeader("Set-Cookie", "Test")
if (includeCookies) {
response.addHeader("Set-Cookie", "theme=dark; Path=/")
}

server.enqueue(response)
Expand All @@ -126,8 +127,8 @@ abstract class SentryApollo4BuilderExtensionsClientErrorsTest(
captureFailedRequests = captureFailedRequests,
failedRequestTargets = failedRequestTargets,
)
if (sendDefaultPii) {
builder.addHttpHeader("Cookie", "Test")
if (includeCookies) {
builder.addHttpHeader("Cookie", "theme=dark; sessionId=secret")
}

return builder.build()
Expand Down Expand Up @@ -356,6 +357,46 @@ abstract class SentryApollo4BuilderExtensionsClientErrorsTest(
)
}

@Test
fun `data collection filters cookies`() {
val sut =
fixture.getSut(responseBody = fixture.responseBodyNotOk, includeCookies = true) {
dataCollection.cookies = KeyValueCollectionBehavior.denyList("theme")
}
executeQuery(sut)

verify(fixture.scopes)
.captureEvent(
check {
assertEquals("theme=[Filtered]; sessionId=[Filtered]", it.request!!.cookies)
assertEquals("theme=[Filtered]; Path=/", it.contexts.response!!.cookies)
},
any<Hint>(),
)
}

@Test
fun `data collection can disable cookies`() {
val sut =
fixture.getSut(
responseBody = fixture.responseBodyNotOk,
sendDefaultPii = true,
includeCookies = true,
) {
dataCollection.cookies = KeyValueCollectionBehavior.off()
}
executeQuery(sut)

verify(fixture.scopes)
.captureEvent(
check {
assertNull(it.request!!.cookies)
assertNull(it.contexts.response!!.cookies)
},
any<Hint>(),
)
}

@Test
fun `data collection filters request headers`() {
val sut =
Expand Down Expand Up @@ -398,7 +439,7 @@ abstract class SentryApollo4BuilderExtensionsClientErrorsTest(
check {
val request = it.request!!

assertEquals("Test", request.cookies)
assertEquals("theme=dark; sessionId=secret", request.cookies)
assertNotNull(request.headers)
},
any<Hint>(),
Expand Down Expand Up @@ -487,7 +528,7 @@ abstract class SentryApollo4BuilderExtensionsClientErrorsTest(
check {
val response = it.contexts.response!!

assertEquals("Test", response.cookies)
assertEquals("theme=dark; Path=/", response.cookies)
assertNotNull(response.headers)
assertEquals(200, response.headers?.get("Content-Length")?.toInt())
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.sentry.TypeCheckHint
import io.sentry.exception.ExceptionMechanismException
import io.sentry.exception.SentryHttpClientException
import io.sentry.protocol.Mechanism
import io.sentry.util.CookieUtils
import io.sentry.util.HttpUtils
import io.sentry.util.UrlUtils

Expand All @@ -36,18 +37,16 @@ internal object SentryKtorClientUtils {

val sentryRequest =
io.sentry.protocol.Request().apply {
// Cookie is only sent if isSendDefaultPii is enabled
urlDetails.applyToRequest(this)
cookies = if (scopes.options.isSendDefaultPii) request.headers["Cookie"] else null
cookies = CookieUtils.filterCookies(request.headers["Cookie"], scopes.options)
method = request.method.value
headers = getRequestHeaders(scopes, request.headers)
bodySize = request.content.contentLength
}

val sentryResponse =
io.sentry.protocol.Response().apply {
// Set-Cookie is only sent if isSendDefaultPii is enabled due to PII
cookies = if (scopes.options.isSendDefaultPii) response.headers["Set-Cookie"] else null
cookies = CookieUtils.filterSetCookie(response.headers["Set-Cookie"], scopes.options)
headers = getResponseHeaders(scopes, response.headers)
statusCode = response.status.value
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class SentryKtorClientPluginTest {
MockResponse()
.setBody(responseBody)
.addHeader("myResponseHeader", "myValue")
.addHeader("Set-Cookie", "theme=dark; Path=/")
.setSocketPolicy(socketPolicy)
.setResponseCode(httpStatusCode)
)
Expand Down Expand Up @@ -256,6 +257,60 @@ class SentryKtorClientPluginTest {
verify(fixture.scopes, never()).captureEvent(any(), any<Hint>())
}

@Test
fun `data collection filters cookies`(): Unit = runBlocking {
val sut =
fixture.getSut(
captureFailedRequests = true,
httpStatusCode = 500,
optionsConfiguration =
Sentry.OptionsConfiguration {
it.dataCollection.cookies = KeyValueCollectionBehavior.denyList("theme")
},
)

sut.get(fixture.server.url("/hello").toString()) {
headers["Cookie"] = "language=en; theme=dark; sessionId=secret"
}

verify(fixture.scopes)
.captureEvent(
check<SentryEvent> {
assertEquals(
"language=en; theme=[Filtered]; sessionId=[Filtered]",
it.request!!.cookies,
)
assertEquals("theme=[Filtered]; Path=/", it.contexts.response!!.cookies)
},
any<Hint>(),
)
}

@Test
fun `data collection can disable cookies`(): Unit = runBlocking {
val sut =
fixture.getSut(
captureFailedRequests = true,
httpStatusCode = 500,
sendDefaultPii = true,
optionsConfiguration =
Sentry.OptionsConfiguration {
it.dataCollection.cookies = KeyValueCollectionBehavior.off()
},
)

sut.get(fixture.server.url("/hello").toString()) { headers["Cookie"] = "theme=dark" }

verify(fixture.scopes)
.captureEvent(
check<SentryEvent> {
assertNull(it.request!!.cookies)
assertNull(it.contexts.response!!.cookies)
},
any<Hint>(),
)
}

@Test
fun `data collection filters request headers`(): Unit = runBlocking {
val sut =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.sentry.TypeCheckHint
import io.sentry.exception.ExceptionMechanismException
import io.sentry.exception.SentryHttpClientException
import io.sentry.protocol.Mechanism
import io.sentry.util.CookieUtils
import io.sentry.util.HttpUtils
import io.sentry.util.UrlUtils
import okhttp3.Headers
Expand Down Expand Up @@ -37,8 +38,7 @@ internal object SentryOkHttpUtils {
val sentryRequest =
io.sentry.protocol.Request().apply {
urlDetails.applyToRequest(this)
// Cookie is only sent if isSendDefaultPii is enabled
cookies = if (scopes.options.isSendDefaultPii) request.headers["Cookie"] else null
cookies = CookieUtils.filterCookies(request.headers["Cookie"], scopes.options)
method = request.method
headers = getRequestHeaders(scopes, request.headers)

Expand All @@ -47,8 +47,7 @@ internal object SentryOkHttpUtils {

val sentryResponse =
io.sentry.protocol.Response().apply {
// Set-Cookie is only sent if isSendDefaultPii is enabled due to PII
cookies = if (scopes.options.isSendDefaultPii) response.headers["Set-Cookie"] else null
cookies = CookieUtils.filterSetCookie(response.headers["Set-Cookie"], scopes.options)
headers = getResponseHeaders(scopes, response.headers)
statusCode = response.code

Expand Down
Loading
Loading