Skip to content

Add shared OutboundHostValidator anti-SSRF control - #543

Merged
Rodrigo Brandão (rodrigobr-msft) merged 7 commits into
mainfrom
users/robrandao/ssrf
Aug 18, 2026
Merged

Add shared OutboundHostValidator anti-SSRF control#543
Rodrigo Brandão (rodrigobr-msft) merged 7 commits into
mainfrom
users/robrandao/ssrf

Conversation

@rodrigobr-msft

Copy link
Copy Markdown
Contributor

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.

Copilot AI lite review requested due to automatic review settings August 17, 2026 21:23
@rodrigobr-msft
Rodrigo Brandão (rodrigobr-msft) marked this pull request as ready for review August 17, 2026 21:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot AI review requested due to automatic review settings August 18, 2026 18:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 tolerates None at runtime (and the new tests pass None), 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 a serviceurl claim 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-str inputs and returns them directly; None is also handled safely (it returns None), but the annotation currently excludes it. Align the annotation with the actual accepted inputs to avoid internal typing inconsistencies once is_allowed() accepts None.

This issue also appears on line 91 of the same file.

def _try_create_url(url: str | URL) -> URL | None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI review requested due to automatic review settings August 18, 2026 21:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 True is 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,
    ):

@rodrigobr-msft
Rodrigo Brandão (rodrigobr-msft) merged commit a667abf into main Aug 18, 2026
12 checks passed
@rodrigobr-msft
Rodrigo Brandão (rodrigobr-msft) deleted the users/robrandao/ssrf branch August 18, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants