diff --git a/plugins/pii_filter/pii_filter.py b/plugins/pii_filter/pii_filter.py index c18fde260..eff6e44cb 100644 --- a/plugins/pii_filter/pii_filter.py +++ b/plugins/pii_filter/pii_filter.py @@ -95,34 +95,19 @@ class PIIFilterConfig(BaseModel): detect_api_keys: bool = Field(default=True, description="Detect generic API keys") # Masking configuration - default_mask_strategy: MaskingStrategy = Field( - default=MaskingStrategy.REDACT, - description="Default masking strategy" - ) + default_mask_strategy: MaskingStrategy = Field(default=MaskingStrategy.REDACT, description="Default masking strategy") redaction_text: str = Field(default="[REDACTED]", description="Text to use for redaction") # Behavior configuration - block_on_detection: bool = Field( - default=False, - description="Block request if PII is detected" - ) + block_on_detection: bool = Field(default=False, description="Block request if PII is detected") log_detections: bool = Field(default=True, description="Log PII detections") - include_detection_details: bool = Field( - default=True, - description="Include detection details in metadata" - ) + include_detection_details: bool = Field(default=True, description="Include detection details in metadata") # Custom patterns - custom_patterns: List[PIIPattern] = Field( - default_factory=list, - description="Custom PII patterns to detect" - ) + custom_patterns: List[PIIPattern] = Field(default_factory=list, description="Custom PII patterns to detect") # Whitelist configuration - whitelist_patterns: List[str] = Field( - default_factory=list, - description="Patterns to exclude from PII detection" - ) + whitelist_patterns: List[str] = Field(default_factory=list, description="Patterns to exclude from PII detection") class PIIDetector: @@ -145,151 +130,104 @@ def _compile_patterns(self) -> None: # Social Security Number patterns if self.config.detect_ssn: - patterns.append(PIIPattern( - type=PIIType.SSN, - pattern=r'\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b', - description="US Social Security Number", - mask_strategy=MaskingStrategy.PARTIAL - )) + patterns.append(PIIPattern(type=PIIType.SSN, pattern=r"\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b", description="US Social Security Number", mask_strategy=MaskingStrategy.PARTIAL)) # Credit Card patterns (basic validation for common formats) if self.config.detect_credit_card: - patterns.append(PIIPattern( - type=PIIType.CREDIT_CARD, - pattern=r'\b(?:\d{4}[-\s]?){3}\d{4}\b', - description="Credit card number", - mask_strategy=MaskingStrategy.PARTIAL - )) + patterns.append(PIIPattern(type=PIIType.CREDIT_CARD, pattern=r"\b(?:\d{4}[-\s]?){3}\d{4}\b", description="Credit card number", mask_strategy=MaskingStrategy.PARTIAL)) # Email patterns if self.config.detect_email: - patterns.append(PIIPattern( - type=PIIType.EMAIL, - pattern=r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', - description="Email address", - mask_strategy=MaskingStrategy.PARTIAL - )) + patterns.append(PIIPattern(type=PIIType.EMAIL, pattern=r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", description="Email address", mask_strategy=MaskingStrategy.PARTIAL)) # Phone number patterns (US and international) if self.config.detect_phone: - patterns.extend([ - PIIPattern( - type=PIIType.PHONE, - pattern=r'\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b', - description="US phone number", - mask_strategy=MaskingStrategy.PARTIAL - ), - PIIPattern( - type=PIIType.PHONE, - pattern=r'\b\+?[1-9]\d{1,14}\b', - description="International phone number", - mask_strategy=MaskingStrategy.PARTIAL - ) - ]) + patterns.extend( + [ + PIIPattern(type=PIIType.PHONE, pattern=r"\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b", description="US phone number", mask_strategy=MaskingStrategy.PARTIAL), + PIIPattern(type=PIIType.PHONE, pattern=r"\b\+?[1-9]\d{1,14}\b", description="International phone number", mask_strategy=MaskingStrategy.PARTIAL), + ] + ) # IP Address patterns (IPv4 and IPv6) if self.config.detect_ip_address: - patterns.extend([ - PIIPattern( - type=PIIType.IP_ADDRESS, - pattern=r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b', - description="IPv4 address", - mask_strategy=MaskingStrategy.REDACT - ), - PIIPattern( - type=PIIType.IP_ADDRESS, - pattern=r'\b(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\b', - description="IPv6 address", - mask_strategy=MaskingStrategy.REDACT - ) - ]) + patterns.extend( + [ + PIIPattern( + type=PIIType.IP_ADDRESS, + pattern=r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b", + description="IPv4 address", + mask_strategy=MaskingStrategy.REDACT, + ), + PIIPattern(type=PIIType.IP_ADDRESS, pattern=r"\b(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\b", description="IPv6 address", mask_strategy=MaskingStrategy.REDACT), + ] + ) # Date of Birth patterns if self.config.detect_date_of_birth: - patterns.extend([ - PIIPattern( - type=PIIType.DATE_OF_BIRTH, - pattern=r'\b(?:DOB|Date of Birth|Born|Birthday)[:\s]+\d{1,2}[-/]\d{1,2}[-/]\d{2,4}\b', - description="Date of birth with label", - mask_strategy=MaskingStrategy.REDACT - ), - PIIPattern( - type=PIIType.DATE_OF_BIRTH, - pattern=r'\b(?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12]\d|3[01])[-/](?:19|20)\d{2}\b', - description="Date in MM/DD/YYYY format", - mask_strategy=MaskingStrategy.REDACT - ) - ]) + patterns.extend( + [ + PIIPattern( + type=PIIType.DATE_OF_BIRTH, + pattern=r"\b(?:DOB|Date of Birth|Born|Birthday)[:\s]+\d{1,2}[-/]\d{1,2}[-/]\d{2,4}\b", + description="Date of birth with label", + mask_strategy=MaskingStrategy.REDACT, + ), + PIIPattern( + type=PIIType.DATE_OF_BIRTH, + pattern=r"\b(?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12]\d|3[01])[-/](?:19|20)\d{2}\b", + description="Date in MM/DD/YYYY format", + mask_strategy=MaskingStrategy.REDACT, + ), + ] + ) # Passport patterns if self.config.detect_passport: - patterns.append(PIIPattern( - type=PIIType.PASSPORT, - pattern=r'\b[A-Z]{1,2}\d{6,9}\b', - description="Passport number", - mask_strategy=MaskingStrategy.REDACT - )) + patterns.append(PIIPattern(type=PIIType.PASSPORT, pattern=r"\b[A-Z]{1,2}\d{6,9}\b", description="Passport number", mask_strategy=MaskingStrategy.REDACT)) # Driver's License patterns (US states) if self.config.detect_driver_license: - patterns.append(PIIPattern( - type=PIIType.DRIVER_LICENSE, - pattern=r'\b(?:DL|License|Driver\'?s? License)[#:\s]+[A-Z0-9]{5,20}\b', - description="Driver's license number", - mask_strategy=MaskingStrategy.REDACT - )) + patterns.append( + PIIPattern( + type=PIIType.DRIVER_LICENSE, pattern=r"\b(?:DL|License|Driver\'?s? License)[#:\s]+[A-Z0-9]{5,20}\b", description="Driver's license number", mask_strategy=MaskingStrategy.REDACT + ) + ) # Bank Account patterns if self.config.detect_bank_account: - patterns.extend([ - PIIPattern( - type=PIIType.BANK_ACCOUNT, - pattern=r'\b\d{8,17}\b', # Generic bank account - description="Bank account number", - mask_strategy=MaskingStrategy.REDACT - ), - PIIPattern( - type=PIIType.BANK_ACCOUNT, - pattern=r'\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}(?:\d{3})?\b', # IBAN - description="IBAN", - mask_strategy=MaskingStrategy.PARTIAL - ) - ]) + patterns.extend( + [ + PIIPattern(type=PIIType.BANK_ACCOUNT, pattern=r"\b\d{8,17}\b", description="Bank account number", mask_strategy=MaskingStrategy.REDACT), # Generic bank account + PIIPattern(type=PIIType.BANK_ACCOUNT, pattern=r"\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}(?:\d{3})?\b", description="IBAN", mask_strategy=MaskingStrategy.PARTIAL), # IBAN + ] + ) # Medical Record patterns if self.config.detect_medical_record: - patterns.append(PIIPattern( - type=PIIType.MEDICAL_RECORD, - pattern=r'\b(?:MRN|Medical Record)[#:\s]+[A-Z0-9]{6,12}\b', - description="Medical record number", - mask_strategy=MaskingStrategy.REDACT - )) + patterns.append( + PIIPattern(type=PIIType.MEDICAL_RECORD, pattern=r"\b(?:MRN|Medical Record)[#:\s]+[A-Z0-9]{6,12}\b", description="Medical record number", mask_strategy=MaskingStrategy.REDACT) + ) # AWS Access Key patterns if self.config.detect_aws_keys: - patterns.extend([ - PIIPattern( - type=PIIType.AWS_KEY, - pattern=r'\bAKIA[0-9A-Z]{16}\b', - description="AWS Access Key ID", - mask_strategy=MaskingStrategy.REDACT - ), - PIIPattern( - type=PIIType.AWS_KEY, - pattern=r'\b[A-Za-z0-9/+=]{40}\b', - description="AWS Secret Access Key", - mask_strategy=MaskingStrategy.REDACT - ) - ]) + patterns.extend( + [ + PIIPattern(type=PIIType.AWS_KEY, pattern=r"\bAKIA[0-9A-Z]{16}\b", description="AWS Access Key ID", mask_strategy=MaskingStrategy.REDACT), + PIIPattern(type=PIIType.AWS_KEY, pattern=r"\b[A-Za-z0-9/+=]{40}\b", description="AWS Secret Access Key", mask_strategy=MaskingStrategy.REDACT), + ] + ) # Generic API Key patterns if self.config.detect_api_keys: - patterns.append(PIIPattern( - type=PIIType.API_KEY, - pattern=r'\b(?:api[_-]?key|apikey|api_token|access[_-]?token)[:\s]+[\'"]?[A-Za-z0-9\-_]{20,}[\'"]?\b', - description="Generic API key", - mask_strategy=MaskingStrategy.REDACT - )) + patterns.append( + PIIPattern( + type=PIIType.API_KEY, + pattern=r'\b(?:api[_-]?key|apikey|api_token|access[_-]?token)[:\s]+[\'"]?[A-Za-z0-9\-_]{20,}[\'"]?\b', + description="Generic API key", + mask_strategy=MaskingStrategy.REDACT, + ) + ) # Add custom patterns patterns.extend(self.config.custom_patterns) @@ -300,16 +238,11 @@ def _compile_patterns(self) -> None: compiled = re.compile(pattern_config.pattern, re.IGNORECASE) if pattern_config.type not in self.patterns: self.patterns[pattern_config.type] = [] - self.patterns[pattern_config.type].append( - (compiled, pattern_config.mask_strategy) - ) + self.patterns[pattern_config.type].append((compiled, pattern_config.mask_strategy)) def _compile_whitelist(self) -> None: """Compile whitelist patterns.""" - self.whitelist_patterns = [ - re.compile(pattern, re.IGNORECASE) - for pattern in self.config.whitelist_patterns - ] + self.whitelist_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.config.whitelist_patterns] def _is_whitelisted(self, text: str, match_start: int, match_end: int) -> bool: """Check if a matched pattern is whitelisted. @@ -349,19 +282,12 @@ def detect(self, text: str) -> Dict[PIIType, List[Dict]]: # Check if this overlaps with any existing detection overlaps = False for start, end in seen_ranges: - if (match.start() >= start and match.start() < end) or \ - (match.end() > start and match.end() <= end) or \ - (match.start() <= start and match.end() >= end): + if (match.start() >= start and match.start() < end) or (match.end() > start and match.end() <= end) or (match.start() <= start and match.end() >= end): overlaps = True break if not overlaps: - type_detections.append({ - 'value': match.group(), - 'start': match.start(), - 'end': match.end(), - 'mask_strategy': mask_strategy - }) + type_detections.append({"value": match.group(), "start": match.start(), "end": match.end(), "mask_strategy": mask_strategy}) seen_ranges.append((match.start(), match.end())) if type_detections: @@ -386,25 +312,17 @@ def mask(self, text: str, detections: Dict[PIIType, List[Dict]]) -> str: all_detections = [] for pii_type, items in detections.items(): for item in items: - item['type'] = pii_type + item["type"] = pii_type all_detections.append(item) - all_detections.sort(key=lambda x: x['start'], reverse=True) + all_detections.sort(key=lambda x: x["start"], reverse=True) # Apply masking masked_text = text for detection in all_detections: - strategy = detection.get('mask_strategy', self.config.default_mask_strategy) - masked_value = self._apply_mask( - detection['value'], - detection['type'], - strategy - ) - masked_text = ( - masked_text[:detection['start']] + - masked_value + - masked_text[detection['end']:] - ) + strategy = detection.get("mask_strategy", self.config.default_mask_strategy) + masked_value = self._apply_mask(detection["value"], detection["type"], strategy) + masked_text = masked_text[: detection["start"]] + masked_value + masked_text[detection["end"] :] return masked_text @@ -435,7 +353,7 @@ def _apply_mask(self, value: str, pii_type: PIIType, strategy: MaskingStrategy) return self.config.redaction_text elif pii_type == PIIType.EMAIL: - parts = value.split('@') + parts = value.split("@") if len(parts) == 2: name = parts[0] if len(name) > 2: @@ -457,6 +375,7 @@ def _apply_mask(self, value: str, pii_type: PIIType, strategy: MaskingStrategy) elif strategy == MaskingStrategy.HASH: # Standard import hashlib + return f"[HASH:{hashlib.sha256(value.encode()).hexdigest()[:8]}]" elif strategy == MaskingStrategy.TOKENIZE: @@ -487,11 +406,7 @@ def __init__(self, config: PluginConfig): self.detection_count = 0 self.masked_count = 0 - async def prompt_pre_fetch( - self, - payload: PromptPrehookPayload, - context: PluginContext - ) -> PromptPrehookResult: + async def prompt_pre_fetch(self, payload: PromptPrehookPayload, context: PluginContext) -> PromptPrehookResult: """Process prompt before retrieval to detect and mask PII. Args: @@ -516,26 +431,16 @@ async def prompt_pre_fetch( all_detections[key] = detections if self.pii_config.log_detections: - logger.warning( - f"PII detected in prompt argument '{key}': " - f"{', '.join(detections.keys())}" - ) + logger.warning(f"PII detected in prompt argument '{key}': " f"{', '.join(detections.keys())}") if self.pii_config.block_on_detection: violation = PluginViolation( reason="PII detected in prompt", description=f"Sensitive information detected in argument '{key}'", code="PII_DETECTED", - details={ - "field": key, - "types": list(detections.keys()), - "count": sum(len(items) for items in detections.values()) - } - ) - return PromptPrehookResult( - continue_processing=False, - violation=violation + details={"field": key, "types": list(detections.keys()), "count": sum(len(items) for items in detections.values())}, ) + return PromptPrehookResult(continue_processing=False, violation=violation) # Mask the PII masked_value = self.detector.mask(value, detections) @@ -552,35 +457,18 @@ async def prompt_pre_fetch( "pre_fetch": { "detected": True, "fields": list(all_detections.keys()), - "types": list(set( - pii_type - for field_detections in all_detections.values() - for pii_type in field_detections.keys() - )), - "total_count": sum( - len(items) - for field_detections in all_detections.values() - for items in field_detections.values() - ) + "types": list(set(pii_type for field_detections in all_detections.values() for pii_type in field_detections.keys())), + "total_count": sum(len(items) for field_detections in all_detections.values() for items in field_detections.values()), } } # Return modified payload if PII was masked if all_detections: - return PromptPrehookResult( - modified_payload=PromptPrehookPayload( - name=payload.name, - args=modified_args - ) - ) + return PromptPrehookResult(modified_payload=PromptPrehookPayload(name=payload.name, args=modified_args)) return PromptPrehookResult() - async def prompt_post_fetch( - self, - payload: PromptPosthookPayload, - context: PluginContext - ) -> PromptPosthookResult: + async def prompt_post_fetch(self, payload: PromptPosthookPayload, context: PluginContext) -> PromptPosthookResult: """Process prompt after rendering to detect and mask PII in response. Args: @@ -598,7 +486,7 @@ async def prompt_post_fetch( # Process each message for message in payload.result.messages: - if message.content and hasattr(message.content, 'text'): + if message.content and hasattr(message.content, "text"): text = message.content.text detections = self.detector.detect(text) @@ -606,10 +494,7 @@ async def prompt_post_fetch( all_detections[f"message_{message.role}"] = detections if self.pii_config.log_detections: - logger.warning( - f"PII detected in {message.role} message: " - f"{', '.join(detections.keys())}" - ) + logger.warning(f"PII detected in {message.role} message: " f"{', '.join(detections.keys())}") # Mask the PII masked_text = self.detector.mask(text, detections) @@ -625,23 +510,12 @@ async def prompt_post_fetch( context.metadata["pii_detections"]["post_fetch"] = { "detected": True, "messages": list(all_detections.keys()), - "types": list(set( - pii_type - for msg_detections in all_detections.values() - for pii_type in msg_detections.keys() - )), - "total_count": sum( - len(items) - for msg_detections in all_detections.values() - for items in msg_detections.values() - ) + "types": list(set(pii_type for msg_detections in all_detections.values() for pii_type in msg_detections.keys())), + "total_count": sum(len(items) for msg_detections in all_detections.values() for items in msg_detections.values()), } # Add summary statistics - context.metadata["pii_filter_stats"] = { - "total_detections": self.detection_count, - "total_masked": self.masked_count - } + context.metadata["pii_filter_stats"] = {"total_detections": self.detection_count, "total_masked": self.masked_count} if modified: return PromptPosthookResult(modified_payload=payload) @@ -670,33 +544,19 @@ async def tool_pre_invoke(self, payload: ToolPreInvokePayload, context: PluginCo modified, detections = self._process_nested_data_for_pii(payload.args, "args", all_detections) if detections: - detected_types = list(set( - pii_type - for arg_detections in all_detections.values() - for pii_type in arg_detections.keys() - )) + detected_types = list(set(pii_type for arg_detections in all_detections.values() for pii_type in arg_detections.keys())) if self.pii_config.log_detections: - logger.warning( - f"PII detected in tool '{payload.name}' arguments: {', '.join(map(str, detected_types))}" - ) + logger.warning(f"PII detected in tool '{payload.name}' arguments: {', '.join(map(str, detected_types))}") if detections and self.pii_config.block_on_detection: violation = PluginViolation( reason="PII detected in tool arguments", - description=f"Detected PII in tool arguments", + description="Detected PII in tool arguments", code="PII_DETECTED_IN_TOOL_ARGS", details={ - "detected_types": list(set( - pii_type - for arg_detections in all_detections.values() - for pii_type in arg_detections.keys() - )), - "total_count": sum( - len(items) - for arg_detections in all_detections.values() - for items in arg_detections.values() - ) - } + "detected_types": list(set(pii_type for arg_detections in all_detections.values() for pii_type in arg_detections.keys())), + "total_count": sum(len(items) for arg_detections in all_detections.values() for items in arg_detections.values()), + }, ) return ToolPreInvokeResult(continue_processing=False, violation=violation) @@ -708,16 +568,8 @@ async def tool_pre_invoke(self, payload: ToolPreInvokePayload, context: PluginCo context.metadata["pii_detections"]["tool_pre_invoke"] = { "detected": True, "arguments": list(all_detections.keys()), - "types": list(set( - pii_type - for arg_detections in all_detections.values() - for pii_type in arg_detections.keys() - )), - "total_count": sum( - len(items) - for arg_detections in all_detections.values() - for items in arg_detections.values() - ) + "types": list(set(pii_type for arg_detections in all_detections.values() for pii_type in arg_detections.keys())), + "total_count": sum(len(items) for arg_detections in all_detections.values() for items in arg_detections.values()), } if modified: @@ -760,10 +612,7 @@ async def tool_post_invoke(self, payload: ToolPostInvokePayload, context: Plugin reason="PII detected in tool result", description=f"Detected {', '.join(detections.keys())} in tool output", code="PII_DETECTED_IN_TOOL_RESULT", - details={ - "detected_types": list(detections.keys()), - "count": sum(len(items) for items in detections.values()) - } + details={"detected_types": list(detections.keys()), "count": sum(len(items) for items in detections.values())}, ) return ToolPostInvokeResult(continue_processing=False, violation=violation) @@ -778,20 +627,12 @@ async def tool_post_invoke(self, payload: ToolPostInvokePayload, context: Plugin if detections and self.pii_config.block_on_detection: violation = PluginViolation( reason="PII detected in tool result", - description=f"Detected PII in nested tool result data", + description="Detected PII in nested tool result data", code="PII_DETECTED_IN_TOOL_RESULT", details={ - "detected_types": list(set( - pii_type - for field_detections in all_detections.values() - for pii_type in field_detections.keys() - )), - "total_count": sum( - len(items) - for field_detections in all_detections.values() - for items in field_detections.values() - ) - } + "detected_types": list(set(pii_type for field_detections in all_detections.values() for pii_type in field_detections.keys())), + "total_count": sum(len(items) for field_detections in all_detections.values() for items in field_detections.values()), + }, ) return ToolPostInvokeResult(continue_processing=False, violation=violation) @@ -803,23 +644,12 @@ async def tool_post_invoke(self, payload: ToolPostInvokePayload, context: Plugin context.metadata["pii_detections"]["tool_post_invoke"] = { "detected": True, "fields": list(all_detections.keys()), - "types": list(set( - pii_type - for field_detections in all_detections.values() - for pii_type in field_detections.keys() - )), - "total_count": sum( - len(items) - for field_detections in all_detections.values() - for items in field_detections.values() - ) + "types": list(set(pii_type for field_detections in all_detections.values() for pii_type in field_detections.keys())), + "total_count": sum(len(items) for field_detections in all_detections.values() for items in field_detections.values()), } # Update summary statistics - context.metadata["pii_filter_stats"] = { - "total_detections": self.detection_count, - "total_masked": self.masked_count - } + context.metadata["pii_filter_stats"] = {"total_detections": self.detection_count, "total_masked": self.masked_count} if modified: logger.info(f"Modified tool '{payload.name}' result to mask PII") @@ -855,18 +685,11 @@ def _process_nested_data_for_pii(self, data: Any, path: str, all_detections: dic if self.pii_config.log_detections: logger.warning(f"PII detected in tool result at '{path}': {', '.join(detections.keys())}") - # Mask the PII in-place if possible - if hasattr(data, '__setitem__'): # This won't work for strings, but we handle that in the caller - masked_data = self.detector.mask(data, detections) - # We can't modify strings in place, so return the masked version - # The caller needs to handle the assignment - modified = True - self.masked_count += sum(len(items) for items in detections.values()) - # Try to parse as JSON and process nested content try: # Standard import json + parsed_json = json.loads(data) json_modified, json_detections = self._process_nested_data_for_pii(parsed_json, f"{path}(json)", all_detections) has_detections = has_detections or json_detections @@ -896,11 +719,12 @@ def _process_nested_data_for_pii(self, data: Any, path: str, all_detections: dic try: # Standard import json + parsed_json = json.loads(value) # Apply masking to the parsed JSON self._apply_pii_masking_to_parsed_json(parsed_json, json_path, all_detections) # Re-serialize with masked data - data[key] = json.dumps(parsed_json, ensure_ascii=False, separators=(',', ':')) + data[key] = json.dumps(parsed_json, ensure_ascii=False, separators=(",", ":")) modified = True except (json.JSONDecodeError, TypeError): pass @@ -928,11 +752,12 @@ def _process_nested_data_for_pii(self, data: Any, path: str, all_detections: dic try: # Standard import json + parsed_json = json.loads(item) # Apply masking to the parsed JSON self._apply_pii_masking_to_parsed_json(parsed_json, json_path, all_detections) # Re-serialize with masked data - data[i] = json.dumps(parsed_json, ensure_ascii=False, separators=(',', ':')) + data[i] = json.dumps(parsed_json, ensure_ascii=False, separators=(",", ":")) modified = True except (json.JSONDecodeError, TypeError): pass @@ -983,7 +808,4 @@ def _apply_pii_masking_to_parsed_json(self, data: Any, base_path: str, all_detec async def shutdown(self) -> None: """Cleanup when plugin shuts down.""" - logger.info( - f"PII Filter plugin shutting down. " - f"Total masked: {self.masked_count} items" - ) + logger.info(f"PII Filter plugin shutting down. " f"Total masked: {self.masked_count} items")