Skip to content

fix: keep external origin wildcards inside the URI authority - #8274

Merged
Jingjing Jia (jingjingjia-ms) merged 3 commits into
mainfrom
fix/bound-external-origin-wildcards
Sep 25, 2026
Merged

Jingjing Jia (jingjingjia-ms) merged 3 commits into
mainfrom
fix/bound-external-origin-wildcards

Conversation

@jingjingjia-ms

@jingjingjia-ms Jingjing Jia (jingjingjia-ms) commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The external reference allowlist expands every * in an entry to .* and matches the resulting expression against the whole URI string:

Regex.IsMatch(candidate, $"^{Regex.Escape(pattern).Replace("\\*", ".*")}$", ...)

.* matches any character, including the delimiters that separate the URI components. A wildcard written in the scheme or the host therefore does not stop at the end of the authority: it can keep consuming characters and complete the match using text from a later part of the URI.

The practical effect is that an entry such as https://*.contoso.com/*, which reads as "any contoso.com subdomain", does not restrict the request to that host. The literal text following the wildcard can be satisfied by another URI component, so hosts outside the intended set can pass the check. The same applies to wildcards that cross the user information and port delimiters.

Fix

Only the part of a pattern before the path determines which host a request reaches. Once the path starts, the destination is already pinned, so a wildcard there cannot redirect the request.

The pattern is split at the first / after ://, and the two halves expand their wildcards differently:

  • before the path, * becomes [^/@:?#]*, so it cannot consume the delimiters that end the authority;
  • from the path onwards, * stays .*, exactly as before.
https://*.contoso.com/*   ->   ^https://[^/@:?#]*\.contoso\.com/.*$

This keeps the existing model: a single anchored whole-string glob. The allowlist entry is still never parsed as a URI, so there is no new handling of ports, IDNs, IPv6 literals or percent-encoding, and a literal : in an entry such as https://[fe80::1]/schemas/* remains escaped text rather than a wildcard.

Behavior

Entry Matching Changed
* allows every external reference no
https://contoso.com/schemas/* wildcard is in the path no
file:///C:/schemas/* authority is empty, wildcard is in the path no
C:\schemas\*, schemas/* filesystem branch, untouched no
https://*.contoso.com/* wildcard bounded to the host yes

Only entries with a wildcard in the scheme or the authority change, which is exactly the affected set. Filesystem patterns are matched by a separate branch that is left as is, so they keep matching across directory separators on every platform.

Two consequences are worth calling out:

  • https://*.contoso.com/* no longer matches a URI that carries user information, because the wildcard cannot cross @. An entry that intends to allow user information has to spell it out.
  • An entry without a scheme, such as *.contoso.com, now matches nothing and the reference is rejected. It never worked as a host pattern, because matching is performed against the whole URI string rather than against the host alone. URI patterns need a scheme, for example https://*.contoso.com/*.

A follow-up could warn at startup when an entry contains * but no scheme, so this is reported clearly instead of surfacing as a denied reference.

Tests

AllowedExternalOriginsStreamLoaderRejectsAuthorityBypass asserts that a wildcard cannot cross the scheme, user information, host and port boundaries; several of its cases fail without the fix. AllowedExternalOriginsStreamLoaderAllowsMatchingOrigins covers subdomains at several depths, wildcard hosts, explicit ports, query wildcards and exact user information, and a new test covers wildcard file:// entries.

The full Kiota.Builder.Tests suite passes (2409 passed, 2 skipped).

Supersedes #8266, which parsed the entry into URI components and matched them individually. This achieves the same result with a much smaller change.

Copilot AI lite review requested due to automatic review settings September 24, 2026 18:30
@msgraph-bot msgraph-bot Bot added this to Kiota Sep 24, 2026

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 review overview

🟡 Changes recommended

Address the unresolved query/fragment boundary and backslash authority bypass findings.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Fixes external-origin URI wildcard matching so authority wildcards cannot cross URI boundaries.

Changes:

  • Bounds scheme and authority wildcards.
  • Adds bypass and compatibility regression tests.
  • Documents the behavior change.

Review findings:

  • Critical (1 vote): Backslashes are not excluded from authority wildcards, allowing a raw-URI bypass.
  • Moderate (4 votes): Query or fragment patterns without / are treated as authority, rejecting valid values containing /.
File Summary
tests/​Kiota.Builder.Tests/​OpenApiDocumentDownloadServiceTests.cs Adds security, matching, and file-URI regression coverage.
src/​Kiota.Builder/​AllowedExternalOriginsStreamLoader.cs Implements bounded URI wildcard matching; requires fixes for query/fragment boundaries and backslash handling.
CHANGELOG.md Documents the allowlist behavior change.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Kiota.Builder/AllowedExternalOriginsStreamLoader.cs Outdated
Comment thread src/Kiota.Builder/AllowedExternalOriginsStreamLoader.cs Outdated
@github-code-quality

github-code-quality Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: C#

C# / code-coverage/dotnet

The overall line coverage in commit 89dadbd in the fix/bound-external-o... branch is 32%. Line coverage data for the main branch is not yet available.

Show a line coverage summary of the most covered files.
File main fix/bound-external-o... 89dadbd +/-
/home/runner/wo...guageRefiner.cs — 98% —
/home/runner/wo...criptRefiner.cs — 98% —
/home/runner/wo...MethodWriter.cs — 98% —
/home/runner/wo...MethodWriter.cs — 97% —
/home/runner/wo...rs/GoRefiner.cs — 97% —
/home/runner/wo...MethodWriter.cs — 97% —
/home/runner/wo...MethodWriter.cs — 95% —
/home/runner/wo...KiotaBuilder.cs — 93% —
/home/runner/wo...ationService.cs — 89% —
/home/runner/wo...MethodWriter.cs — 89% —

Updated September 25, 2026 16:52 UTC

Copilot AI review requested due to automatic review settings September 24, 2026 18:39
@jingjingjia-ms
Jingjing Jia (jingjingjia-ms) force-pushed the fix/bound-external-origin-wildcards branch from ea17e96 to b89aac2 Compare September 24, 2026 18:39

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 review overview

🔵 Needs a closer look

Unresolved moderate findings affect query/fragment handling, wildcard schemes, and backslash validation.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)

Copilot AI review requested due to automatic review settings September 24, 2026 18:46

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 review overview

🟡 Changes recommended

Critical case-sensitive separator handling can allow mixed- or uppercase-scheme authority bypasses.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Comment thread src/Kiota.Builder/AllowedExternalOriginsStreamLoader.cs
Copilot AI review requested due to automatic review settings September 24, 2026 20:12

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 review overview

🟢 Approval recommended

The security fix is covered by regression tests and preserves intended matching behavior.

Review effort: Lite
Findings: None

Resolved since last review (1)

@jingjingjia-ms
Jingjing Jia (jingjingjia-ms) marked this pull request as ready for review September 24, 2026 20:40
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicting changes, the author must resolve the conflicts before this pull request can be merged.

The external reference allowlist expands every `*` in an entry to `.*` and
matches the resulting expression against the whole URI string. `.*` also
matches the delimiters that separate the URI components, so a wildcard placed
in the scheme or the host does not stop at the end of the authority: it keeps
consuming characters and can complete the match with text taken from a later
part of the URI. An entry such as `https://*.contoso.com/*` therefore does not
restrict the request to that host, and the same applies to wildcards that cross
the user information and port delimiters.

A wildcard placed before the path is now expanded to a bounded character class
that cannot consume `/`, `@`, `:`, `?` or `#`, so it stays within the component
it was written in. Wildcards from the path onwards keep matching any character
because the destination is already pinned by then, which leaves path, query and
fragment patterns, `file://` patterns, filesystem path patterns and the
standalone `*` entry unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
An entry may place a query or a fragment directly after the authority, without
a path. The authority boundary was located by looking for the first `/` after
`://`, so such a suffix was treated as part of the authority and its wildcard
was bounded, rejecting values that contain `/`. The boundary now ends at the
first `/`, `?` or `#`, which keeps the unbounded expansion for everything that
follows the authority.

Backslash is also excluded from the authority wildcard. The URI parser refuses
a backslash in that position today, so this is defence in depth rather than a
reachable bypass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The scheme separator contains no letters, so locating it is unaffected by the
case of the scheme and the authority is delimited identically. Lock that in
with an entry written as `HTTPS://`, both for a candidate it should allow and
for one it should reject.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Conflicts have been resolved. A maintainer will take a look shortly.

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 review overview

🟢 Approval recommended

No unresolved review issues were identified.

Review effort: Lite
Findings: None

@jingjingjia-ms
Jingjing Jia (jingjingjia-ms) added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 5e7fead Sep 25, 2026
321 checks passed
@jingjingjia-ms
Jingjing Jia (jingjingjia-ms) deleted the fix/bound-external-origin-wildcards branch September 25, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done ✔️

Development

Successfully merging this pull request may close these issues.

3 participants