diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index c8dba71106..4948c3bffb 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1823,14 +1823,31 @@ def match_regex_list( def is_sentry_url(client: "sentry_sdk.client.BaseClient", url: str) -> bool: """ - Determines whether the given URL matches the Sentry DSN. + Determines whether the given URL's hostname matches the Sentry DSN. + + ``url`` may be an absolute URL or a raw host with an optional port. """ - return ( - client is not None - and client.transport is not None - and client.transport.parsed_dsn is not None - and client.transport.parsed_dsn.netloc in url - ) + if ( + client is None + or client.transport is None + or client.transport.parsed_dsn is None + ): + return False + + dsn_host = client.transport.parsed_dsn.host + + # ``HTTPConnection.host`` is a raw hostname without brackets for IPv6. + if url.lower() == dsn_host.lower(): + return True + + candidate = url if "://" in url or url.startswith("//") else "//" + url + + try: + hostname = urlsplit(candidate).hostname + except ValueError: + return False + + return hostname is not None and hostname.lower() == dsn_host.lower() def _generate_installed_modules() -> "Iterator[Tuple[str, str]]": diff --git a/tests/test_utils.py b/tests/test_utils.py index 1b6d2892b9..6d85c0342f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -882,15 +882,15 @@ class TestIntegration(Integration): @pytest.fixture -def mock_client_with_dsn_netloc(): +def mock_client_with_dsn(): """ - Returns a mocked Client with a DSN netloc of "abcd1234.ingest.sentry.io". + Returns a mocked Client with a DSN host of "abcd1234.ingest.sentry.io". """ mock_client = mock.Mock(spec=sentry_sdk.Client) mock_client.transport = mock.Mock(spec=sentry_sdk.Transport) mock_client.transport.parsed_dsn = mock.Mock(spec=Dsn) - mock_client.transport.parsed_dsn.netloc = "abcd1234.ingest.sentry.io" + mock_client.transport.parsed_dsn.host = "abcd1234.ingest.sentry.io" return mock_client @@ -899,13 +899,37 @@ def mock_client_with_dsn_netloc(): ["test_url", "is_sentry_url_expected"], [ ["https://asdf@abcd1234.ingest.sentry.io/123456789", True], + ["HTTP://ABCD1234.INGEST.SENTRY.IO:8443/envelope", True], + ["abcd1234.ingest.sentry.io", True], + ["abcd1234.ingest.sentry.io:9000", True], ["https://asdf@abcd1234.ingest.notsentry.io/123456789", False], + ["https://abcd1234.ingest.sentry.io.evil.test/api/1", False], + ["https://abcd1234.ingest.sentry.io@attacker.test/api/1", False], + ["https://attacker.test/abcd1234.ingest.sentry.io", False], + ["https://attacker.test/?next=abcd1234.ingest.sentry.io", False], + ["abcd1234.ingest.sentry.io.evil.test", False], + ["https://[::1", False], ], ) -def test_is_sentry_url_true( - test_url, is_sentry_url_expected, mock_client_with_dsn_netloc -): - ret_val = is_sentry_url(mock_client_with_dsn_netloc, test_url) +def test_is_sentry_url(test_url, is_sentry_url_expected, mock_client_with_dsn): + ret_val = is_sentry_url(mock_client_with_dsn, test_url) + + assert ret_val == is_sentry_url_expected + + +@pytest.mark.parametrize( + ["test_url", "is_sentry_url_expected"], + [ + ["2001:db8::1", True], + ["[2001:db8::1]:9000", True], + ["HTTP://[2001:DB8::1]:9000/envelope", True], + ["http://[2001:db8::2]:9000/envelope", False], + ], +) +def test_is_sentry_url_ipv6(test_url, is_sentry_url_expected, mock_client_with_dsn): + mock_client_with_dsn.transport.parsed_dsn.host = "2001:db8::1" + + ret_val = is_sentry_url(mock_client_with_dsn, test_url) assert ret_val == is_sentry_url_expected diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index 9690f2ffc0..3afb5dfd1f 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -393,6 +393,26 @@ def test_should_propagate_trace( "http://squirrelchasers.ingest.sentry.io/12312012", False, ), + ( + "https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012", + "https://SQUIRRELCHASERS.INGEST.SENTRY.IO/12312012", + False, + ), + ( + "https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012", + "https://squirrelchasers.ingest.sentry.io.evil.test/12312012", + True, + ), + ( + "https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012", + "https://squirrelchasers.ingest.sentry.io@attacker.test/12312012", + True, + ), + ( + "https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012", + "https://attacker.test/?next=squirrelchasers.ingest.sentry.io", + True, + ), ( "https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012", "http://ingest.sentry.io/12312012",