Skip to content

Add support for suffix-based (wildcard) matching in AllowedHostsValidator #700

Description

@dabla

Is your feature request related to a problem? Please describe.

The AllowedHostsValidator currently only supports exact host matching. This becomes a significant limitation when working with services that use dynamic, tenant-specific subdomains — most notably Microsoft Fabric.

For example, Microsoft Fabric GraphQL endpoints follow this pattern:

https://<workspace-guid>.z2c.graphql.fabric.microsoft.com/v1/workspaces/<workspace-id>/graphqlapis/<api-id>/graphql

Where <workspace-guid> is unique per workspace (e.g. 2c662c476e2f45e0b842bb63307de046). Since the subdomain changes per workspace, it is impossible to pre-configure the full hostname in allowed_hosts. In practice, this forces developers to:

  1. Enumerate and register every workspace-specific hostname ahead of time, or
  2. Set allowed_hosts to an empty list, which disables host validation entirely and silently sends bearer tokens to any host — a security risk.

Neither option is practical or safe when consuming multiple Fabric workspaces.

Describe the solution you'd like

Support suffix-based (wildcard) matching in AllowedHostsValidator. A host entry starting with . would match any subdomain under that domain. For example:

AllowedHostsValidator(allowed_hosts=[".fabric.microsoft.com"])

Would match:

  • 2c662c47.z2c.graphql.fabric.microsoft.com
  • a1b2c3d4.z2c.graphql.fabric.microsoft.com
  • fabric.microsoft.com (the bare domain itself) ❌ — suffix matching only applies to subdomains

The implementation in is_url_host_valid would add a single check alongside the existing exact-match logic:

def is_url_host_valid(self, url: str) -> bool:
    if not url:
        return False
    if not self.get_allowed_hosts():
        return True
    o = urlparse(url)
    if not all([o.scheme, o.netloc]):
        return False
    if not o.hostname:
        return False
    hostname = o.hostname.lower()
    # Existing exact match
    if hostname in self.allowed_hosts:
        return True
    # Suffix match for entries starting with "."
    return any(
        suffix.startswith(".") and hostname.endswith(suffix)
        for suffix in self.allowed_hosts
    )

The . prefix convention is well-established (it mirrors cookie domain scoping in RFC 6265 and DNS zone delegation) and is unambiguous: existing exact-match entries are unaffected, so this is fully backward-compatible.

Describe alternatives you've considered

  1. Glob/regex patterns (e.g. *.fabric.microsoft.com): More expressive, but increases attack surface. A malformed regex or overly broad glob could inadvertently match unintended hosts. Suffix matching with the . prefix convention is simpler, harder to misconfigure, and covers the primary use case.

  2. Callback-based validation (e.g. passing a Callable[[str], bool]): Maximum flexibility, but shifts security responsibility entirely to the consumer and makes misconfiguration easy. It would also break the current simple list-based API contract.

  3. Empty allowed_hosts (disable validation): Works today but is a security anti-pattern — bearer tokens would be sent to any host the request is redirected to or misconfigured against.

  4. Monkey-patching is_url_host_valid: This is our current workaround. It works, but it's fragile, invisible to other kiota consumers, and could break with internal refactors.

Additional context

This is not limited to Microsoft Fabric. Any service that uses tenant-specific or resource-specific subdomains has the same problem. Examples include:

  • Microsoft Fabric GraphQL: <guid>.z2c.graphql.fabric.microsoft.com
  • Azure API Management: <service-name>.azure-api.net
  • Power BI Embedded: <cluster>.pbidedicated.windows.net

The AllowedHostsValidator is a security boundary — it controls where bearer tokens are sent. Supporting suffix matching preserves that security role (tokens are still restricted to a known domain) while accommodating the reality of modern cloud APIs with dynamic hostnames.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Status
    Needs Triage 🔍

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions