From 251b88517581b78de59dec9aee3f2330b53b1e22 Mon Sep 17 00:00:00 2001 From: Sahil Date: Mon, 23 Mar 2026 10:31:43 +0530 Subject: [PATCH 1/2] fix: sanitize environment document cache payload to prevent internal data leak --- api/environments/models.py | 6 +++-- .../test_unit_environments_models.py | 26 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/api/environments/models.py b/api/environments/models.py index d5b27ea318db..47956511c3ff 100644 --- a/api/environments/models.py +++ b/api/environments/models.py @@ -4,6 +4,7 @@ from copy import deepcopy from typing import TYPE_CHECKING, Literal +from flagsmith_schemas.api import V1EnvironmentDocumentResponse from common.core.utils import using_database_replica from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation @@ -361,10 +362,11 @@ def write_environment_documents( ): environment_document_cache.set_many( { - e.api_key: map_environment_to_environment_document(e) + # Use the SDK mapper so the cache perfectly matches the DB fallback + e.api_key: map_environment_to_sdk_document(e) for e in environments } - ) + ) def get_feature_state( self, diff --git a/api/tests/unit/environments/test_unit_environments_models.py b/api/tests/unit/environments/test_unit_environments_models.py index 2f17845bfc7e..daa5a59468df 100644 --- a/api/tests/unit/environments/test_unit_environments_models.py +++ b/api/tests/unit/environments/test_unit_environments_models.py @@ -20,6 +20,8 @@ from core.constants import STRING from core.request_origin import RequestOrigin from environments.identities.models import Identity +from environments.enums import EnvironmentDocumentCacheMode +from flagsmith_schemas.api import V1EnvironmentDocumentResponse from environments.metrics import CACHE_HIT, CACHE_MISS from environments.models import ( Environment, @@ -41,7 +43,8 @@ from segments.models import Segment from tests.types import EnableFeaturesFixture from users.models import FFAdminUser -from util.mappers import map_environment_to_environment_document +from util.mappers import (map_environment_to_environment_document, + map_environment_to_sdk_document) if typing.TYPE_CHECKING: from django.db.models import Model @@ -1267,7 +1270,7 @@ def test_environment_clone_from_non_versioned_environment_with_use_v2_feature_ve # When new_environment = environment.clone(name="new-environment") - # Then + # Then assert new_environment.use_v2_feature_versioning # we only expect a single environment feature version as we are essentially @@ -1280,3 +1283,22 @@ def test_environment_clone_from_non_versioned_environment_with_use_v2_feature_ve latest_feature_states = get_environment_flags_queryset(new_environment) assert latest_feature_states.count() == 2 assert {fs.environment_feature_version for fs in latest_feature_states} == {efv} + +@mock.patch("environments.models.environment_document_cache") +def test_write_environment_documents__persistent_caching_enabled__caches_sdk_document( + mock_document_cache: MagicMock, + environment: Environment, + settings: typing.Any +) -> None: + # Given + settings.CACHE_ENVIRONMENT_DOCUMENT_MODE = EnvironmentDocumentCacheMode.PERSISTENT + + # When + Environment.write_environment_documents(environment_id=environment.id) + + # Then + cache_payload = mock_document_cache.set_many.call_args[0][0] + cached_document = cache_payload[environment.api_key] + expected_document = map_environment_to_sdk_document(environment) + + assert cached_document == expected_document From de6e26d0bb77d5a9bb502afe390f1dd188bee4c7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 05:42:27 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- api/environments/models.py | 4 +--- .../test_unit_environments_models.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/api/environments/models.py b/api/environments/models.py index 47956511c3ff..6bf93e2b9775 100644 --- a/api/environments/models.py +++ b/api/environments/models.py @@ -4,7 +4,6 @@ from copy import deepcopy from typing import TYPE_CHECKING, Literal -from flagsmith_schemas.api import V1EnvironmentDocumentResponse from common.core.utils import using_database_replica from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation @@ -57,7 +56,6 @@ from projects.models import Project from segments.models import Segment from util.mappers import ( - map_environment_to_environment_document, map_environment_to_sdk_document, ) from webhooks.models import AbstractBaseExportableWebhookModel @@ -366,7 +364,7 @@ def write_environment_documents( e.api_key: map_environment_to_sdk_document(e) for e in environments } - ) + ) def get_feature_state( self, diff --git a/api/tests/unit/environments/test_unit_environments_models.py b/api/tests/unit/environments/test_unit_environments_models.py index daa5a59468df..038449a33500 100644 --- a/api/tests/unit/environments/test_unit_environments_models.py +++ b/api/tests/unit/environments/test_unit_environments_models.py @@ -19,9 +19,8 @@ from audit.related_object_type import RelatedObjectType from core.constants import STRING from core.request_origin import RequestOrigin -from environments.identities.models import Identity from environments.enums import EnvironmentDocumentCacheMode -from flagsmith_schemas.api import V1EnvironmentDocumentResponse +from environments.identities.models import Identity from environments.metrics import CACHE_HIT, CACHE_MISS from environments.models import ( Environment, @@ -43,8 +42,10 @@ from segments.models import Segment from tests.types import EnableFeaturesFixture from users.models import FFAdminUser -from util.mappers import (map_environment_to_environment_document, - map_environment_to_sdk_document) +from util.mappers import ( + map_environment_to_environment_document, + map_environment_to_sdk_document, +) if typing.TYPE_CHECKING: from django.db.models import Model @@ -1270,7 +1271,7 @@ def test_environment_clone_from_non_versioned_environment_with_use_v2_feature_ve # When new_environment = environment.clone(name="new-environment") - # Then + # Then assert new_environment.use_v2_feature_versioning # we only expect a single environment feature version as we are essentially @@ -1284,11 +1285,10 @@ def test_environment_clone_from_non_versioned_environment_with_use_v2_feature_ve assert latest_feature_states.count() == 2 assert {fs.environment_feature_version for fs in latest_feature_states} == {efv} + @mock.patch("environments.models.environment_document_cache") def test_write_environment_documents__persistent_caching_enabled__caches_sdk_document( - mock_document_cache: MagicMock, - environment: Environment, - settings: typing.Any + mock_document_cache: MagicMock, environment: Environment, settings: typing.Any ) -> None: # Given settings.CACHE_ENVIRONMENT_DOCUMENT_MODE = EnvironmentDocumentCacheMode.PERSISTENT