From 1e9d22bed0767cbdd1c32d652edf1e7f964fc75c Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 30 Jun 2025 10:28:17 +1200 Subject: [PATCH 1/4] Fix Directory.Build.targets when using SentryNoMobile.slnf #skip-changelog --- samples/Directory.Build.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/Directory.Build.targets b/samples/Directory.Build.targets index d8a727cf4f..e757f55436 100644 --- a/samples/Directory.Build.targets +++ b/samples/Directory.Build.targets @@ -10,7 +10,7 @@ + Condition="'$(SENTRY_DSN)' != ''"> @@ -25,7 +25,7 @@ namespace Sentry.Samples%3B internal static class EnvironmentVariables { /// <summary> - /// To make things easier for the SDK maintainers we have a custom build target that writes the + /// To make things easier for the SDK maintainers, we have a custom build target that writes the /// SENTRY_DSN environment variable into an EnvironmentVariables class that is available for mobile /// targets. This allows us to share one DSN defined in the ENV across desktop and mobile samples. /// Generally, you won't want to do this in your own mobile projects though - you should set the DSN From 447494ed3e7f7fb672c0dec363ed84f90fbfaf14 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 30 Jun 2025 11:07:23 +1200 Subject: [PATCH 2/4] fix: Crontab validation when capturing checkins Resolves #3719: - https://github.com/getsentry/sentry-dotnet/issues/3719 --- src/Sentry/SentryMonitorOptions.cs | 28 +++-- .../Sentry.Tests/SentryMonitorOptionsTests.cs | 112 ++++++++++++++++++ 2 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 test/Sentry.Tests/SentryMonitorOptionsTests.cs diff --git a/src/Sentry/SentryMonitorOptions.cs b/src/Sentry/SentryMonitorOptions.cs index c61aa7e4ab..9eada37823 100644 --- a/src/Sentry/SentryMonitorOptions.cs +++ b/src/Sentry/SentryMonitorOptions.cs @@ -51,14 +51,26 @@ public enum SentryMonitorInterval /// public partial class SentryMonitorOptions : ISentryJsonSerializable { - // Breakdown of the validation - // ^(\*|([0-5]?\d)) Minute 0 - 59 - // (\s+)(\*|([01]?\d|2[0-3])) Hour 0 - 23 - // (\s+)(\*|([1-9]|[12]\d|3[01])) Day 1 - 31 - // (\s+)(\*|([1-9]|1[0-2])) Month 1 - 12 - // (\s+)(\*|([0-7])) Weekday 0 - 7 - // $ End of string - private const string ValidCrontabPattern = @"^(\*|([0-5]?\d))(\s+)(\*|([01]?\d|2[0-3]))(\s+)(\*|([1-9]|[12]\d|3[01]))(\s+)(\*|([1-9]|1[0-2]))(\s+)(\*|([0-7]))$"; + // Breakdown of the validation regex pattern: + // For each time field (minute, hour, day, month, weekday): + // - Allows * for "any value" + // - Allows */n for step values where n must be: + // - Minutes: 1-59 + // - Hours: 1-23 + // - Days: 1-31 + // - Months: 1-12 + // - Weekdays: 1-7 + // - Allows single values within their valid ranges + // - Allows ranges (e.g., 8-10) + // - Allows lists of values and ranges (e.g., 6,8,9 or 8-10,12-14) + // + // Valid ranges for each field: + // - Minutes: 0-59 + // - Hours: 0-23 + // - Days: 1-31 + // - Months: 1-12 + // - Weekdays: 0-7 (0 and 7 both represent Sunday) + private const string ValidCrontabPattern = @"^(\*|(\*\/([1-9]|[1-5][0-9]))|([0-5]?\d)(-[0-5]?\d)?)(,([0-5]?\d)(-[0-5]?\d)?)*(\s+)(\*|(\*\/([1-9]|1[0-9]|2[0-3]))|([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)((,([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)*)?(\s+)(\*|(\*\/([1-9]|[12][0-9]|3[01]))|([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)((,([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)*)?(\s+)(\*|(\*\/([1-9]|1[0-2]))|([1-9]|1[0-2])(-([1-9]|1[0-2]))?)((,([1-9]|1[0-2])(-([1-9]|1[0-2]))?)*)?(\s+)(\*|(\*\/[1-7])|[0-7](-[0-7])?)((,[0-7](-[0-7])?)*)?$"; private SentryMonitorScheduleType _type = SentryMonitorScheduleType.None; private string? _crontab; diff --git a/test/Sentry.Tests/SentryMonitorOptionsTests.cs b/test/Sentry.Tests/SentryMonitorOptionsTests.cs new file mode 100644 index 0000000000..5dc70e1f7e --- /dev/null +++ b/test/Sentry.Tests/SentryMonitorOptionsTests.cs @@ -0,0 +1,112 @@ +namespace Sentry.Tests; + +public class SentryMonitorOptionsTests +{ + /* + The time and date fields are: + field allowed values + ----- -------------- + minute 0-59 + hour 0-23 + day of month 1-31 + month 1-12 (or names, see below) + day of week 0-7 (0 or 7 is Sunday, or use names) + + Special characters are: + * any value + , value list separator + - range of values + / step values + */ + [Theory] + [InlineData("* * * * *")] + [InlineData("0 0 1 1 *")] + [InlineData("0 0 1 * 0")] + [InlineData("59 23 31 12 7")] + [InlineData("0 */2 * * *")] + [InlineData("0 8-10 * * *")] + [InlineData("0 6,8,9 * * *")] + // Step values (*/n) + [InlineData("*/15 * * * *")] // Every 15 minutes + [InlineData("0 */6 * * *")] // Every 6 hours + [InlineData("0 0 */2 * *")] // Every 2 days + [InlineData("0 0 1 */3 *")] // Every 3 months + // Complex ranges + [InlineData("1-15 * * * *")] // Minutes 1 through 15 + [InlineData("* 9-17 * * *")] // Business hours + [InlineData("* * 1-15,16-31 * *")] // Split day ranges + // Multiple comma-separated values + [InlineData("1,15,30,45 * * * *")] // Specific minutes + [InlineData("* 9,12,15,17 * * *")] // Specific hours + [InlineData("0 0 * * 1,3,5")] // Monday, Wednesday, Friday + // Combinations of special characters + [InlineData("*/15 9-17 * * 1-5")] // Every 15 min during business hours on weekdays + [InlineData("0 8-10,13-15 * * *")] // Morning and afternoon ranges + // Edge cases + [InlineData("0 0 1 1 0")] // Minimum values + [InlineData("*/1 * * * *")] // Step of 1 + [InlineData("* * 31 */2 *")] // 31st of every other month + public void Interval_ValidCrontab_DoesNotThrow(string crontab) + { + // Arrange + var options = new SentryMonitorOptions(); + + // Act + options.Interval(crontab); + } + + [Fact] + public void Interval_SetMoreThanOnce_Throws() + { + // Arrange + var options = new SentryMonitorOptions(); + + // Act + options.Interval(1, SentryMonitorInterval.Month); + Assert.Throws(() => options.Interval(2, SentryMonitorInterval.Day)); + } + + [Theory] + [InlineData("")] + [InlineData("not a crontab")] + [InlineData("* * a * *")] + [InlineData("60 * * * *")] + [InlineData("* 24 * * *")] + [InlineData("* * 32 * *")] + [InlineData("* * * 13 *")] + [InlineData("* * * * 8")] + // Invalid step values + [InlineData("*/0 * * * *")] // Step value cannot be 0 + [InlineData("*/100 * * * *")] // Step value too large for minutes + [InlineData("* */25 * * *")] // Step value too large for hours + [InlineData("* * */32 * *")] // Step value too large for days + [InlineData("* * * */13 *")] // Step value too large for months + [InlineData("* * * * */8")] // Step value too large for weekdays + [InlineData("*/60 * * * *")] // Step value equals max value for minutes + [InlineData("* */24 * * *")] // Step value equals max value for hours + // Invalid ranges + [InlineData("5-60 * * * *")] // Minute range exceeds 59 + [InlineData("* 5-24 * * *")] // Hour range exceeds 23 + [InlineData("* * 0-31 * *")] // Day cannot be 0 + [InlineData("* * * 0-12 *")] // Month cannot be 0 + // Invalid combinations + [InlineData("*-5 * * * *")] // Invalid range with asterisk + [InlineData("1,2,60 * * * *")] // Invalid value in list + [InlineData("1-5-10 * * * *")] // Multiple ranges + [InlineData("*/2/3 * * * *")] // Multiple steps + // Malformed expressions + [InlineData("* * * *")] // Too few fields + [InlineData("* * * * * *")] // Too many fields + [InlineData("** * * * *")] // Double asterisk + [InlineData("*/* * * * *")] // Invalid step format + [InlineData(",1,2 * * * *")] // Leading comma + [InlineData("1,2, * * * *")] // Trailing comma + public void CaptureCheckIn_InvalidCrontabSet_Throws(string crontab) + { + // Arrange + var options = new SentryMonitorOptions(); + + // Act + Assert.Throws(() => options.Interval(crontab)); + } +} From 6c84e7e2dc6c99d6651c1bec37df695dba860188 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 30 Jun 2025 11:11:22 +1200 Subject: [PATCH 3/4] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f325b27ee8..ecd487a840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Added StartSpan and GetTransaction methods to the SentrySdk ([#4303](https://github.com/getsentry/sentry-dotnet/pull/4303)) +### Fixes + +- Crontab validation when capturing checkins ([#4314](https://github.com/getsentry/sentry-dotnet/pull/4314)) + ### Dependencies - Bump Native SDK from v0.9.0 to v0.9.1 ([#4309](https://github.com/getsentry/sentry-dotnet/pull/4309)) From 825dd0b5fd93d881e03274eaa47b4ff956942dfb Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 1 Jul 2025 11:08:03 +1200 Subject: [PATCH 4/4] Fixed step value validation --- src/Sentry/SentryMonitorOptions.cs | 9 ++------- test/Sentry.Tests/SentryMonitorOptionsTests.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/Sentry/SentryMonitorOptions.cs b/src/Sentry/SentryMonitorOptions.cs index 9eada37823..9ec7406822 100644 --- a/src/Sentry/SentryMonitorOptions.cs +++ b/src/Sentry/SentryMonitorOptions.cs @@ -54,12 +54,7 @@ public partial class SentryMonitorOptions : ISentryJsonSerializable // Breakdown of the validation regex pattern: // For each time field (minute, hour, day, month, weekday): // - Allows * for "any value" - // - Allows */n for step values where n must be: - // - Minutes: 1-59 - // - Hours: 1-23 - // - Days: 1-31 - // - Months: 1-12 - // - Weekdays: 1-7 + // - Allows */n for step values where n must be any positive integer (except zero) // - Allows single values within their valid ranges // - Allows ranges (e.g., 8-10) // - Allows lists of values and ranges (e.g., 6,8,9 or 8-10,12-14) @@ -70,7 +65,7 @@ public partial class SentryMonitorOptions : ISentryJsonSerializable // - Days: 1-31 // - Months: 1-12 // - Weekdays: 0-7 (0 and 7 both represent Sunday) - private const string ValidCrontabPattern = @"^(\*|(\*\/([1-9]|[1-5][0-9]))|([0-5]?\d)(-[0-5]?\d)?)(,([0-5]?\d)(-[0-5]?\d)?)*(\s+)(\*|(\*\/([1-9]|1[0-9]|2[0-3]))|([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)((,([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)*)?(\s+)(\*|(\*\/([1-9]|[12][0-9]|3[01]))|([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)((,([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)*)?(\s+)(\*|(\*\/([1-9]|1[0-2]))|([1-9]|1[0-2])(-([1-9]|1[0-2]))?)((,([1-9]|1[0-2])(-([1-9]|1[0-2]))?)*)?(\s+)(\*|(\*\/[1-7])|[0-7](-[0-7])?)((,[0-7](-[0-7])?)*)?$"; + private const string ValidCrontabPattern = @"^(\*|(\*\/([1-9][0-9]*))|([0-5]?\d)(-[0-5]?\d)?)(,([0-5]?\d)(-[0-5]?\d)?)*(\s+)(\*|(\*\/([1-9][0-9]*))|([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)((,([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)*)?(\s+)(\*|(\*\/([1-9][0-9]*))|([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)((,([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)*)?(\s+)(\*|(\*\/([1-9][0-9]*))|([1-9]|1[0-2])(-([1-9]|1[0-2]))?)((,([1-9]|1[0-2])(-([1-9]|1[0-2]))?)*)?(\s+)(\*|(\*\/([1-9][0-9]*))|[0-7](-[0-7])?)((,[0-7](-[0-7])?)*)?$"; private SentryMonitorScheduleType _type = SentryMonitorScheduleType.None; private string? _crontab; diff --git a/test/Sentry.Tests/SentryMonitorOptionsTests.cs b/test/Sentry.Tests/SentryMonitorOptionsTests.cs index 5dc70e1f7e..86891f8a6e 100644 --- a/test/Sentry.Tests/SentryMonitorOptionsTests.cs +++ b/test/Sentry.Tests/SentryMonitorOptionsTests.cs @@ -31,6 +31,13 @@ day of month 1-31 [InlineData("0 */6 * * *")] // Every 6 hours [InlineData("0 0 */2 * *")] // Every 2 days [InlineData("0 0 1 */3 *")] // Every 3 months + [InlineData("*/100 * * * *")] // Step value 100 for minutes + [InlineData("* */25 * * *")] // Step value 25 for hours + [InlineData("* * */32 * *")] // Step value 32 for days + [InlineData("* * * */13 *")] // Step value 13 for months + [InlineData("* * * * */8")] // Step value 8 for weekdays + [InlineData("*/60 * * * *")] // Step value 60 for minutes + [InlineData("* */24 * * *")] // Step value 24 for hours // Complex ranges [InlineData("1-15 * * * *")] // Minutes 1 through 15 [InlineData("* 9-17 * * *")] // Business hours @@ -77,13 +84,6 @@ public void Interval_SetMoreThanOnce_Throws() [InlineData("* * * * 8")] // Invalid step values [InlineData("*/0 * * * *")] // Step value cannot be 0 - [InlineData("*/100 * * * *")] // Step value too large for minutes - [InlineData("* */25 * * *")] // Step value too large for hours - [InlineData("* * */32 * *")] // Step value too large for days - [InlineData("* * * */13 *")] // Step value too large for months - [InlineData("* * * * */8")] // Step value too large for weekdays - [InlineData("*/60 * * * *")] // Step value equals max value for minutes - [InlineData("* */24 * * *")] // Step value equals max value for hours // Invalid ranges [InlineData("5-60 * * * *")] // Minute range exceeds 59 [InlineData("* 5-24 * * *")] // Hour range exceeds 23