feat: add response sanitization and validation for MCP authorization

- Add Field(exclude=True) to authorization parameter to prevent token leakage in responses
- Add model validator to reject Authorization header in headers dict
- Users must use dedicated 'authorization' parameter instead of headers
- Headers field is preserved for legitimate non-auth headers (tracing, routing, etc.)

This implements the security requirement that authorization params are never
returned in responses, unlike generic headers which may be echoed back.
This commit is contained in:
Omar Abdelwahab 2025-11-07 10:50:20 -08:00
parent 267c895827
commit 1c27c1bef6

View file

@ -486,8 +486,8 @@ class OpenAIResponseInputToolMCP(BaseModel):
:param type: Tool type identifier, always "mcp"
:param server_label: Label to identify this MCP server
:param server_url: URL endpoint of the MCP server
:param headers: (Optional) HTTP headers to include when connecting to the server
:param authorization: (Optional) OAuth access token for authenticating with the MCP server
:param headers: (Optional) HTTP headers to include when connecting to the server (cannot contain Authorization)
:param authorization: (Optional) OAuth access token for authenticating with the MCP server (excluded from responses)
:param require_approval: Approval requirement for tool calls ("always", "never", or filter)
:param allowed_tools: (Optional) Restriction on which tools can be used from this server
"""
@ -496,11 +496,28 @@ class OpenAIResponseInputToolMCP(BaseModel):
server_label: str
server_url: str
headers: dict[str, Any] | None = None
authorization: str | None = None
# Authorization is excluded from serialization for security (never returned in responses)
authorization: str | None = Field(default=None, exclude=True)
require_approval: Literal["always"] | Literal["never"] | ApprovalFilter = "never"
allowed_tools: list[str] | AllowedToolsFilter | None = None
@model_validator(mode="after")
def validate_no_auth_in_headers(self) -> "OpenAIResponseInputToolMCP":
"""Ensure Authorization header is not passed via headers dict.
Authorization must be provided via the dedicated 'authorization' parameter
to ensure proper security handling and prevent token leakage in responses.
"""
if self.headers:
for key in self.headers.keys():
if key.lower() == "authorization":
raise ValueError(
"Authorization header cannot be passed via 'headers'. "
"Please use the 'authorization' parameter instead."
)
return self
OpenAIResponseInputTool = Annotated[
OpenAIResponseInputToolWebSearch