Skip to content

feat(inbound-filters): Add semver based inbound filters for releases - #125877

Draft
shellmayr wants to merge 2 commits into
masterfrom
shellmayr/feat/semver-inbound-filter-backend
Draft

shellmayr wants to merge 2 commits into
masterfrom
shellmayr/feat/semver-inbound-filter-backend

Conversation

@shellmayr

@shellmayr shellmayr commented Sep 29, 2026 •

Copy link
Copy Markdown
Member
  • A release condition value that compares versions compiles to Relay's semver rule condition, added in feat(generic-filters): Add a semver rule condition relay#6430. That is a value with a leading >, >=, <, <= or =, or a plain release with a version such as 1.2.0 or myapp@1.2.0, which compares as equal. Any other value stays a glob pattern, so one condition can mix 1.* with >=3.0.
  • A comparison without a package applies to releases of every package. With a package, only that package's releases compare.
  • The API rejects a comparison whose release carries no version, such as >2* or <a4b7e0f9c2d1. Relay would never match those, so the filter would silently lose part of its values.
  • A Relay without the semver op reads the condition as unsupported and never matches it. Such a filter drops nothing instead of dropping more than configured.

…itions

A release condition value that starts with >, >=, <, <= or = now compiles to
Relay's semver rule condition instead of a glob, so a filter can drop data from
a version range such as >=1.2.0 without listing each version as a pattern.
The API rejects a comparison whose release carries no version, as Relay would
never match it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Sep 29, 2026
Comment on lines +137 to +142
if attrs["type"] == ConditionType.RELEASE:
invalid = [
value
for value in attrs["value"]
if (comparison := parse_release_comparison(value))
and not is_release_version(comparison.release)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release comparison validation can 500 on RelayError from parse_release

Wrap is_release_version() so parse_release RelayError returns False; otherwise invalid comparison values like >=package@1.0.0-@ crash this serializer with a 500 instead of a 400.

Evidence
  • The new RELEASE branch calls is_release_version(comparison.release) on each user-supplied comparison value.
  • is_release_version() invokes sentry_relay.processing.parse_release with no try/except.
  • Release.is_semver_version() and other call sites catch RelayError from parse_release ("invalid legacy releases") and treat it as non-semver.
  • An uncaught RelayError here escapes DRF validation and becomes a 500 instead of the intended validation error.

Identified by Warden · sentry-backend-bugs · FYL-YBT

A release value without a comparator that carries a version, such as 1.2.0 or
myapp@1.2.0, now compares as equal to that version instead of matching the
text. So 1.2 matches 1.2.0 of every package, and a build code does not get
in the way. A value without a version, such as a commit hash or a glob, still
matches the text.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on d662220 in this run:

tests/sentry/api/endpoints/test_project_custom_inbound_filters.py::CustomInboundFilterDetailsTest::test_put_rejects_growing_past_the_size_cap — log
[gw0] linux -- Python 3.13.15 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_custom_inbound_filters.py:798: in test_put_rejects_growing_past_the_size_cap
    assert str(response.data["conditions"][0]) == (
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   KeyError: 'conditions'
tests/sentry/ingest/test_inbound_filters.py::test_custom_inbound_filter_condition_translation[plain_version_compares_as_equal] — log
[gw1] linux -- Python 3.13.15 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/ingest/test_inbound_filters.py:509: in test_custom_inbound_filter_condition_translation
    assert_relay_accepts_condition(generic_filter["condition"])
tests/sentry/ingest/test_inbound_filters.py:218: in assert_relay_accepts_condition
    validate_rule_condition(json.dumps(condition))
.venv/lib/python3.13/site-packages/sentry_relay/processing.py:291: in validate_rule_condition
    raise ValueError(error)
E   ValueError: unsupported condition
tests/sentry/api/endpoints/test_project_custom_inbound_filters.py::CustomInboundFiltersTest::test_allows_condition_values_at_the_size_cap — log
[gw0] linux -- Python 3.13.15 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_custom_inbound_filters.py:489: in test_allows_condition_values_at_the_size_cap
    self.get_success_response(
src/sentry/testutils/cases.py:628: in get_success_response
    assert_status_code(response, status_code)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 202
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/api/endpoints/test_project_custom_inbound_filters.py::CustomInboundFilterDetailsTest::test_put_keeps_stored_oversized_filter_but_refuses_growth — log
[gw0] linux -- Python 3.13.15 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_custom_inbound_filters.py:818: in test_put_keeps_stored_oversized_filter_but_refuses_growth
    assert str(response.data["conditions"][0]) == (
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   KeyError: 'conditions'
tests/sentry/ingest/test_inbound_filters.py::test_custom_inbound_filter_condition_translation[catch_all_release_range] — log
[gw0] linux -- Python 3.13.15 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/ingest/test_inbound_filters.py:509: in test_custom_inbound_filter_condition_translation
    assert_relay_accepts_condition(generic_filter["condition"])
tests/sentry/ingest/test_inbound_filters.py:218: in assert_relay_accepts_condition
    validate_rule_condition(json.dumps(condition))
.venv/lib/python3.13/site-packages/sentry_relay/processing.py:291: in validate_rule_condition
    raise ValueError(error)
E   ValueError: unsupported condition
tests/sentry/api/endpoints/test_project_custom_inbound_filters.py::CustomInboundFiltersTest::test_rejects_oversized_condition_values — log
[gw0] linux -- Python 3.13.15 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/api/endpoints/test_project_custom_inbound_filters.py:479: in test_rejects_oversized_condition_values
    assert str(response.data["conditions"][0]) == (
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   KeyError: 'conditions'
tests/sentry/relay/test_config.py::test_project_config_custom_inbound_filters_v2[True-True-REGION] — log
[gw0] linux -- Python 3.13.15 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/relay/test_config.py:267: in test_project_config_custom_inbound_filters_v2
    _validate_project_config(cfg["config"])
tests/sentry/relay/test_config.py:96: in _validate_project_config
    assert normalize_project_config(config) == config
E   AssertionError: assert {'allowedDoma...tion': 0, ...} == {'allowedDoma...tion': 0, ...}
E     
E     Omitting 9 identical items, use -vv to show
E     Differing items:
E     �[0m{�[33m'�[39;49;00m�[33mfilterSettings�[39;49;00m�[33m'�[39;49;00m: {�[33m'�[39;49;00m�[33mcsp�[39;49;00m�[33m'�[39;49;00m: {�[33m'�[39;49;00m�[33mdisallowedSources�[39;49;00m�[33m'�[39;49;00m: [�[33m'�[39;49;00m�[33mabout�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33mms-browser-extension�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33mchrome://*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33mchrome-extension://*...patterns�[39;49;00m�[33m'�[39;49;00m: [�[33m'�[39;49;00m�[33m*healthcheck*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*health-check*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*heartbeat*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*/health�[39;49;00m�[33m{�[39;49;00m�[33m/,}�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*/healthy�[39;49;00m�[33m{�[39;49;00m�[33m/,}�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*/healthz�[39;49;00m�[33m{�[39;49;00m�[33m/,}�[39;49;00m�[33m'�[39;49;00m, ...]}}}�[90m�[39;49;00m != �[0m{�[33m'�[39;49;00m�[33mfilterSettings�[39;49;00m�[33m'�[39;49;00m: {�[33m'�[39;49;00m�[33mcsp�[39;49;00m�[33m'�[39;49;00m: {�[33m'�[39;49;00m�[33mdisallowedSources�[39;49;00m�[33m'�[39;49;00m: [�[33m'�[39;49;00m�[33mabout�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33mms-browser-extension�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33mchrome://*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33mchrome-extension://*...patterns�[39;49;00m�[33m'�[39;49;00m: [�[33m'�[39;49;00m�[33m*healthcheck*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*health-check*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*heartbeat*�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*/health�[39;49;00m�[33m{�[39;49;00m�[33m/,}�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*/healthy�[39;49;00m�[33m{�[39;49;00m�[33m/,}�[39;49;00m�[33m'�[39;49;00m, �[33m'�[39;49;00m�[33m*/healthz�[39;49;00m�[33m{�[39;49;00m�[33m/,}�[39;49;00m�[33m'�[39;49;00m, ...]}}}�[90m�[39;49;00m
E     
E     Full diff:
E     �[0m�[90m �[39;49;00m {�[90m�[39;49;00m
E     �[90m �[39;49;00m     'allowedDomains': [�[90m�[39;49;00m
E     �[90m �[39;49;00m         '*',�[90m�[39;49;00m
E     �[90m �[39;49;00m     ],�[90m�[39;49;00m
E     �[90m �[39;49;00m     'breakdownsV2': {�[90m�[39;49;00m
E     �[90m �[39;49;00m         'span_ops': {�[90m�[39;49;00m
E     �[90m �[39;49;00m             'matches': [�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'http',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'db',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'browser',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'resource',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'ui',�[90m�[39;49;00m
E     �[90m �[39;49;00m             ],�[90m�[39;49;00m
E     �[90m �[39;49;00m             'type': 'spanOperations',�[90m�[39;49;00m
E     �[90m �[39;49;00m         },�[90m�[39;49;00m
E     �[90m �[39;49;00m     },�[90m�[39;49;00m
E     �[90m �[39;49;00m     'datascrubbingSettings': {�[90m�[39;49;00m
E     �[90m �[39;49;00m         'excludeFields': [],�[90m�[39;49;00m
E     �[90m �[39;49;00m         'scrubData': True,�[90m�[39;49;00m
E     �[90m �[39;49;00m         'scrubDefaults': True,�[90m�[39;49;00m
E     �[90m �[39;49;00m         'sensitiveFields': [],�[90m�[39;49;00m
E     �[90m �[39;49;00m     },�[90m�[39;49;00m
E     �[90m �[39;49;00m     'downsampledEventRetention': 0,�[90m�[39;49;00m
E     �[90m �[39;49;00m     'filterSettings': {�[90m�[39;49;00m
E     �[90m �[39;49;00m         'csp': {�[90m�[39;49;00m
E     �[90m �[39;49;00m             'disallowedSources': [�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'about',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'ms-browser-extension',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'chrome://*',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'chrome-extension://*',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'chrome-extension',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'chromeinvokeimmediate://*',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'chromenull://*',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'data:text/html,chromewebdata',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'safari-extension://*',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'safari-web-extension://*',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'mxaddon-pkg://*',�[90m�[39;49;00m
E     �[90m �[39;49;00m                 'jar://*',�[90m�[39;49;00m
... (944 more lines)

@shellmayr shellmayr changed the title feat(inbound-filters): Compare release versions in custom filter conditions feat(inbound-filters): Add semver based inbound filters for releases Sep 30, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant