fix: keep external origin wildcards inside the URI authority - #8274
Conversation
There was a problem hiding this comment.
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
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.
Code Coverage OverviewLanguages: C# C# / code-coverage/dotnetThe overall line coverage in commit 89dadbd in the Show a line coverage summary of the most covered files.
Updated |
ea17e96 to
b89aac2
Compare
There was a problem hiding this comment.
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
Resolved since last review (2)
|
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>
37a380c to
89dadbd
Compare
|
Conflicts have been resolved. A maintainer will take a look shortly. |


Problem
The external reference allowlist expands every
*in an entry to.*and matches the resulting expression against the whole URI string:.*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 "anycontoso.comsubdomain", 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:*becomes[^/@:?#]*, so it cannot consume the delimiters that end the authority;*stays.*, exactly as before.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 ashttps://[fe80::1]/schemas/*remains escaped text rather than a wildcard.Behavior
*https://contoso.com/schemas/*file:///C:/schemas/*C:\schemas\*,schemas/*https://*.contoso.com/*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.*.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 examplehttps://*.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
AllowedExternalOriginsStreamLoaderRejectsAuthorityBypassasserts that a wildcard cannot cross the scheme, user information, host and port boundaries; several of its cases fail without the fix.AllowedExternalOriginsStreamLoaderAllowsMatchingOriginscovers subdomains at several depths, wildcard hosts, explicit ports, query wildcards and exact user information, and a new test covers wildcardfile://entries.The full
Kiota.Builder.Testssuite 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.