From c56935883b1ff6769ae9907d705cfe872a18c301 Mon Sep 17 00:00:00 2001 From: macoladik <17333511+macoladik@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:34:07 +0900 Subject: [PATCH] fix(parameters): stop mutating caller's Filters list in SecretsProvider.get_multiple _get_multiple appended the generated {"Key": "name"} filter to the list passed through sdk_options["Filters"]. When callers reuse a module-level Filters constant, as the documentation example does, every call left a stale name filter behind and the next request sent all of them. Build a new list instead of mutating the caller's one, and cover the reuse case with a regression test. --- .../utilities/parameters/secrets.py | 10 ++--- .../_boto3/test_utilities_parameters.py | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/aws_lambda_powertools/utilities/parameters/secrets.py b/aws_lambda_powertools/utilities/parameters/secrets.py index eff4e745738..a517299b0b0 100644 --- a/aws_lambda_powertools/utilities/parameters/secrets.py +++ b/aws_lambda_powertools/utilities/parameters/secrets.py @@ -152,13 +152,11 @@ def _get_multiple(self, names: list[str], **sdk_options) -> dict[str, Any]: # t When the parameter provider fails to retrieve secrets """ - # Merge filters: combine names with any additional filters from sdk_options - filters = sdk_options.get("Filters", []) + # Merge filters: combine names with any additional filters from sdk_options. + # A new list is created on purpose: mutating the caller's list would leak + # a stale name filter into every subsequent call that reuses it. 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..4a00a6568a6 100644 --- a/tests/functional/parameters/_boto3/test_utilities_parameters.py +++ b/tests/functional/parameters/_boto3/test_utilities_parameters.py @@ -3103,6 +3103,47 @@ 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() does not mutate the caller's Filters list + """ + # GIVEN a SecretsProvider instance and a caller-owned Filters list + provider = parameters.SecretsProvider(boto_config=config) + caller_filters = [{"Key": "primary-region", "Values": ["us-east-1"]}] + + stubber = stub.Stubber(provider.client) + first_expected_params = { + "Filters": [{"Key": "primary-region", "Values": ["us-east-1"]}, {"Key": "name", "Values": ["first-secret"]}], + } + second_expected_params = { + "Filters": [{"Key": "primary-region", "Values": ["us-east-1"]}, {"Key": "name", "Values": ["second-secret"]}], + } + stubber.add_response( + "batch_get_secret_value", + {"SecretValues": [{"Name": "first-secret", "SecretString": "1"}], "Errors": []}, + first_expected_params, + ) + stubber.add_response( + "batch_get_secret_value", + {"SecretValues": [{"Name": "second-secret", "SecretString": "2"}], "Errors": []}, + second_expected_params, + ) + stubber.activate() + + try: + # WHEN calling get_multiple twice with the same Filters list + first = provider.get_multiple(["first-secret"], Filters=caller_filters) + second = provider.get_multiple(["second-secret"], Filters=caller_filters) + + # THEN each call sends only its own name filter and the caller's list stays untouched + assert first == {"first-secret": "1"} + assert second == {"second-secret": "2"} + assert caller_filters == [{"Key": "primary-region", "Values": ["us-east-1"]}] + stubber.assert_no_pending_responses() + finally: + stubber.deactivate() + + def test_secrets_provider_get_multiple_with_binary(config): """ Test SecretsProvider.get_multiple() with binary secrets