Skip to content

Commit 4de21d4

Browse files
nhoeningFlix6x
andauthored
chore: filter out more 404s for Sentry (#2257)
* chore: filter out 404s which triggered SecurityErrors by also doing host poisoning Signed-off-by: Nicolas Höning <nicolas@seita.nl> * PR number in changelog Signed-off-by: Nicolas Höning <nicolas@seita.nl> * remove unused imports Signed-off-by: Nicolas Höning <nicolas@seita.nl> * Update flexmeasures/utils/app_utils.py Co-authored-by: Felix Claessen <30658763+Flix6x@users.noreply.github.com> Signed-off-by: Nicolas Höning <iam@nicolashoening.de> * remove whitelist and simplify what we catch, also add more explanation Signed-off-by: Nicolas Höning <nicolas@seita.nl> --------- Signed-off-by: Nicolas Höning <nicolas@seita.nl> Signed-off-by: Nicolas Höning <iam@nicolashoening.de> Signed-off-by: Felix Claessen <30658763+Flix6x@users.noreply.github.com> Co-authored-by: Felix Claessen <30658763+Flix6x@users.noreply.github.com>
1 parent 9588cb3 commit 4de21d4

3 files changed

Lines changed: 49 additions & 3 deletions

File tree

documentation/changelog.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ FlexMeasures Changelog
44
**********************
55

66

7+
78
v1.0.0 | July XX, 2026
89
============================
910

@@ -35,9 +36,17 @@ Bugfixes
3536

3637
v0.33.1 | July XX, 2026
3738
============================
39+
40+
Bugfixes
41+
-----------
3842
* Allow flex-model and flex-context to be missing from scheduling requests, because by now the whole flex-config can be defined on assets (in the db) instead [see `PR #2237 <https://www.github.com/FlexMeasures/flexmeasures/pull/2237>`_]
3943
* Fix Chart Point sessions chart [see `PR #2259 <https://www.github.com/FlexMeasures/flexmeasures/pull/2259>`_]
4044

45+
Infrastructure / Support
46+
------------------------
47+
* Filter handled untrusted-host ``SecurityError`` events out of Sentry, alongside the existing 404 filtering [see `PR #2257 <https://www.github.com/FlexMeasures/flexmeasures/pull/2257>`_]
48+
49+
4150

4251
v0.33.0 | June 1, 2026
4352
============================

flexmeasures/utils/app_utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,37 @@ def flexmeasures_cli():
2828
pass
2929

3030

31+
# For the Sentry integration, a crucial task is to filter out noise before it reaches Sentry.
32+
# Limiting what gets sent to Sentry (by 95%) keeps your costs to what you are interested in.
33+
# We want to filter out 404s (also those who in addition use untrusted-host request headers),
34+
# which are common probes in the wild.
35+
# Note: errors may reach Sentry twice - as raised Exception plus if FlexMeasures logs the error (e.g. during handling it)
36+
# With verbose=False, Sentry might only see the logging event, not an Exception, as it is only visible in the LogRecord message rather than in hint["exc_info"].
37+
38+
3139
def _sentry_filter_notfound(event, hint):
32-
"""Filter out 404 Not Found errors to avoid inflating Sentry error budgets."""
40+
"""Filter out noisy handled web errors to avoid inflating Sentry error budgets."""
3341
if "exc_info" in hint:
34-
exc_type, exc_value, _tb = hint["exc_info"]
42+
_exc_type, exc_value, _tb = hint["exc_info"]
3543
if isinstance(exc_value, NotFound):
3644
return None
3745
# FlexMeasures logs handled 404s with verbose=False to keep automated
3846
# scans for hackable URLs from overwhelming log files. Sentry receives
3947
# those as logging events, so the NotFound exception is only visible in
4048
# the LogRecord message rather than in hint["exc_info"].
49+
# We also filter out handled SecurityErrors that are logged when untrusted-host
50+
# request headers are used.
4151
log_record = hint.get("log_record")
4252
if log_record is not None:
4353
message = log_record.getMessage()
4454
if message.startswith("NotFound - URL was: "):
4555
return None
56+
if (
57+
message.startswith("SecurityError - URL was: ")
58+
and " - \"Host '" in message
59+
and message.endswith("' is not trusted.\"")
60+
):
61+
return None
4662
return event
4763

4864

flexmeasures/utils/tests/test_app_utils.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from flask import Flask
55
from sentry_sdk.integrations.flask import FlaskIntegration
66
from sentry_sdk.transport import Transport
7-
from werkzeug.exceptions import InternalServerError, NotFound
7+
from werkzeug.exceptions import InternalServerError, NotFound, SecurityError
88

99
from flexmeasures.utils.app_utils import _sentry_filter_notfound
1010
from flexmeasures.utils.error_utils import add_basic_error_handlers
@@ -47,6 +47,17 @@ def test_sentry_filter_passes_other_errors():
4747
assert _sentry_filter_notfound(event, hint) is event
4848

4949

50+
def test_sentry_filter_drops_untrusted_host_security_error():
51+
"""Untrusted-host SecurityErrors with exc_info should still be reported."""
52+
event = {"message": "Security Error"}
53+
hint = make_hint(
54+
SecurityError(
55+
"Host 'static.152.131.199.138.clients.your-server.de' is not trusted."
56+
)
57+
)
58+
assert _sentry_filter_notfound(event, hint) is event
59+
60+
5061
def test_sentry_filter_passes_events_without_exc_info():
5162
"""Events without exc_info (e.g. captured messages) should be passed through."""
5263
event = {"message": "some log message"}
@@ -70,6 +81,16 @@ def test_sentry_filter_passes_other_log_records():
7081
assert _sentry_filter_notfound(event, hint) is event
7182

7283

84+
def test_sentry_filter_drops_untrusted_host_security_error_log_record():
85+
"""Handled untrusted-host SecurityErrors are logged without exc_info."""
86+
event = {"message": "Security Error"}
87+
log_record = app_logger_record(
88+
"SecurityError - URL was: /admin/config.php - \"Host '0.0.0.0' is not trusted.\""
89+
)
90+
hint = {"log_record": log_record}
91+
assert _sentry_filter_notfound(event, hint) is None
92+
93+
7394
def test_sentry_filter_drops_flask_404_logging_event():
7495
"""The Flask error handler logs 404s with a LogRecord hint."""
7596
app = Flask(__name__)

0 commit comments

Comments
 (0)