Add `sanitize_messages` for inbound message-history hardening
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.runpath. It strips client-supplied system prompts, drops non-HTTP file URL schemes, resets non-allowlistedFileUrl.force_downloadvalues toFalse, 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.pynext tonarrow_message_parts— its sibling standalone function over a list of messages — and is exposed frompydantic_ai.messages(not the top-levelpydantic_ainamespace). - The
_sanitize_*helpers move out ofui/_adapter.py, andUIAdapter.sanitize_messagesnow delegates tosanitize_messages(no behavior change on the UI path — base adapter warning tests pass unchanged). - Includes a
bytearrayexclusion fix in the tool-return-content walk (alongsidestr/bytes). - Docs: a new "Loading untrusted history" section in
docs/message-history.mdand aninput.mdcross-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.