AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

PIIMiddleware State Hooks Miss Tool-Call Args And Corrupt Structured Content

PIIMiddleware has two divergent redaction paths: the state-level hooks (before_model/after_model) still manually stringify message content, while the recursive walker added in #37616 handles structured blocks and tool-call arguments. This causes tool-call-only AI messages to be skipped entirely and structured content blocks to be corrupted into their Python repr string, leading to PII leakage and downstream parsing errors.

highConfidence 85%LangchainAffected V1.3.1

Origin Analysis

The before_model and after_model hooks were not updated to reuse the shared recursive redaction logic introduced in PR #37616. They still use legacy code that checks for empty content and calls str(message.content), which bypasses tool_call redaction for AIMessage with empty content and turns list-typed structured content into a string representation, violating the expectation that structured content remains a list of blocks.
1. Create PIIMiddleware with apply_to_output=True.\n2. Build an AgentState containing an AIMessage with content='' and tool_calls=[ToolCall(name='send_email', args={'to': 'alice@example.com'}, id='c1')].\n3. Call middleware.after_model(state, Runtime()).\n4. Observe that the method returns None and the tool call args are not redacted (actual behavior), whereas they should be redacted to '[REDACTED_EMAIL]'.\n5. Create PIIMiddleware with apply_to_input=True and a HumanMessage with content=[{'type': 'text', 'text': 'Reach me at alice@example.com'}].\n6. Call middleware.before_model(state, Runtime()).\n7. Observe that the content is converted to a string representation like "[{'type': 'text', 'text': 'Reach me at alice@example.com'}]" instead of remaining a list of blocks with only the text redacted.

Fixing Code Block

Edge Case Audit

This fix assumes that _redact_base_message exists and is fully functional in the current codebase (as per PR #37616). If the method is missing or incomplete, the fix will raise AttributeError. Additionally, while the recursive redactor handles tool_call args and structured content, it may not cover all custom message subclasses; developers should extend the BaseMessage check if needed. Concurrency: the middleware is stateless for redaction patterns, but if any internal caching is introduced, ensure thread-safety. Rollback: if issues arise, revert to the original manual stringification logic while retaining the tool-call handling by adding a specific branch for AIMessage with empty content. Test thoroughly with streaming and non-streaming paths to ensure no regression in the existing redaction coverage.

Ecosystem Topology