fix: Resolved anonymization action logic#519
Conversation
PR Review: Reversible Anonymization Support (#519)This PR adds end-to-end reversible anonymization: PII is replaced with random-but-format-preserving placeholders before the prompt hits the LLM, and the placeholders are swapped back to originals in the finalization step. Good direction overall. Below is a detailed review organized by severity. 🔴 Bugs / Correctness Issues1. The convenience function signature accepts # anonymization.py line 313 — current
return anonymize_contents(original_content, triggered_keywords, use_random_format) # bool, not dict!
# should be
return anonymize_contents(original_content, triggered_keywords)2. Transformation-map key collision in The merge logic uses base_idx = max(result.transformation_map.keys()) # not len()3. The sync 🟡 Design & Reliability Concerns4. Random replacements can collide with real content or with each other
5. Silent failure on Pydantic de-anonymization ( The bare 6. Anonymization notice is user-visible in task description ( The 7.
🟢 Minor / Style8. Dead import in 9. Eager import at module top in 10. 🔵 Test Coverage GapsNo tests were added for the new
The existing Summary
The core idea is sound and the 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| Returns: | ||
| Tuple of (anonymized_contents, transformation_map) | ||
| """ | ||
| return anonymize_contents(original_content, triggered_keywords, use_random_format) |
There was a problem hiding this comment.
Bool passed where Dict expected causes AttributeError
High Severity
The create_anonymization_result function passes use_random_format (a bool) as the third argument to anonymize_contents, but that function expects existing_map (an Optional[Dict]). When use_random_format=True is passed, the code at line 217 will attempt True.copy(), raising an AttributeError since booleans don't have a copy() method.
Additional Locations (1)
| debug_log( | ||
| f"De-anonymized response using {len(task._anonymization_map)} mappings", | ||
| "FinalizationStep" | ||
| ) |
There was a problem hiding this comment.
Missing null check for agent before accessing debug
Low Severity
The code accesses agent.debug at line 2986 without first checking if agent is not None. Earlier in the same function (line 2957), there's a defensive pattern if agent and hasattr(agent, 'run_id') suggesting agent may be None in some cases. If agent is None, this would cause an AttributeError.
| debug_log( | ||
| f"De-anonymized response using {len(task._anonymization_map)} mappings", | ||
| "FinalizationStep" | ||
| ) |
There was a problem hiding this comment.
De-anonymization corrupts data when multiple values share placeholder
Medium Severity
The de-anonymization logic applies to both REPLACE and ANONYMIZE actions identically. For REPLACE actions using a fixed placeholder (e.g., [REDACTED]) for multiple PII items, the transformation_map contains multiple entries all mapping to the same anonymous value. When deanonymize_content processes these, it performs sequential replacements where later entries overwrite earlier ones, causing incorrect data restoration. The privacy notice also misleadingly states "random placeholders" for REPLACE actions.


Note
Medium Risk
Touches the privacy/anonymization flow by transforming user prompts and post-processing model outputs, so mistakes could leak PII or corrupt returned content (especially for structured/Pydantic outputs). Changes are localized but affect all runs when anonymization triggers.
Overview
Adds reversible anonymization support end-to-end: safety policies can now return a
transformation_map, which is stored on theTaskwhen user input isREPLACE/ANONYMIZEand accompanied by a privacy-mode notice prepended to the prompt.Introduces
safety_engine.anonymizationutilities and exports them fromsafety_engine.__init__, updatesPolicyResult/PolicyManagerto aggregate transformation maps across policies, and updatesFinalizationStepto de-anonymize the finalcontext.output(and best-effort structured/Pydantic outputs) plustask._responsebefore returning to the caller.Simplifies
PIIAnonymizeActionto use the standardized anonymization path and hardensActionBase.anonymize_triggered_keywordsto skipPII_KEYWORD:markers and handle typed keywords consistently (case-insensitive regex replacement).Written by Cursor Bugbot for commit 008cdcb. This will update automatically on new commits. Configure here.