Add `sanitize_messages` for inbound message-history hardening

Merged
DS
4mo ago
+767-357

Adds a public sanitize_messages function that strips message parts that aren't safe to honor when the history came from an untrusted boundary (e.g. a browser request deserialized into message_history and passed straight to Agent.run).

What this adds

  • New pydantic_ai.messages.sanitize_messages — the same default sanitization the UI adapters already apply to client-submitted messages, now callable directly for the plain-Agent.run path. It strips client-supplied system prompts, drops non-HTTP file URL schemes, resets non-allowlisted FileUrl.force_download values to False, drops uploaded file references, and removes unresolved tool calls at the end of the history. Each sanitization can be toggled off via a keyword flag for trusted server-side content (strip_system_prompts, allowed_file_url_schemes, allowed_file_url_force_download, allow_uploaded_files, resolved_tool_call_ids).
  • It lives in messages.py next to narrow_message_parts — its sibling standalone function over a list of messages — and is exposed from pydantic_ai.messages (not the top-level pydantic_ai namespace).
  • The _sanitize_* helpers move out of ui/_adapter.py, and UIAdapter.sanitize_messages now delegates to sanitize_messages (no behavior change on the UI path — base adapter warning tests pass unchanged).
  • Includes a bytearray exclusion fix in the tool-return-content walk (alongside str/bytes).
  • Docs: a new "Loading untrusted history" section in docs/message-history.md and an input.md cross-reference.

Why

message_history is trusted-by-contract: parts passed to Agent.run are honored as-is. Applications that accept serialized history across a trust boundary — a browser request deserialized into message_history, for example — therefore need a way to sanitize it first. The UI adapters already apply this sanitization to client-submitted messages, but the plain-Agent.run path had no first-class equivalent. This function provides one, so applications that opt into accepting untrusted history have a documented way to sanitize it before handing it to the agent. It is not a fix for a live vulnerability.

Split from #5255

This is one of the PRs split out of #5255 (the multimodal-tool-return bundle) for focused review. This PR is the inbound trust-boundary sanitizer and is independent of the others (it applies cleanly on main on its own).

Checklist

  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • No breaking changes in accordance with the version policy.
  • PR title is fit for the release changelog.