From e28c00c9ba237cc8a52f0628c6042a838782af00 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 4 Aug 2025 18:33:51 +0200 Subject: [PATCH 1/3] Improve SentryTraceHeader constructor parameter validation --- .../java/io/sentry/SentryTraceHeader.java | 27 ++++--- .../java/io/sentry/SentryTraceHeaderTest.kt | 75 +++++++++++++++++++ 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryTraceHeader.java b/sentry/src/main/java/io/sentry/SentryTraceHeader.java index 49ffe25e414..813fc617154 100644 --- a/sentry/src/main/java/io/sentry/SentryTraceHeader.java +++ b/sentry/src/main/java/io/sentry/SentryTraceHeader.java @@ -2,6 +2,8 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -13,6 +15,11 @@ public final class SentryTraceHeader { private final @NotNull SpanId spanId; private final @Nullable Boolean sampled; + final Pattern SENTRY_TRACEPARENT_HEADER_REGEX = + Pattern.compile( + "^[ \\t]*(?[0-9a-f]{32})-(?[0-9a-f]{16})-?(?[01])?[ \\t]*$", + Pattern.CASE_INSENSITIVE); + public SentryTraceHeader( final @NotNull SentryId traceId, final @NotNull SpanId spanId, @@ -23,20 +30,16 @@ public SentryTraceHeader( } public SentryTraceHeader(final @NotNull String value) throws InvalidSentryTraceHeaderException { - final String[] parts = value.split("-", -1); - if (parts.length < 2) { + Matcher matcher = SENTRY_TRACEPARENT_HEADER_REGEX.matcher(value); + boolean matchesExist = matcher.matches(); + + if (!matchesExist || matcher.group("traceId") == null || matcher.group("spanId") == null) { throw new InvalidSentryTraceHeaderException(value); - } else if (parts.length == 3) { - this.sampled = "1".equals(parts[2]); - } else { - this.sampled = null; - } - try { - this.traceId = new SentryId(parts[0]); - this.spanId = new SpanId(parts[1]); - } catch (Throwable e) { - throw new InvalidSentryTraceHeaderException(value, e); } + + this.traceId = new SentryId(matcher.group("traceId")); + this.spanId = new SpanId(matcher.group("spanId")); + this.sampled = matcher.group("sampled") == null ? null : "1".equals(matcher.group("sampled")); } public @NotNull String getName() { diff --git a/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt b/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt index ac01309ee22..0233238a63c 100644 --- a/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt @@ -6,6 +6,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNull +import kotlin.text.substring class SentryTraceHeaderTest { @Test @@ -15,6 +16,80 @@ class SentryTraceHeaderTest { assertEquals("sentry-trace header does not conform to expected format: $sentryId", ex.message) } + @Test + fun `when trace-id has less than 32 characters throws exception`() { + val sentryId = SentryId().toString().substring(0, 8) + val spanId = SpanId() + val ex = + assertFailsWith { SentryTraceHeader("$sentryId-$spanId") } + assertEquals( + "sentry-trace header does not conform to expected format: $sentryId-$spanId", + ex.message, + ) + } + + @Test + fun `when trace-id has more than 32 characters throws exception`() { + val sentryId = SentryId().toString() + "abc" + val spanId = SpanId() + val ex = + assertFailsWith { SentryTraceHeader("$sentryId-$spanId") } + assertEquals( + "sentry-trace header does not conform to expected format: $sentryId-$spanId", + ex.message, + ) + } + + @Test + fun `when trace-id contains invalid characters throws exception`() { + var sentryId = SentryId().toString() + sentryId = sentryId.substring(0, 8) + "g" + sentryId.substring(8) + val spanId = SpanId() + val ex = + assertFailsWith { SentryTraceHeader("$sentryId-$spanId") } + assertEquals( + "sentry-trace header does not conform to expected format: $sentryId-$spanId", + ex.message, + ) + } + + @Test + fun `when span-id has less than 16 characters throws exception`() { + val sentryId = SentryId() + val spanId = SpanId().toString().substring(0, 8) + val ex = + assertFailsWith { SentryTraceHeader("$sentryId-$spanId") } + assertEquals( + "sentry-trace header does not conform to expected format: $sentryId-$spanId", + ex.message, + ) + } + + @Test + fun `when span-id has more than 32 characters throws exception`() { + val sentryId = SentryId() + val spanId = SpanId().toString() + "abc" + val ex = + assertFailsWith { SentryTraceHeader("$sentryId-$spanId") } + assertEquals( + "sentry-trace header does not conform to expected format: $sentryId-$spanId", + ex.message, + ) + } + + @Test + fun `when span-id contains invalid characters throws exception`() { + val sentryId = SentryId() + var spanId = SpanId().toString() + spanId = spanId.substring(0, 8) + "g" + spanId.substring(8) + val ex = + assertFailsWith { SentryTraceHeader("$sentryId-$spanId") } + assertEquals( + "sentry-trace header does not conform to expected format: $sentryId-$spanId", + ex.message, + ) + } + @Test fun `handles header with positive sampling decision`() { val sentryId = SentryId() From eb0870181529cfc2cb4481e417e39b103f706adf Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 5 Aug 2025 10:42:08 +0200 Subject: [PATCH 2/3] Reject malformed header with trailing slash and add test case --- .../src/main/java/io/sentry/SentryTraceHeader.java | 5 +++-- .../src/test/java/io/sentry/SentryTraceHeaderTest.kt | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryTraceHeader.java b/sentry/src/main/java/io/sentry/SentryTraceHeader.java index 813fc617154..4b7fd2d7444 100644 --- a/sentry/src/main/java/io/sentry/SentryTraceHeader.java +++ b/sentry/src/main/java/io/sentry/SentryTraceHeader.java @@ -17,7 +17,7 @@ public final class SentryTraceHeader { final Pattern SENTRY_TRACEPARENT_HEADER_REGEX = Pattern.compile( - "^[ \\t]*(?[0-9a-f]{32})-(?[0-9a-f]{16})-?(?[01])?[ \\t]*$", + "^[ \\t]*(?[0-9a-f]{32})-(?[0-9a-f]{16})(?-[01])?[ \\t]*$", Pattern.CASE_INSENSITIVE); public SentryTraceHeader( @@ -39,7 +39,8 @@ public SentryTraceHeader(final @NotNull String value) throws InvalidSentryTraceH this.traceId = new SentryId(matcher.group("traceId")); this.spanId = new SpanId(matcher.group("spanId")); - this.sampled = matcher.group("sampled") == null ? null : "1".equals(matcher.group("sampled")); + this.sampled = + matcher.group("sampled") == null ? null : "1".equals(matcher.group("sampled").substring(1)); } public @NotNull String getName() { diff --git a/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt b/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt index 0233238a63c..f63ef07f33d 100644 --- a/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt @@ -16,6 +16,18 @@ class SentryTraceHeaderTest { assertEquals("sentry-trace header does not conform to expected format: $sentryId", ex.message) } + @Test + fun `when there is a trailing dash without sampling decision throws exception`() { + val sentryId = SentryId() + val spanId = SpanId() + val ex = + assertFailsWith { SentryTraceHeader("$sentryId-$spanId-") } + assertEquals( + "sentry-trace header does not conform to expected format: $sentryId-$spanId-", + ex.message, + ) + } + @Test fun `when trace-id has less than 32 characters throws exception`() { val sentryId = SentryId().toString().substring(0, 8) From 1a820e6d04e4a004cd341af59787aff3af430838 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 5 Aug 2025 11:50:09 +0200 Subject: [PATCH 3/3] Make sentry-header regex private and final --- sentry/src/main/java/io/sentry/SentryTraceHeader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/SentryTraceHeader.java b/sentry/src/main/java/io/sentry/SentryTraceHeader.java index 4b7fd2d7444..71e6d01bc7c 100644 --- a/sentry/src/main/java/io/sentry/SentryTraceHeader.java +++ b/sentry/src/main/java/io/sentry/SentryTraceHeader.java @@ -15,7 +15,7 @@ public final class SentryTraceHeader { private final @NotNull SpanId spanId; private final @Nullable Boolean sampled; - final Pattern SENTRY_TRACEPARENT_HEADER_REGEX = + private static final Pattern SENTRY_TRACEPARENT_HEADER_REGEX = Pattern.compile( "^[ \\t]*(?[0-9a-f]{32})-(?[0-9a-f]{16})(?-[01])?[ \\t]*$", Pattern.CASE_INSENSITIVE);