From 389c7a0423a7f066ebc06cbd5c233e2f98d34020 Mon Sep 17 00:00:00 2001 From: wanjinhao1 <394959433@qq.com> Date: Sun, 18 Oct 2026 20:05:27 +0800 Subject: [PATCH] Fix #8596: do not mutate caller's Filters in SecretsProvider.get_multiple --- .../utilities/parameters/secrets.py | 8 ++- .../_boto3/test_utilities_parameters.py | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/aws_lambda_powertools/utilities/parameters/secrets.py b/aws_lambda_powertools/utilities/parameters/secrets.py index eff4e745738..c4beb4feefe 100644 --- a/aws_lambda_powertools/utilities/parameters/secrets.py +++ b/aws_lambda_powertools/utilities/parameters/secrets.py @@ -153,12 +153,10 @@ def _get_multiple(self, names: list[str], **sdk_options) -> dict[str, Any]: # t """ # Merge filters: combine names with any additional filters from sdk_options - filters = sdk_options.get("Filters", []) + # without mutating the caller's Filters list (a copy avoids appending the + # name filter to a shared/module-level list on every call) name_filter = {"Key": "name", "Values": names} - - # Add name filter to existing filters - filters.append(name_filter) - sdk_options["Filters"] = filters + sdk_options["Filters"] = [*sdk_options.get("Filters", []), name_filter] # Remove SecretIdList if present to avoid conflicts sdk_options.pop("SecretIdList", None) diff --git a/tests/functional/parameters/_boto3/test_utilities_parameters.py b/tests/functional/parameters/_boto3/test_utilities_parameters.py index dc7643795c1..f8807adec98 100644 --- a/tests/functional/parameters/_boto3/test_utilities_parameters.py +++ b/tests/functional/parameters/_boto3/test_utilities_parameters.py @@ -3103,6 +3103,59 @@ def test_secrets_provider_get_multiple_basic(config): stubber.deactivate() +def test_secrets_provider_get_multiple_does_not_mutate_caller_filters(config): + """ + Test SecretsProvider.get_multiple() leaves the caller's Filters list untouched + """ + # GIVEN a SecretsProvider instance + provider = parameters.SecretsProvider(boto_config=config) + + # GIVEN a shared Filters list, as used in the documentation example + my_filters = [{"Key": "primary-region", "Values": ["us-east-1"]}] + + # Stub the boto3 client with two paginated-free responses, one per call + stubber = stub.Stubber(provider.client) + response_a = { + "SecretValues": [ + { + "Name": "a", + "SecretString": "1", + }, + ], + "Errors": [], + } + expected_params_a = { + "Filters": [*my_filters, {"Key": "name", "Values": ["a"]}], + } + stubber.add_response("batch_get_secret_value", response_a, expected_params_a) + + response_b = { + "SecretValues": [ + { + "Name": "b", + "SecretString": "2", + }, + ], + "Errors": [], + } + expected_params_b = { + "Filters": [*my_filters, {"Key": "name", "Values": ["b"]}], + } + stubber.add_response("batch_get_secret_value", response_b, expected_params_b) + stubber.activate() + + try: + # WHEN calling get_multiple twice with the same Filters list + assert provider.get_multiple(["a"], Filters=my_filters) == {"a": "1"} + assert provider.get_multiple(["b"], Filters=my_filters) == {"b": "2"} + stubber.assert_no_pending_responses() + + # THEN the caller's list must not have grown stale name filters + assert my_filters == [{"Key": "primary-region", "Values": ["us-east-1"]}] + finally: + stubber.deactivate() + + def test_secrets_provider_get_multiple_with_binary(config): """ Test SecretsProvider.get_multiple() with binary secrets