Skip to content

.Net: Python: allowed_base_urls path prefix can be escaped with dot segments (.. / %2e) — validator and HTTP client disagree on the path #14662

Open

Description

@adeeluddin01

Summary

When allowed_base_urls contains a non-root path prefix, a URL whose path only looks inside that prefix passes validation, but the HTTP client normalizes the path before sending it, so the server receives a request outside the prefix. This is a two-parser differential: the validator checks the raw path string, httpx checks the normalized path.

Reproduction (validator level, no server needed)

from semantic_kernel.connectors.openapi_plugin.server_url_validator import (
    ServerUrlValidationOptions,
    validate_server_url,
)

options = ServerUrlValidationOptions(
    allowed_base_urls=["https://api.example.com/api"]
)

# Currently returns [] (allowed). The request httpx actually sends is
# https://api.example.com/admin/delete-all.
await validate_server_url(
    "https://api.example.com/api/../admin/delete-all", options
)

The same holds for /api/v1/../../admin against an /api/v1 prefix, and for percent-encoded dot segments such as /api/%2e%2e/admin (see scope note below).

Root cause

python/semantic_kernel/connectors/openapi_plugin/server_url_validator.py:

  • _matches_path_prefix is a plain case-insensitive string-prefix check on url.path exactly as urllib.parse.urlparse returns it. urlparse performs no dot-segment removal, so /api/../admin starts with /api/ and passes.
  • On a match, validate_server_url returns [] immediately, skipping every other check.
  • openapi_runner.py validates, then sends the same URL string via httpx (unpinned in the allowlist case). httpx normalizes dot segments before transmission (RFC 3986 section 5.2.4): /api/../secret becomes /secret, /api/v1/../../admin becomes /admin (verified against a current httpx release).

Impact and scope — stated plainly

This is a same-host path-scope escape, not a private-network SSRF and not CVE-class. It requires all three of:

  1. an allowlist configured with a non-root path prefix (the feature exists to fence an agent to endpoints on a host),
  2. attacker influence over the operation URL path, and
  3. a sensitive endpoint outside the prefix on the same host.

Proposed fix

Normalize the path once, in _parse_absolute_url, so every consumer — including the allowlist matcher, which parses both the request URL and each configured base URL through that function — compares canonical paths:

  • RFC 3986 section 5.2.4 dot-segment removal; .. above the root is clamped at the root; empty segments (//) are preserved, matching httpx.
  • Percent-encoded dot segments (%2e, any case) are decoded before resolving. httpx sends those literally, but a downstream server may decode before routing, so the validator deliberately fails closed on ambiguous encodings. Other percent-encodings are left untouched, and only ./.. segments are special.
  • Backslash separators are out of scope: httpx does not normalize \ to / in paths (verified), so no change is needed there.

The patch is minimal: one new _normalize_path function and one changed return line. No change to the runner, the pinning logic, or the scheme/host/port checks. I have a prepared patch and tests covering the escape cases, the encoded variants, and the legitimate cases that must keep passing (/api/./users, /api/users, /api, /apiusers still rejected), and I can open a PR from it if maintainers agree with the approach.

Note for maintainers (not part of this change)

The .NET SDK has a parallel validator with the same raw-prefix shape (ServerUrlValidator.cs / PublicNetworkAddressValidator.cs). This proposal covers the Python SDK only; the .NET side is flagged here for a follow-up by someone working in that codebase.

Activity

  1. added theissue type on Oct 26, 2026
  2. changed the title [-]Python: allowed_base_urls path prefix can be escaped with dot segments (.. / %2e) — validator and HTTP client disagree on the path[/-] [+].Net: Python: allowed_base_urls path prefix can be escaped with dot segments (.. / %2e) — validator and HTTP client disagree on the path[/+] on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETIssue or Pull requests regarding .NET codebugSomething isn't workingpythonPull requests for the Python Semantic Kerneltriage

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions