From e71dcce33d5721ca6d746730e54bac4c6c684f61 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 29 Aug 2026 12:07:39 +0530 Subject: [PATCH 1/3] feat: exclude verified Edge Proxy requests from API usage tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Edge Proxy sends X-Proxy-Key on its environment-document fetches and reports the requests it serves through its own usage endpoint, so counting its fetches here would double-count. The private edge_proxy app's is_edge_proxy_request() decides the exemption — it verifies the key and that its grants cover the presented environment, so a spoofed header never dodges metering. Wired only on non-SaaS deployments with the edge_proxy app installed; requires the flagsmith-private release that ships is_edge_proxy_request (bump the lock before merge). --- api/app_analytics/middleware.py | 17 +++- .../unit/app_analytics/test_middleware.py | 98 +++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/api/app_analytics/middleware.py b/api/app_analytics/middleware.py index 99a072747321..fa2e6909677f 100644 --- a/api/app_analytics/middleware.py +++ b/api/app_analytics/middleware.py @@ -1,5 +1,7 @@ from typing import Callable +from common.core.utils import is_saas +from django.conf import settings from django.http import HttpRequest, HttpResponse from app_analytics.mappers import map_request_to_labels @@ -30,9 +32,22 @@ def __init__( get_response: Callable[[HttpRequest], HttpResponse], ) -> None: self.get_response = get_response + # An Edge Proxy reports the requests it serves itself, so its own + # requests to core must not also be counted here. The private + # edge_proxy app decides what counts as the proxy's own: a verified + # X-Proxy-Key whose grants cover the presented environment — bare + # header presence is never trusted. + self.is_edge_proxy_request: Callable[[HttpRequest], bool] | None = None + if settings.EDGE_PROXY_INSTALLED and not is_saas(): + from edge_proxy.authentication import is_edge_proxy_request + + self.is_edge_proxy_request = is_edge_proxy_request def __call__(self, request: HttpRequest) -> HttpResponse: - if environment_key := request.headers.get("X-Environment-Key"): + if (environment_key := request.headers.get("X-Environment-Key")) and not ( + self.is_edge_proxy_request is not None + and self.is_edge_proxy_request(request) + ): track_usage_by_resource_host_and_environment( resource=get_resource_from_uri(request.path), host=request.get_host(), diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index 165a679ad587..5a3f0c5ec593 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -1,3 +1,5 @@ +import sys + import pytest from django.test import RequestFactory from pytest_django.fixtures import SettingsWrapper @@ -8,6 +10,13 @@ from tests.types import EnableFeaturesFixture +@pytest.fixture(autouse=True) +def edge_proxy_not_installed(settings: SettingsWrapper) -> None: + # Keep these tests hermetic: whether the private edge_proxy wheel is + # installed in the test environment must not change middleware wiring. + settings.EDGE_PROXY_INSTALLED = False + + @pytest.mark.parametrize( "path, resource_name", [ @@ -131,3 +140,92 @@ def test_api_usage_middleware__request_not_tracked__not_calls_expected( # Then mocked_track_request.delay.assert_not_called() + + +@pytest.mark.parametrize( + "edge_proxy_installed, saas, expect_wired", + [ + (True, False, True), + (True, True, False), + (False, False, False), + ], +) +def test_api_usage_middleware__edge_proxy_check__wired_only_where_expected( + mocker: MockerFixture, + settings: SettingsWrapper, + edge_proxy_installed: bool, + saas: bool, + expect_wired: bool, +) -> None: + # Given a deployment with/without the private edge_proxy app + settings.EDGE_PROXY_INSTALLED = edge_proxy_installed + mocker.patch("app_analytics.middleware.is_saas", return_value=saas) + is_edge_proxy_request = mocker.MagicMock() + mocker.patch.dict( + sys.modules, + { + "edge_proxy": mocker.MagicMock(), + "edge_proxy.authentication": mocker.MagicMock( + is_edge_proxy_request=is_edge_proxy_request + ), + }, + ) + + # When + middleware = APIUsageMiddleware(mocker.MagicMock()) + + # Then the verifier is wired only where the proxy reports usage + # itself: a non-SaaS deployment with the edge_proxy app installed + assert middleware.is_edge_proxy_request is ( + is_edge_proxy_request if expect_wired else None + ) + + +@pytest.mark.parametrize("is_verified_proxy_request", [True, False]) +def test_api_usage_middleware__edge_proxy_check_wired__tracks_unverified_only( + rf: RequestFactory, + mocker: MockerFixture, + settings: SettingsWrapper, + is_verified_proxy_request: bool, +) -> None: + # Given a request bearing proxy headers the edge_proxy app does or + # does not verify + headers = {"HTTP_X-Environment-Key": "test", "HTTP_X-Proxy-Key": "pk.key"} + request = rf.get("/api/v1/environment-document", **headers) # type: ignore[arg-type] + settings.EDGE_PROXY_INSTALLED = False + mocked_track_usage = mocker.patch( + "app_analytics.middleware.track_usage_by_resource_host_and_environment" + ) + middleware = APIUsageMiddleware(mocker.MagicMock()) + middleware.is_edge_proxy_request = mocker.MagicMock( + return_value=is_verified_proxy_request + ) + + # When + middleware(request) + + # Then only a verified proxy request is exempt — a spoofed header is not + middleware.is_edge_proxy_request.assert_called_once_with(request) + assert mocked_track_usage.called is not is_verified_proxy_request + + +def test_api_usage_middleware__edge_proxy_check_not_wired__proxy_header_still_tracked( + rf: RequestFactory, + mocker: MockerFixture, + settings: SettingsWrapper, +) -> None: + # Given a request bearing an X-Proxy-Key on a deployment with no + # edge_proxy app to verify it + headers = {"HTTP_X-Environment-Key": "test", "HTTP_X-Proxy-Key": "pk.key"} + request = rf.get("/api/v1/environment-document", **headers) # type: ignore[arg-type] + settings.EDGE_PROXY_INSTALLED = False + mocked_track_usage = mocker.patch( + "app_analytics.middleware.track_usage_by_resource_host_and_environment" + ) + + # When + middleware = APIUsageMiddleware(mocker.MagicMock()) + middleware(request) + + # Then + mocked_track_usage.assert_called_once() From b0a2a51ad3cf0166fdb03765e4d1c4c110c03c15 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 29 Aug 2026 15:51:07 +0530 Subject: [PATCH 2/3] fix: assert on the mock binding, not the middleware attribute --- api/tests/unit/app_analytics/test_middleware.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index 5a3f0c5ec593..ad96425571c5 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -196,16 +196,15 @@ def test_api_usage_middleware__edge_proxy_check_wired__tracks_unverified_only( mocked_track_usage = mocker.patch( "app_analytics.middleware.track_usage_by_resource_host_and_environment" ) + is_edge_proxy_request = mocker.MagicMock(return_value=is_verified_proxy_request) middleware = APIUsageMiddleware(mocker.MagicMock()) - middleware.is_edge_proxy_request = mocker.MagicMock( - return_value=is_verified_proxy_request - ) + middleware.is_edge_proxy_request = is_edge_proxy_request # When middleware(request) # Then only a verified proxy request is exempt — a spoofed header is not - middleware.is_edge_proxy_request.assert_called_once_with(request) + is_edge_proxy_request.assert_called_once_with(request) assert mocked_track_usage.called is not is_verified_proxy_request From 93225c5d46b37ffb6e81a51c6b5b3676f0b267ef Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 29 Aug 2026 16:02:52 +0530 Subject: [PATCH 3/3] fix: tolerate an edge_proxy app that predates is_edge_proxy_request Core's lock pins a flagsmith-private wheel that does not ship the helper yet, so a hard import broke every request in CI. Feature-detect instead: on an older wheel the proxy's fetches are simply counted as before, and the exclusion activates when the pin catches up. --- api/app_analytics/middleware.py | 12 +++++++--- .../unit/app_analytics/test_middleware.py | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/api/app_analytics/middleware.py b/api/app_analytics/middleware.py index fa2e6909677f..076680d6e9c2 100644 --- a/api/app_analytics/middleware.py +++ b/api/app_analytics/middleware.py @@ -1,3 +1,4 @@ +import importlib from typing import Callable from common.core.utils import is_saas @@ -39,9 +40,14 @@ def __init__( # header presence is never trusted. self.is_edge_proxy_request: Callable[[HttpRequest], bool] | None = None if settings.EDGE_PROXY_INSTALLED and not is_saas(): - from edge_proxy.authentication import is_edge_proxy_request - - self.is_edge_proxy_request = is_edge_proxy_request + # getattr, not a hard import: the installed edge_proxy app may + # predate the helper — the proxy's fetches are then counted as + # before. + self.is_edge_proxy_request = getattr( + importlib.import_module("edge_proxy.authentication"), + "is_edge_proxy_request", + None, + ) def __call__(self, request: HttpRequest) -> HttpResponse: if (environment_key := request.headers.get("X-Environment-Key")) and not ( diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index ad96425571c5..75ee3b0067e9 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -181,6 +181,28 @@ def test_api_usage_middleware__edge_proxy_check__wired_only_where_expected( ) +def test_api_usage_middleware__edge_proxy_app_predates_helper__not_wired( + mocker: MockerFixture, + settings: SettingsWrapper, +) -> None: + # Given an installed edge_proxy app without is_edge_proxy_request + settings.EDGE_PROXY_INSTALLED = True + mocker.patch("app_analytics.middleware.is_saas", return_value=False) + mocker.patch.dict( + sys.modules, + { + "edge_proxy": mocker.MagicMock(), + "edge_proxy.authentication": mocker.MagicMock(spec=[]), + }, + ) + + # When + middleware = APIUsageMiddleware(mocker.MagicMock()) + + # Then requests are counted as before + assert middleware.is_edge_proxy_request is None + + @pytest.mark.parametrize("is_verified_proxy_request", [True, False]) def test_api_usage_middleware__edge_proxy_check_wired__tracks_unverified_only( rf: RequestFactory,