Add shared OutboundHostValidator anti-SSRF control - #543
Conversation
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (4)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/outbound_host_validator.py:27
- _try_create_url currently returns the input unchanged for any non-str value. If a caller accidentally passes a non-URL truthy object (e.g., int, dict), is_allowed() will later attempt url_obj.absolute and raise AttributeError instead of returning False. Also, None is used in tests but is not reflected in the type signature.
try:
return URL(url) if isinstance(url, str) else url
except (ValueError, TypeError):
return None
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/outbound_host_validator.py:91
- is_allowed() is used with None in the test suite, but its type annotation excludes None. Updating the annotation keeps the public API consistent with actual usage and the behavior of _try_create_url.
def is_allowed(self, url: str | URL) -> bool:
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_http_adapter_base.py:142
- process_request() returns a 401 with message "Service URL is not allowed by the host validator.", but _validate_service_url() can also fail due to a serviceUrl claim host mismatch. The response message is misleading in that case; consider using a more generic message (or the default unauthorized()) that covers both failure modes.
if not self._validate_service_url(claims_identity, activity):
return HttpResponseFactory.unauthorized(
"Service URL is not allowed by the host validator."
)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_http_adapter_base.py:195
- _validate_service_url() checks
if not claims_identity, but ClaimsIdentity instances are always truthy (and process_request always supplies an instance). This branch is effectively dead code; using is_authenticated makes the intent explicit and avoids a misleading condition.
if not claims_identity:
return True
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/outbound_host_validator.py:91
OutboundHostValidator.is_allowed()already toleratesNoneat runtime (and the new tests passNone), but the type signature does not. Updating the annotation avoids confusing type-checker errors for callers.
def is_allowed(self, url: str | URL) -> bool:
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_http_adapter_base.py:139
- The 401 message is misleading:
_validate_service_url()can also return False due to aserviceurlclaim mismatch (not only an allow-list denial), but the response always says it was blocked by the host validator. Consider using a message that covers both cases so callers can diagnose correctly.
if not self._validate_service_url(claims_identity, activity):
return HttpResponseFactory.unauthorized(
"Service URL is not allowed by the host validator."
)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/outbound_host_validator.py:18
_try_create_url()is called with non-strinputs and returns them directly;Noneis also handled safely (it returnsNone), but the annotation currently excludes it. Align the annotation with the actual accepted inputs to avoid internal typing inconsistencies onceis_allowed()acceptsNone.
This issue also appears on line 91 of the same file.
def _try_create_url(url: str | URL) -> URL | None:
tracyboehrer (tracyboehrer)
left a comment
There was a problem hiding this comment.
This doesn't appear to have the 2 attachment downloaders that .net has. I would create an issue to add those, and also use the outbound validator there too.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (7)
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_http_adapter_base.py:193
- claims_identity is always a ClaimsIdentity instance here (it is defaulted to ClaimsIdentity() in process_request), so
if not claims_identity: return Trueis effectively dead code and can mislead future maintenance. Consider removing it.
if not claims_identity:
return True
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_http_adapter_base.py:194
- Use the shared constant for the service URL claim name instead of the hard-coded string "serviceurl". The codebase already centralizes this in AuthenticationConstants.SERVICE_URL_CLAIM (e.g., channel_service_adapter.py:253).
claims_service_url = claims_identity.get_claim_value("serviceurl")
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/outbound_host_validator.py:91
- is_allowed is used with None in tests, but its type annotation doesn't allow None. Updating the signature avoids type-checker friction and aligns with the actual supported inputs.
def is_allowed(self, url: str | URL) -> bool:
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/outbound_host_validator.py:27
- _try_create_url can return non-URL values (e.g., None or any non-str object) because it returns the input unchanged for non-str inputs. This makes the return type inaccurate and can lead to AttributeError later if callers accidentally pass a non-URL object; tests also pass None via is_allowed. Consider tightening the input handling so only str/URL/None are accepted and the function always returns URL|None.
def _try_create_url(url: str | URL) -> URL | None:
"""Attempts to create a URL object from the given string or URL.
:param url: The URL string or URL object to create.
:return: A URL object if successful, None otherwise.
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_http_adapter_base.py:44
- host_validator was added to the constructor but the parameter isn’t documented in the docstring below. Since this is a public entry point for enabling outbound host enforcement, please add it to the :param list.
This issue also appears in the following locations of the same file:
- line 191
- line 194
connection_manager: Connections | None = None,
channel_service_client_factory: ChannelServiceClientFactoryBase | None = None,
channel_service_client_factory_options: dict | None = None,
host_validator: OutboundHostValidator | None = None,
):
libraries/microsoft-agents-hosting-fastapi/microsoft_agents/hosting/fastapi/cloud_adapter.py:33
- host_validator was added to the constructor signature, but the docstring doesn’t document what it does or how to use it. Please add a :param host_validator: entry to avoid a silent/documentation-breaking API change.
connection_manager: Connections | None = None,
channel_service_client_factory: ChannelServiceClientFactoryBase | None = None,
channel_service_client_factory_options: dict | None = None,
host_validator: OutboundHostValidator | None = None,
):
libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/cloud_adapter.py:32
- host_validator was added to the constructor signature, but the docstring doesn’t document what it does or how to use it. Please add a :param host_validator: entry to avoid a silent/documentation-breaking API change.
connection_manager: Connections | None = None,
channel_service_client_factory: ChannelServiceClientFactoryBase | None = None,
channel_service_client_factory_options: dict | None = None,
host_validator: OutboundHostValidator | None = None,
):
This pull request introduces a new outbound host validation mechanism to the Microsoft Agents Hosting SDK, enhancing security by ensuring that outbound requests are only made to allowed hosts. The main changes include adding the OutboundHostValidator class, integrating host validation into the HTTP adapter base, and updating both the FastAPI and Aiohttp cloud adapters to support host validation.
Security: Outbound Host Validation
Added the OutboundHostValidator class (outbound_host_validator.py) to centralize logic for validating outbound URLs against a configurable allow-list of host suffixes, with support for default Microsoft service hosts. (libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/outbound_host_validator.pyR1-R116)
Integrated host validation into the HTTP adapter base (_http_adapter_base.py): outbound requests are now checked against the validator, and unauthorized requests are blocked with appropriate responses. ([1] [2] [3] [4])
Adapter Integration
Updated both cloud_adapter.py files for FastAPI and Aiohttp to accept an optional host_validator parameter and pass it to the base adapter, enabling host validation in these hosting environments. ([1] [2] [3] [4] [5] [6])
Core Module Export
Exported OutboundHostValidator from the core module’s init.py, making it available for external use and configuration. ([1] [2])
These changes collectively improve the security posture of the SDK by helping prevent SSRF (Server-Side Request Forgery) attacks and giving developers fine-grained control over which hosts the agent can communicate with.