From 9506c1b90f4ddcfafdd6bb0acac77194933c6df6 Mon Sep 17 00:00:00 2001 From: Guillaume Marty Date: Mon, 19 Oct 2026 12:40:46 +0200 Subject: [PATCH] fix(data-masking): parse JSON string input when erasing with masking_rules erase(data, fields=...) accepts a JSON string, but erase(data, masking_rules=...) ran the JSONPath rules against the raw string, so no path ever matched: it raised DataMaskingFieldNotFoundError, or returned the data unmasked when raise_on_missing_field=False. --- .../utilities/data_masking/base.py | 4 ++++ .../test_erase_data_masking.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/aws_lambda_powertools/utilities/data_masking/base.py b/aws_lambda_powertools/utilities/data_masking/base.py index a7c0d355150..0dc8afb4fb9 100644 --- a/aws_lambda_powertools/utilities/data_masking/base.py +++ b/aws_lambda_powertools/utilities/data_masking/base.py @@ -422,6 +422,10 @@ def _apply_masking_rules(self, data: dict, masking_rules: dict) -> dict: Returns: dict: The masked data dictionary """ + if isinstance(data, str): + # Same as `fields`: JSON strings are parsed so path expressions can be resolved + data = self.json_deserializer(data) + result = deepcopy(data) for path, rule in masking_rules.items(): diff --git a/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py b/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py index 44f54458800..45f636e1c7e 100644 --- a/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py +++ b/tests/functional/data_masking/required_dependencies/test_erase_data_masking.py @@ -364,6 +364,21 @@ def test_mask_nested_field_success(data_masker): assert data_masked == {"user": {"contact": {"details": {"address": {"street": "123 Main St", "zip": "xxx"}}}}} +def test_erase_json_string_with_masking_rules(data_masker): + # GIVEN the data type is a JSON string + data = json.dumps({"email": "johndoe@example.com", "address": {"zip": 13000, "street": "123 Main St"}}) + + # WHEN erase is called with masking rules + masking_rules = { + "email": {"regex_pattern": "(.)(.*)(@.*)", "mask_format": r"\1****\3"}, + "address.zip": {"custom_mask": "xxx"}, + } + masked = data_masker.erase(data=data, masking_rules=masking_rules) + + # THEN the JSON string is parsed and the fields are masked, as with `fields` + assert masked == {"email": "j****@example.com", "address": {"zip": "xxx", "street": "123 Main St"}} + + def test_erase_dictionary_with_masking_rules(data_masker): # GIVEN a dictionary with nested sensitive data data = {"user": {"name": "John Doe", "ssn": "123-45-6789", "address": {"street": "123 Main St", "zip": "12345"}}}