Skip to content

[Feature] Add AllowedDomains URL allowlisting for WebView, HybridWebView, and BlazorWebView - #35470

Closed
mattleibow wants to merge 19 commits into
net11.0from
mattleibow/feature-webview-url-allowlist
Closed

[Feature] Add AllowedDomains URL allowlisting for WebView, HybridWebView, and BlazorWebView#35470
mattleibow wants to merge 19 commits into
net11.0from
mattleibow/feature-webview-url-allowlist

Conversation

@mattleibow

@mattleibow mattleibow commented May 15, 2026

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

Adds an AllowedDomains property (IList<string>?) to WebView, HybridWebView, and BlazorWebView that restricts navigation and sub-resource loading to only the listed domains (and their subdomains).

When AllowedDomains is null or empty, all URLs are allowed (backward-compatible default). When set, only URLs whose host matches one of the listed domains are permitted — blocked navigations are cancelled and blocked sub-resources return 403.

Opt-in, non-breaking API

AllowedDomains is defined by a new optional interface IAllowedDomainsWebView, implemented by WebView, HybridWebView and BlazorWebView. It is intentionally not a base interface of IWebView/IHybridWebView/IBlazorWebView, so existing implementers of those public interfaces are not source/binary broken. Handlers read the allowlist by pattern-matching IAllowedDomainsWebView.

Platform enforcement

Platform Navigation blocking Sub-resource blocking
Android ShouldOverrideUrlLoading ShouldInterceptRequest (403)
iOS/MacCatalyst WKNavigationDelegate.DecidePolicy WKAppBoundDomains in Info.plist
Windows NavigationStarting + FrameNavigationStarting WebResourceRequested (403, wired only when an allowlist is set)
Tizen NavigationPolicyDecided navigation-level (Blazor also guards the HTTP interceptor)

Sub-resource blocking now also covers the standard WebView on Windows, and BlazorWebView on Android and Windows (these previously enforced navigation only). BlazorWebView on iOS additionally gets a code-level navigation check in addition to WKAppBoundDomains.

Domain matching

  • The host is parsed from the URL and compared against each allowlist entry (exact match or .-suffix subdomain match), so the classic bypasses (allowed.com.evil.com, evil.com/?x=allowed.com, userinfo https://allowed.com@evil.com) are rejected.
  • Comparison uses the ASCII/punycode host (Uri.IdnHost + IdnMapping), so internationalized domain names match whether the allowlist entry or the URL is expressed in Unicode or punycode.

Special schemes (with an active allowlist)

  • javascript: is blocked (arbitrary script execution / allowlist-bypass vector).
  • data:, about:, blob: remain allowed (origin-less, page-controlled content; blocking them would break legitimate rendering without adding a security boundary).
  • App-internal origins (app://0.0.0.0/ for HybridWebView, https://0.0.0.1/ for BlazorWebView, etc.) are always allowed.

iOS/MacCatalyst sub-resource note

Apple's WKWebView cannot intercept http/https sub-resource requests at the code level. For sub-resource blocking, apps must declare up to 10 domains in Info.plist under WKAppBoundDomains and enable LimitsNavigationsToAppBoundDomains = true. The implementation validates consistency between AllowedDomains and the plist at runtime and logs warnings for mismatches.

New types

  • IAllowedDomainsWebView — optional interface defining the AllowedDomains contract
  • WebViewDomainAllowlist — shared utility with the IsUrlAllowed() domain-matching logic
  • WebViewPlistValidator (iOS/MacCatalyst) — validates WKAppBoundDomains plist entries

Tests

  • WebViewDomainAllowlistTests (Core unit tests) — matcher logic, including the new javascript: and IDN/punycode cases
  • WebViewAllowedDomainsTests (Controls unit + device tests) — property behavior and navigation blocking
  • Issue35470 HostApp + UI test — end-to-end navigation allow/block and runtime removal of the allowlist
… and BlazorWebView

Adds an AllowedDomains property (IList<string>?) to all three webview controls that
restricts navigation to only the listed domains (and their subdomains). When null or
empty, all URLs are allowed (backward compatible).

Enforcement per platform:
- Android: ShouldOverrideUrlLoading + ShouldInterceptRequest (sub-resource blocking)
- iOS/MacCatalyst: WKNavigationDelegate DecidePolicy + WKAppBoundDomains/LimitsNavigationsToAppBoundDomains for sub-resource blocking
- Windows: NavigationStarting + WebResourceRequested events

New files:
- IAllowedDomainsWebView interface
- WebViewDomainAllowlist shared utility (domain matching logic)
- WebViewPlistValidator (iOS/MacCatalyst Info.plist validation)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow
mattleibow changed the base branch from main to net11.0 May 15, 2026 20:07
@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35470

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35470"
Unit tests (38 tests total):
- WebViewDomainAllowlistTests: 29 tests covering IsUrlAllowed() logic
  (exact match, subdomain, case insensitive, special schemes, app origin,
  null/empty, port, query, fragment, blocked domains, partial match)
- WebViewAllowedDomainsTests: 9 tests for BindableProperty on WebView
  and HybridWebView (default null, set/reset, PropertyChanged, property exists)

Device tests:
- WebViewAllowedDomainsTests: 3 tests for platform handler enforcement
  (allowed navigation succeeds, blocked navigation fails, null allows all)

UI tests:
- Issue35470 HostApp page: WebView with AllowedDomains set, buttons to
  test allowed/blocked navigation and runtime removal
- Issue35470 NUnit test: 3 Appium tests for end-to-end verification

Also fixes WebViewStub to implement IAllowedDomainsWebView.AllowedDomains.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow and others added 2 commits May 17, 2026 22:49
Fixes CS0103 build error for TimeSpan in WebViewAllowedDomainsTests.cs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TreatWarningsAsErrors promotes CA1307 to error. Use explicit
StringComparison.Ordinal for string.Contains calls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow
mattleibow marked this pull request as ready for review May 20, 2026 14:55
@mattleibow

Copy link
Copy Markdown
Member Author

/azp run maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).
- Windows: Add FrameNavigationStarting handler to block iframe
  navigations to disallowed domains (WebView + BlazorWebView)
- Android: Add ShouldInterceptRequest to MauiWebViewClient to block
  sub-resource requests (images, scripts, iframes) to disallowed domains
- iOS/macCat: Already handled — DecidePolicy fires for all frame types

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

kubaflo commented May 21, 2026

Copy link
Copy Markdown
Collaborator

/review -b feature/regression-check -p android

@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Collaborator

/review -b feature/refactor-copilot-yml

@MauiBot

This comment has been minimized.

@kubaflo

kubaflo commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

AI code review for net11.0 target

Verdict: Needs changes

Automated, non-approval review comment (no human approval implied). I inspected the diff, the new public surface, and the core allowlist logic independently before reading the PR narrative.

Blocking

  • All build legs are failing on the PR head (77f5a6e): Build Windows (Debug/Release), Build macOS (Debug/Release) and Pack macOS all fail, while the same legs pass on sibling net11.0 PRs. This is a PR-caused compile/build break (not the shared integration-test flakiness) and must be green before this can be considered. Most likely culprits given the change shape: a PublicAPI.Unshipped.txt mismatch across the 8 touched API files, or the new interfaces (IAllowedDomainsWebView, IWebView/IHybridWebView additions) not being satisfied on every TFM/stub. Please reproduce locally and resolve.

Non-blocking findings

  • Nullability inconsistency in public API: WebView.AllowedDomains is declared non-nullable (IList<string>, listed with ~ oblivious markers) while HybridWebView/BlazorWebView.AllowedDomains are IList<string>?. Align these for a consistent public contract.
  • WebViewDomainAllowlist.IsUrlAllowed always allows javascript:/data:/blob:/about: schemes. For a navigation allowlist this is defensible, but javascript: in particular is a common injection vector; worth a comment confirming this is intentional and that sub-resource enforcement (iOS WKAppBoundDomains) is the real boundary.
  • Subdomain matching (host == domain || host.EndsWith("." + domain)) looks correct and avoids the evilexample.com suffix-trap. Good.

Confidence / CI

Confidence: medium-high on the build-break being PR-caused (all build legs red, isolated to this PR); medium on root cause without log access. Build logs for build 1428352 were not retrievable through tooling.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PR #35470 — Add AllowedDomains URL allowlisting for WebView / HybridWebView / BlazorWebView

Verdict: NEEDS_CHANGES (confidence: high) — HEAD 77f5a6e. Unanimous across all 4 models (gpt-5.5, opus-4.8, opus-4.6, gemini). This is a valuable security feature and the core domain matcher is sound — opus-4.8 and I both verified WebViewDomainAllowlist.IsUrlAllowed matches on the parsed Uri.Host with exact + .+domain suffix checks, so the classic bypasses (allowed.com.evil.com, evil.com/?x=allowed.com, userinfo https://allowed.com@evil.com) are correctly handled. The problems are about where the matcher is (and isn't) applied, plus cross-platform parity.

Blocking (inline)

  1. ❌ Sub-resource bypass (Android BlazorWebView + Windows). The allowlist is enforced for top-level navigation (ShouldOverrideUrlLoading / NavigationStarting) but not in the resource-request paths (ShouldInterceptRequest on Android, WebResourceRequested/HandleWebResourceRequest on Windows) — so an allowed page can still load scripts/images/XHR from disallowed hosts. This contradicts the PR description's "sub-resource blocking" table. (all 4 models + code-verified)
  2. ❌ Tizen is a security no-op. The API is exposed in the Tizen PublicAPI but no Tizen handler enforces it. Setting AllowedDomains on Tizen silently restricts nothing. (gpt-5.5 + gemini + verified)
  3. ⚠️ javascript:/data:/blob:/about: unconditionally allowed even with a non-empty allowlist — a real script/content-execution bypass vector. (gemini + verified)
  4. ⚠️ iOS/MacCatalyst relies solely on app-configured WKAppBoundDomains (plist validation + warnings) with no code-level IsUrlAllowed nav check for BlazorWebView/HybridWebView, unlike the Controls WebView iOS path and unlike Android/Windows. Inconsistent guarantee. (gemini + opus-4.8)

Also worth addressing (not inline, to keep the review focused)

  • Public API surface inconsistency across TFMsWebView.AllowedDomains / the interface entries are declared inconsistently between net / netstandard / net-android / net-windows / net-tizen PublicAPI files (opus-4.8). Reconcile so every TFM matches.
  • IWebView is now : IView, IAllowedDomainsWebView — adding a required AllowedDomains member to the public IWebView is a binary/source breaking change for any external IWebView implementer (gpt-5.5). If IWebView is considered non-externally-implementable this may be acceptable — confirm.
  • IDN/punycode: Uri.Host returns Unicode for IDN; ensure allowlist entries and hosts are compared in the same form (gemini suggestion).

CI

Red legs are the usual unrelated infra flakes; the new WebViewAllowedDomainsTests (Core.UnitTests + DeviceTests) are a good start — please extend them to cover the sub-resource and Tizen gaps above.

Great direction and a solid matcher — the fixes are about closing the sub-resource/Tizen/iOS enforcement gaps so the guarantee is consistent across platforms. Happy to re-review.

return false;
}

if (!Microsoft.Maui.Handlers.WebViewDomainAllowlist.IsUrlAllowed(uri.ToString(), _webViewHandler.VirtualView?.AllowedDomains, AppOriginUri))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sub-resource allowlist bypass (BlazorWebView Android — and the same pattern on Windows). The AllowedDomains check is applied here in ShouldOverrideUrlLoadingCore (top-level navigation), but the companion ShouldInterceptRequest (lines 85-118 in this file) — which is the path Android WebView uses for images/scripts/CSS/XHR/fetch — does not call WebViewDomainAllowlist.IsUrlAllowed; it only checks app interception + Blazor resources, then return base.ShouldInterceptRequest(...). So a page on an allowed domain can still load sub-resources from a disallowed host (e.g. https://evil.com/x.js). This contradicts the PR description's table, which lists Android sub-resource blocking as enforced via ShouldInterceptRequest. The identical gap exists on Windows: WebView2WebViewManager registers WebResourceRequested for all contexts (AddWebResourceRequestedFilter("*", ...All)) but HandleWebResourceRequest (line 337) serves Blazor content without an IsUrlAllowed check, and the standard Windows WebViewHandler only hooks NavigationStarting/FrameNavigationStarting, never WebResourceRequested. Please enforce the allowlist in the resource-request paths (return an empty/blocked response for disallowed hosts) on both platforms, or correct the PR's sub-resource claims. (All 4 models flagged this; the core IsUrlAllowed matcher itself is sound — this is about applying it at the sub-resource entry points.)

@@ -1,2 +1,4 @@
#nullable enable
static Microsoft.Maui.GridLength.implicit operator Microsoft.Maui.GridLength(string! value) -> Microsoft.Maui.GridLength
Microsoft.Maui.IAllowedDomainsWebView

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AllowedDomains is a security no-op on Tizen. The new IAllowedDomainsWebView / AllowedDomains surface is added to the Tizen PublicAPI here (and in the Controls + BlazorWebView net-tizen baselines), but no Tizen handler (*.Tizen.cs for WebView, HybridWebView, or BlazorWebView) references AllowedDomains, WebViewDomainAllowlist, or IsUrlAllowed — confirmed via git grep at this head. So an app that sets AllowedDomains on Tizen gets a silent false sense of security: navigations and sub-resources are not restricted at all, unlike Android/iOS/Windows which at least block top-level navigation. Either implement enforcement in the Tizen handlers or don't expose the API on Tizen (and document the platform support matrix accordingly). (gpt-5.5 + gemini consensus + verified.)


// Always allow special schemes
var scheme = uri.Scheme;
if (string.Equals(scheme, "data", StringComparison.OrdinalIgnoreCase) ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

data:, about:, javascript:, and blob: are unconditionally allowed even when an allowlist is set. When AllowedDomains is non-empty, the intent is to restrict what the web view can load — but these schemes return true before any host check (lines 33-39). javascript: and data:text/html URLs can execute script / render arbitrary content in the page's origin, and blob:/data: can exfiltrate or load attacker-controlled payloads, so unconditionally allowing them is a real bypass vector for a feature whose purpose is content restriction. Consider at minimum gating javascript: and data: (these are the script/content-execution vectors), or document explicitly that these schemes are intentionally exempt. (gemini flagged; verified.)

config.LimitsNavigationsToAppBoundDomains = true;
}

Microsoft.Maui.Handlers.WebViewPlistValidator.ValidateAllowedDomainsAgainstPlist(allowedDomains, Logger);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

iOS/MacCatalyst enforcement relies entirely on the app correctly configuring WKAppBoundDomains, with no code-level fallback. Here the iOS BlazorWebView only validates the plist and logs warnings (WebViewPlistValidator.ValidateAllowedDomainsAgainstPlist) — there's no programmatic IsUrlAllowed navigation check in the WKNavigationDelegate, unlike the Controls WebView iOS path (MauiWebViewNavigationDelegate) which does perform a code-level check. The iOS HybridWebView handler has the same gap (no code-level nav check). The result is an inconsistent security guarantee: if the app doesn't (or can't — WKAppBoundDomains is capped at 10 entries) configure the plist, iOS BlazorWebView/HybridWebView silently enforce nothing, while Android/Windows still block top-level navigation programmatically. Consider adding a code-level decidePolicyForNavigationAction allowlist check on iOS for parity. (gemini + opus-4.8.)

Copilot AI added 2 commits June 30, 2026 22:58
… gaps

- Make IAllowedDomainsWebView an optional interface implemented by WebView,
  HybridWebView and BlazorWebView, instead of a base of IWebView/IHybridWebView/
  IBlazorWebView. This avoids a source/binary breaking change for existing
  IWebView implementers. Handlers read the allowlist via a pattern-matching
  IsUrlAllowed(string, IView, Uri) overload.
- Align WebView.AllowedDomains to IList<string>? and reconcile the AllowedDomains
  PublicAPI entries so they are consistent across all TFMs.
- Block javascript: navigations once an allowlist is active (XSS/bypass vector);
  data:, about: and blob: remain allowed as page-controlled content.
- Match domains using the ASCII/punycode host (Uri.IdnHost + IdnMapping) so IDN
  allowlist entries and URLs compare correctly.
- Close the sub-resource bypass: standard WebView on Windows (WebResourceRequested,
  enabled only when an allowlist is set), Blazor on Android (ShouldInterceptRequest)
  and Blazor on Windows (WinUIWebViewManager).
- Enforce AllowedDomains on Tizen via NavigationPolicyDecided for the standard
  WebView and BlazorWebView (previously a no-op).
- Add a code-level navigation check for Blazor on iOS in addition to the existing
  WKAppBoundDomains enforcement.
- Make the device-test WebView stub implement IAllowedDomainsWebView and extend the
  matcher unit tests for the javascript: and IDN behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 30, 2026 21:33
@kubaflo

This comment has been minimized.

@github-actions

This comment has been minimized.

@kubaflo

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

@mattleibow — test-failure review results are available based on commit ac9f1a3.
To request a fresh review after new comments, commits, or CI runs, comment /review tests.

Overall Insufficient data Failures 0 Regressed vs base 0 Baseline 0 on base

Test Failure Review: Insufficient data - click to expand

Overall verdict: Insufficient data — 15 checks are failing, but every one of them was uninspectable (the backing AzDO builds 1493722/1493723/1493724 all returned 404 unauthenticated), so zero distinct failures could be extracted and none could be attributed to the PR or dismissed as pre-existing. No net11.0 base builds were available to compare against (0 base builds sampled), so no baseline evidence exists either way. This is a data-collection gap, not a clean run — a human with AzDO access must confirm.

  • i Uncertain — inaccessible AzDO build/UI-test legs (~14 checks): the maui-pr, maui-pr-uitests, and maui-pr-devicetests builds and legs (e.g. maui-pr-uitests (iOS UITests CoreCLR Controls (vlatest) CollectionView)) returned 404 unauthenticated, so no logs or test names could be read.
  • i Uncertain — device-test checks unverified (~4 legs): green device-test legs such as maui-pr-devicetests (net11.0 CoreCLR Android Helix Tests Run DeviceTests Android (CoreCLR)) could not be confirmed Failed == 0 (XHarness exits 0 even on failures).
  • i Uncertain — aborted MacCatalyst legs (~2 legs): maui-pr-uitests (MacCatalyst UITests CoreCLR Controls Shell) and ... Controls CollectionView were CANCELLED, so their status cannot be trusted green.

Coverage: 138 checks · 123 passing · 15 failing · 0 pending · 14 inaccessible · 1 unmapped · 0 unexplained build legs · 0 unaccounted failing checks · 2 aborted failing checks · 0 canceled-build checks · 4 device-test unverified · 0 unattributed · 0 regressed-vs-base. Deterministic ceiling: Insufficient data — 14 failing check(s) could not be inspected (AzDO build/logs inaccessible).

Builds (this PR): maui-pr 1493722, maui-pr-uitests 1493723, maui-pr-devicetests 1493724. Base sampling (net11.0, 0 recent builds per definition): none available.

Recommended action

A human with AzDO dnceng-public access should open the failing maui-pr/maui-pr-uitests/maui-pr-devicetests builds directly to determine whether the failures are PR-caused; automated inspection was blocked by 404s and no baseline was available.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 18, 2026
@MauiBot MauiBot added the s/agent-changes-requested AI agent recommends changes - found a better alternative or issues label Jul 18, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@mattleibow — new AI review results are available based on this last commit: ac9f1a3. To request a fresh review after new comments or commits, comment /review rerun.

Gate Inconclusive Confidence Low Platform iOS


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ INCONCLUSIVE

Platform: IOS · Base: net11.0 · Merge base: 7f139ed4

🩺 Base branch does not compile — the without-fix build failed. The gate's "does the test fail without the fix" check is unreliable here; this usually means main is broken or a merge-base file went missing. Note: this PR ADDS 3 new file(s), which the gate removes to reconstruct the pre-fix baseline; if the PR's own (never-reverted) test files reference types defined in those new files, the baseline cannot compile — that reflects a new-feature PR the gate cannot isolate a "before" state for, not necessarily a broken main. The with-fix result below is the reliable signal. Investigate before trusting this gate.

/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(12,24): error CS1061: 'WebView' does not contain a definition for 'AllowedDomains' and no accessible exte...

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 WebViewAllowedDomainsTests WebViewAllowedDomainsTests 🛠️ BUILD ERROR ✅ PASS — 21s
🧪 WebViewDomainAllowlistTests WebViewDomainAllowlistTests 🛠️ BUILD ERROR ✅ PASS — 7s
📱 WebViewAllowedDomainsTests (AllowedDomainsAllowsPermittedNavigation, AllowedDomainsBlocksDisallowedNavigation, NullAllowedDomainsAllowsAllNavigation) Category=WebView 🛠️ BUILD ERROR ✅ PASS — 86s
🖥️ Issue35470 Issue35470 🛠️ BUILD ERROR ✅ PASS — 247s
🔴 Without fix — 🧪 WebViewAllowedDomainsTests: 🛠️ BUILD ERROR · 39s

Error-relevant lines (filtered from the build log):

/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(51,27): error CS0117: 'WebView' does not contain a definition for 'AllowedDomainsProperty' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(52,32): error CS0117: 'WebView' does not contain a definition for 'AllowedDomains' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(52,57): error CS0117: 'WebView' does not contain a definition for 'AllowedDomainsProperty' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(59,30): error CS1061: 'HybridWebView' does not contain a definition for 'AllowedDomains' and no accessible extension method 'AllowedDomains' accepting a first argument of type 'HybridWebView' could be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(67,18): error CS1061: 'HybridWebView' does not contain a definition for 'AllowedDomains' and no accessible extension method 'AllowedDomains' accepting a first argument of type 'HybridWebView' could be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(68,40): error CS1061: 'HybridWebView' does not contain a definition for 'AllowedDomains' and no accessible extension method 'AllowedDomains' accepting a first argument of type 'HybridWebView' could be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(78,51): error CS0117: 'HybridWebView' does not contain a definition for 'AllowedDomains' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(82,18): error CS1061: 'HybridWebView' does not contain a definition for 'AllowedDomains' and no accessible extension method 'AllowedDomains' accepting a first argument of type 'HybridWebView' could be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(89,33): error CS0117: 'HybridWebView' does not contain a definition for 'AllowedDomainsProperty' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(90,38): error CS0117: 'HybridWebView' does not contain a definition for 'AllowedDomains' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(90,69): error CS0117: 'HybridWebView' does not contain a definition for 'AllowedDomainsProperty' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 WebViewAllowedDomainsTests: PASS ✅ · 21s

(no coded error found; showing last 1200 chars)

n for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net11.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v11.0)
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 11.0.0-preview.6.26325.125)
[xUnit.net 00:00:00.09]   Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:00.71]   Discovered:  Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:00.72]   Starting:    Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:00.75]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed AllowedDomainsDefaultIsNull [4 ms]
  Passed HybridWebViewAllowedDomainsCanBeSet [3 ms]
  Passed AllowedDomainsBindablePropertyExists [< 1 ms]
  Passed HybridWebViewAllowedDomainsBindablePropertyExists [< 1 ms]
  Passed HybridWebViewAllowedDomainsDefaultIsNull [< 1 ms]
  Passed AllowedDomainsCanBeSet [< 1 ms]
  Passed AllowedDomainsPropertyChangedFires [< 1 ms]
  Passed AllowedDomainsCanBeSetToNull [< 1 ms]
  Passed HybridWebViewAllowedDomainsPropertyChangedFires [< 1 ms]

Test Run Successful.
Total tests: 9
     Passed: 9
 Total time: 0.9997 Seconds

🔴 Without fix — 🧪 WebViewDomainAllowlistTests: 🛠️ BUILD ERROR · 7s

Error-relevant lines (filtered from the build log):

/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(221,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(229,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(237,17): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(244,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(251,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(258,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(265,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(273,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(275,17): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(276,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(288,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(289,17): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(317,6): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(318,6): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
🟢 With fix — 🧪 WebViewDomainAllowlistTests: PASS ✅ · 7s

(no coded error found; showing last 1200 chars)

< 1 ms]
  Passed HttpSchemeIsChecked [< 1 ms]
  Passed EmptyAllowedDomainsAllowsAll [< 1 ms]
  Passed EmptyDomainEntryIsSkipped [< 1 ms]
  Passed WhitespacePaddedDomainEntryMatches [< 1 ms]
  Passed ExactDomainMatchIsAllowed [< 1 ms]
  Passed WhitespaceOnlyDomainEntryIsSkipped [< 1 ms]
  Passed FtpSchemeIsChecked [< 1 ms]
  Passed UncFileUriIsSubjectToAllowlist [< 1 ms]
  Passed ConcurrentMutationDoesNotThrow [122 ms]
  Passed DeepSubdomainMatchIsAllowed [< 1 ms]
  Passed IdnSubdomainMatchIsAllowed [< 1 ms]
  Passed IdnDifferentDomainIsBlocked [< 1 ms]
  Passed NullAllowedDomainsAllowsAll [< 1 ms]
  Passed EmptyUrlIsBlocked [< 1 ms]
  Passed JavaScriptSchemeIsAllowedWhenNoAllowlist [< 1 ms]
  Passed WhitespaceUrlIsBlocked [< 1 ms]
  Passed RelativeUrlIsBlocked [< 1 ms]
[xUnit.net 00:00:00.32]   Finished:    Microsoft.Maui.UnitTests
  Passed SubdomainMatchIsAllowed [< 1 ms]
  Passed NullAppOriginDoesNotCrash [< 1 ms]
  Passed UrlWithQueryStringIsChecked [< 1 ms]
  Passed IdnPunycodeAllowlistMatchesUnicodeUrl [< 1 ms]
  Passed AppOriginIsAlwaysAllowed [< 1 ms]
  Passed NullDomainEntryIsSkipped [< 1 ms]

Test Run Successful.
Total tests: 42
     Passed: 42
 Total time: 0.5626 Seconds

🔴 Without fix — 📱 WebViewAllowedDomainsTests (AllowedDomainsAllowsPermittedNavigation, AllowedDomainsBlocksDisallowedNavigation, NullAllowedDomainsAllowsAllNavigation): 🛠️ BUILD ERROR · 121s

Error-relevant lines (filtered from the build log):

/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Elements/WebView/WebViewAllowedDomainsTests.cs(36,5): error CS0117: 'WebView' does not contain a definition for 'AllowedDomains' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj::TargetFramework=net11.0-ios]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Elements/WebView/WebViewAllowedDomainsTests.cs(63,5): error CS0117: 'WebView' does not contain a definition for 'AllowedDomains' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj::TargetFramework=net11.0-ios]
Build FAILED.
🟢 With fix — 📱 WebViewAllowedDomainsTests (AllowedDomainsAllowsPermittedNavigation, AllowedDomainsBlocksDisallowedNavigation, NullAllowedDomainsAllowsAllNavigation): PASS ✅ · 86s

Error-relevant lines (filtered from the build log):

�[40m�[37mdbug�[39m�[22m�[49m: [08:47:48.4639510] 2026-07-18 08:47:48.463781-0700 Microsoft.Maui.Controls.DeviceTests[7472:53984] [ResourceLoadStatistics] Failed to request known fingerprinting scripts from WebPrivacy: Error Domain=WebPrivacyErrorDomain Code=1001 "List data not found" UserInfo={NSLocalizedDescription=List data not found}
🔴 Without fix — 🖥️ Issue35470: 🛠️ BUILD ERROR · 49s

Error-relevant lines (filtered from the build log):

/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Issues/Issue35470.cs(39,4): error CS0117: 'WebView' does not contain a definition for 'AllowedDomains' [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net11.0-ios]
/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Issues/Issue35470.cs(93,12): error CS1061: 'WebView' does not contain a definition for 'AllowedDomains' and no accessible extension method 'AllowedDomains' accepting a first argument of type 'WebView' could be found (are you missing a using directive or an assembly reference?) [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net11.0-ios]
Build FAILED.
🟢 With fix — 🖥️ Issue35470: PASS ✅ · 247s

(no coded error found; showing last 1200 chars)

st execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net11.0/Controls.TestCases.iOS.Tests.dll
   NUnit3TestExecutor discovered 3 of 3 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 7/18/2026 8:50:40 AM FixtureSetup for Issue35470(iOS)
>>>>> 7/18/2026 8:50:45 AM AllowedDomainNavigationSucceeds Start
>>>>> 7/18/2026 8:51:16 AM AllowedDomainNavigationSucceeds Stop
  Passed AllowedDomainNavigationSucceeds [30 s]
>>>>> 7/18/2026 8:51:16 AM BlockedDomainNavigationIsBlocked Start
>>>>> 7/18/2026 8:51:50 AM BlockedDomainNavigationIsBlocked Stop
  Passed BlockedDomainNavigationIsBlocked [34 s]
>>>>> 7/18/2026 8:51:50 AM RemovingAllowedDomainsAllowsAll Start
>>>>> 7/18/2026 8:51:51 AM RemovingAllowedDomainsAllowsAll Stop
  Passed RemovingAllowedDomainsAllowsAll [748 ms]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35470.trx

Test Run Successful.
Total tests: 3
     Passed: 3
 Total time: 2.1388 Minutes
>>> TRX_RESULT_FILE: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35470.trx

⚠️ Failure Details

  • 🛠️ WebViewAllowedDomainsTests without fix: build failed before tests could run
    • /Users/cloudtest/vss/_work/1/s/src/Controls/tests/Core.UnitTests/WebViewAllowedDomainsTests.cs(12,24): error CS1061: 'WebView' does not contain a definition for 'AllowedDomains' and no accessible exte...
  • 🛠️ WebViewDomainAllowlistTests without fix: build failed before tests could run
    • /Users/cloudtest/vss/_work/1/s/src/Core/tests/UnitTests/Handlers/WebViewDomainAllowlistTests.cs(15,16): error CS0103: The name 'WebViewDomainAllowlist' does not exist in the current context [/Users/cl...
  • 🛠️ WebViewAllowedDomainsTests (AllowedDomainsAllowsPermittedNavigation, AllowedDomainsBlocksDisallowedNavigation, NullAllowedDomainsAllowsAllNavigation) without fix: build failed before tests could run
    • /Users/cloudtest/vss/_work/1/s/src/Controls/tests/DeviceTests/Elements/WebView/WebViewAllowedDomainsTests.cs(36,5): error CS0117: 'WebView' does not contain a definition for 'AllowedDomains' [/Users/c...
  • 🛠️ Issue35470 without fix: build failed before tests could run
    • /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Issues/Issue35470.cs(39,4): error CS0117: 'WebView' does not contain a definition for 'AllowedDomains' [/Users/cloudtest/vss/_work/1...
📁 Fix files reverted (41 files)
  • src/BlazorWebView/src/Maui/Android/BlazorWebChromeClient.cs
  • src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs
  • src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs
  • src/BlazorWebView/src/Maui/BlazorWebView.cs
  • src/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
  • src/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txt
  • src/BlazorWebView/src/Maui/Tizen/BlazorWebViewHandler.Tizen.cs
  • src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs
  • src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs
  • src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs
  • src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs
  • src/Controls/src/Core/HybridWebView/HybridWebView.cs
  • src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/WebView/WebView.cs
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs
  • src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
  • src/Core/src/Handlers/WebView/WebViewHandler.Tizen.cs
  • src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs
  • src/Core/src/Handlers/WebView/WebViewHandler.cs
  • src/Core/src/Platform/Android/MauiHybridWebViewClient.cs
  • src/Core/src/Platform/Android/MauiWebViewClient.cs
  • src/Core/src/Platform/iOS/MauiWebViewNavigationDelegate.cs
  • src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt

New files (not reverted):

  • src/Core/src/Core/IAllowedDomainsWebView.cs
  • src/Core/src/Handlers/WebViewDomainAllowlist.cs
  • src/Core/src/Handlers/WebViewPlistValidator.ios.cs

📱 UI Tests — ViewBaseTests,WebView

Detected UI test categories: ViewBaseTests,WebView

Deep UI tests — 161 passed, 0 failed across 2 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
ViewBaseTests 112/112 ✓
WebView 49/50 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

📋 Pre-Flight — Context & Validation

Issue: #35470 - PR is feature work; no separate linked issue found from accessible context
PR: #35470 - [Feature] Add AllowedDomains URL allowlisting for WebView, HybridWebView, and BlazorWebView
Platforms Affected: Android, iOS, MacCatalyst, Windows, Tizen; requested test platform: iOS
Files Changed: 42 implementation/API, 8 test

Key Findings

  • Adds opt-in AllowedDomains allowlisting for WebView, HybridWebView, and BlazorWebView with shared matcher logic and platform-specific navigation/resource enforcement.
  • Public GitHub context was accessible through unauthenticated API/web fetch; gh CLI operations were unavailable due missing authentication.
  • Gate was provided as inconclusive by caller and was not rerun.
  • Prior review discussion shows multiple earlier findings were addressed, but code review still found iOS standard WebView app-bound configuration and mutable-list/runtime changes as unresolved design risks.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: low
Errors: 3 | Warnings: 1 | Suggestions: 0

Key code review findings:

  • src/Core/src/Platform/iOS/MauiWebViewNavigationDelegate.cs:150 only blocks navigation; standard iOS/MacCatalyst WebView construction does not enable LimitsNavigationsToAppBoundDomains for sub-resources.
  • src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs:471 can leave Windows subresource filtering disabled after in-place mutation of a mutable allowlist.
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs:45 reads AllowedDomains only at WKWebView creation; runtime changes do not update iOS Hybrid navigation policy.
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35470.cs:51 verifies the button label, not actual navigation after removing the allowlist.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35470 Mutable IList<string>? API, shared matcher snapshots, platform navigation/resource checks, iOS plist validation/configuration for Hybrid/Blazor ⚠ INCONCLUSIVE (Gate) 50 files Original PR; gate result supplied by caller

🔬 Code Review — Deep Analysis

Code Review — PR #35470

Independent Assessment

What this changes: Adds AllowedDomains allowlisting to WebView, HybridWebView, and BlazorWebView, with shared domain matching and platform-specific navigation/resource blocking.

Inferred motivation: Provide opt-in web content containment so apps can restrict navigations and subresources to trusted domains.

Reconciliation with PR Narrative

Author claims: Allowlisted domains/subdomains are permitted; non-allowed navigations/subresources are blocked across Android, iOS/MacCatalyst, Windows, and Tizen, with iOS app-bound domain caveats.

Agreement/disagreement: The matcher is generally sound and several bypasses were addressed. However, iOS/MacCatalyst standard WebView, iOS HybridWebView runtime changes, and Windows mutable-list behavior still leave enforcement gaps.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
Android Blazor / Windows subresource bypass kubaflo/MauiBot ✅ Mostly fixed WebKitWebViewClient.cs:100, WinUIWebViewManager.cs:82 now check resources.
Tizen no-op kubaflo ✅ Fixed / narrowed WebViewHandler.Tizen.cs:41, BlazorWebViewHandler.Tizen.cs:141.
iOS standard WebView lacks subresource enforcement MauiBot ❌ Unresolved MauiWebViewNavigationDelegate.cs:150 is navigation-only; MauiWKWebView.CreateConfiguration() does not set app-bound domains.
Android mutable-list/threading issue MauiBot ✅ Mostly fixed WebViewDomainAllowlist.cs:79 snapshots before iteration.
_blank external-launch bypass MauiBot ✅ Fixed BlazorWebChromeClient.cs:34, WebView2WebViewManager.cs:416.
Windows mutable IList subscription gap MauiBot ❌ Unresolved WebViewHandler.Windows.cs:471 only updates filter when mapper runs.
Hostless file:// always allowed MauiBot ❌ Unresolved WebViewDomainAllowlist.cs:66 allows all hostless file URLs.

Blast Radius Assessment

  • Runs for all instances: No, only active when AllowedDomains is non-empty, but matcher is on shared webview request paths.
  • Startup impact: Medium; iOS/Blazor/Hybrid configure WKWebView at creation time.
  • Static/shared state: Yes, shared matcher; iOS MauiWKWebView shared configuration path remains unchanged.

CI Status

  • Required-check result: gh pr checks --required unavailable because gh is unauthenticated.
  • Fallback: public check-runs for PR head show failures in maui-pr, maui-pr-devicetests, maui-pr-uitests, and Build Analysis.
  • Classification: undetermined.
  • Action taken: invoked azdo-build-investigator; ci-analysis skill unavailable; confidence capped low.

Findings

❌ Error — iOS/MacCatalyst standard WebView still allows disallowed subresources

src/Core/src/Platform/iOS/MauiWebViewNavigationDelegate.cs:150 only blocks navigation actions. Standard WebView still uses MauiWKWebView.CreateConfiguration() without LimitsNavigationsToAppBoundDomains or plist validation, so an allowed page can load disallowed scripts/images/fetches on iOS/MacCatalyst despite the API/PR claiming resource restriction.

❌ Error — Windows subresource filtering can remain disabled after in-place allowlist mutation

src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs:471 subscribes WebResourceRequested only when mapper/init observes a non-empty list. Since the API exposes mutable IList<string>, AllowedDomains = new List<string>(); AllowedDomains.Add("example.com") does not rerun the mapper. Top-level navigation reads the live list, but subresources are not intercepted.

❌ Error — iOS HybridWebView runtime changes are not enforced

src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs:45 reads AllowedDomains only during WKWebView creation. There is no mapper or navigation delegate to enforce later changes, contradicting IAllowedDomainsWebView docs that runtime changes still update navigation-level checks.

⚠️ Warning — UI test for removing allowlist does not verify navigation behavior

src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35470.cs:51 only checks a label set by the button handler. It would pass even if AllowedDomains = null had no platform effect.

Failure-Mode Probing

  • Empty list then mutate later on Windows: top-level navigation blocks, subresources still load because filter was never subscribed.
  • iOS standard WebView loads allowed page with external script: navigation delegate is not called for that script request; it can load.
  • HybridWebView set AllowedDomains after handler creation on iOS: no mapper/delegate observes the change.

Verdict: NEEDS_CHANGES

Confidence: low, due red/undetermined CI and platform-handler blast radius.

Summary: The feature is valuable and the shared matcher is mostly sound, but current code still has concrete enforcement gaps. CI is also red/undetermined, so this is not ready to merge.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix Change AllowedDomains public contract from mutable IList<string>? to IReadOnlyList<string>? while API is unshipped ✅ PASS — WebViewDomainAllowlist unit tests 42/42 28 files Strong API-shape alternative for mutable-list ambiguity; larger public API churn
2 try-fix Add iOS HybridWebView WKNavigationDelegate runtime allowlist check ⚠ BLOCKED — iOS device-test runner hung/no output 1 file Directly addresses Hybrid runtime navigation gap; needs iOS device/CI validation
3 try-fix Fail closed for data:/blob: under active allowlist ✅ PASS — WebViewDomainAllowlist unit tests 43/43 2 files Security-stricter but self-review flagged compatibility risk for inline/generated content
4 try-fix Enable LimitsNavigationsToAppBoundDomains and plist validation for standard iOS/MacCatalyst WebView creation ✅ PASS — Core.csproj built for net11.0-ios 1 file Best candidate: narrow iOS fix for standard WebView sub-resource enforcement gap
PR PR #35470 Mutable list API + shared matcher + platform checks + iOS plist validation/configuration for Hybrid/Blazor ⚠ INCONCLUSIVE (Gate supplied by caller) 50 files Original PR

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 2 Yes Suggested enabling/validating app-bound domains for standard iOS/MacCatalyst WebViewHandler; implemented as Candidate 4.
orchestrator 3 No After Candidate 4, remaining materially different approaches are either PR-compatible documentation-only, broad breaking policy changes, or require iOS device validation unavailable here.

Exhausted: Yes
Selected Fix: Candidate #4 — It directly targets the unresolved iOS standard WebView sub-resource enforcement gap, is smaller than Candidate #1, avoids Candidate #3's compatibility break, and passed a targeted net11.0-ios Core build. Candidate #2 remains plausible but unverified due iOS runner blockage.

Notes

  • Gate verification was not rerun per caller instruction.
  • All candidate source changes were restored after artifact capture; the output files under CustomAgentLogsTmp/PRState/35470/PRAgent/ are the persistent result.

📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the winning pr-plus-reviewer candidate changes the API from IList<string>? to IReadOnlyList<string>? and clarifies Tizen's navigation-only limitation, so the current description is now partially stale.

Recommended title

[All] WebView: Add AllowedDomains URL allowlisting

Recommended description

## Description

Adds an `AllowedDomains` property (`IReadOnlyList<string>?`) to `WebView`, `HybridWebView`, and `BlazorWebView` that restricts navigation and supported sub-resource loading to only the listed domains (and their subdomains).

When `AllowedDomains` is `null` or empty, all URLs are allowed (backward-compatible default). When set, only URLs whose host matches one of the listed domains are permitted — blocked navigations are cancelled and blocked sub-resources return `403` on platforms with sub-resource interception support.

### Opt-in, non-breaking API

`AllowedDomains` is defined by a new **optional** interface `IAllowedDomainsWebView`, implemented by `WebView`, `HybridWebView` and `BlazorWebView`. It is intentionally **not** a base interface of `IWebView`/`IHybridWebView`/`IBlazorWebView`, so existing implementers of those public interfaces are not source/binary broken. Handlers read the allowlist by pattern-matching `IAllowedDomainsWebView`.

The property type is `IReadOnlyList<string>?` so apps replace the allowlist value instead of mutating it in place; this keeps bindable-property notifications and platform handler subscription state consistent.

### Platform enforcement

| Platform | Navigation blocking | Sub-resource blocking |
|----------|--------------------|-----------------------|
| **Android** | `ShouldOverrideUrlLoading` | `ShouldInterceptRequest` (403) |
| **iOS/MacCatalyst** | `WKNavigationDelegate.DecidePolicy` | `WKAppBoundDomains` in Info.plist with `LimitsNavigationsToAppBoundDomains` |
| **Windows** | `NavigationStarting` + `FrameNavigationStarting` | `WebResourceRequested` (403, wired only when an allowlist is set) |
| **Tizen** | `NavigationPolicyDecided` | Not supported; navigation-level enforcement only |

Sub-resource blocking now also covers the standard `WebView` on **Windows** and **iOS/MacCatalyst**, and `BlazorWebView` on **Android** and **Windows** (these previously enforced navigation only). `BlazorWebView` on iOS additionally gets a code-level navigation check in addition to `WKAppBoundDomains`.

### Domain matching

- The host is parsed from the URL and compared against each allowlist entry (exact match or `.`-suffix subdomain match), so the classic bypasses (`allowed.com.evil.com`, `evil.com/?x=allowed.com`, userinfo `https://allowed.com@evil.com`) are rejected.
- Comparison uses the **ASCII/punycode** host (`Uri.IdnHost` + `IdnMapping`), so internationalized domain names match whether the allowlist entry or the URL is expressed in Unicode or punycode.

### Special schemes (with an active allowlist)

- `javascript:` is **blocked** (arbitrary script execution / allowlist-bypass vector).
- `data:`, `about:`, `blob:` remain allowed (origin-less, page-controlled content; blocking them would break legitimate rendering without adding a security boundary).
- App-internal origins (`app://0.0.0.0/` for HybridWebView, `https://0.0.0.1/` for BlazorWebView, etc.) are always allowed.

### iOS/MacCatalyst sub-resource note

Apple's WKWebView cannot intercept http/https sub-resource requests at the code level. For sub-resource blocking, apps must declare up to 10 domains in `Info.plist` under `WKAppBoundDomains` and enable `LimitsNavigationsToAppBoundDomains = true`. The implementation validates consistency between `AllowedDomains` and the plist at runtime and logs warnings for mismatches.

Because `LimitsNavigationsToAppBoundDomains` is a `WKWebViewConfiguration` setting, set `AllowedDomains` before the handler creates the underlying `WKWebView`. Runtime changes still update navigation-level checks, but cannot recreate the OS-level app-bound configuration for an existing WKWebView.

### New types

- `IAllowedDomainsWebView` — optional interface defining the `AllowedDomains` contract
- `WebViewDomainAllowlist` — shared utility with the `IsUrlAllowed()` domain-matching logic
- `WebViewPlistValidator` (iOS/MacCatalyst) — validates `WKAppBoundDomains` plist entries

### Tests

- `WebViewDomainAllowlistTests` (Core unit tests) — matcher logic, including `javascript:` and IDN/punycode cases
- `WebViewAllowedDomainsTests` (Controls unit + device tests) — property behavior and WebView navigation blocking
- `Issue35470` HostApp + UI test — end-to-end WebView navigation allow/block and runtime allowlist clearing

### Issues Fixed

Fixes #35470

🏁 Report — Final Recommendation

Comparative Report — PR #35470

Candidates compared

Rank Candidate Regression/Test result Assessment
1 pr-plus-reviewer PASS — sandbox WebViewDomainAllowlist unit tests 42/42 and net11.0-ios Core build Best overall. It preserves the PR's broad feature implementation while adding the expert-review fixes for standard iOS/MacCatalyst WebView app-bound-domain subresource enforcement, read-only allowlist API shape, and Tizen documentation.
2 try-fix-4 PASS — net11.0-ios Core build Strong narrow fix for the iOS standard WebView gap, but by itself does not address the mutable API/Windows subscription issue or Tizen documentation.
3 try-fix-1 PASS — WebViewDomainAllowlist unit tests 42/42 Good API-shape correction that avoids in-place mutation ambiguity, but by itself does not close the standard iOS/MacCatalyst WebView subresource gap.
4 pr Gate INCONCLUSIVE; with-fix targeted checks in gate passed where they ran, but expert review found unresolved issues Broadest submitted implementation, but lower than passing reviewer-improved candidates because it leaves standard iOS/MacCatalyst subresources unprotected and exposes mutable-list behavior that can desynchronize Windows filtering.
5 try-fix-3 PASS — WebViewDomainAllowlist unit tests 43/43 Security-stricter scheme policy, but it changes the documented compatibility stance for data:/blob: and risks breaking legitimate inline/generated content without solving the main iOS standard WebView gap.
6 try-fix-2 BLOCKED — iOS runner hung/no output Plausible iOS Hybrid runtime navigation improvement, but unverified and narrower than the candidates that passed targeted validation.

No candidate had a regression-test failure. Per the ranking rule, blocked/inconclusive candidates are ranked below candidates with passing targeted validation when their technical coverage is otherwise comparable.

Winner

pr-plus-reviewer is the winning candidate.

It combines the raw PR's cross-platform allowlisting implementation with the strongest expert-review and Step 5a improvements: try-fix-4's standard iOS/MacCatalyst WebView app-bound-domain configuration, try-fix-1's read-only API surface, and a documentation correction for Tizen's navigation-only enforcement. It still should add HybridWebView/BlazorWebView interceptor coverage before merge, but among the available candidates it has the best correctness/security posture and passed the targeted validation available in this environment.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 4 findings

See inline comments for details.

#if WINDOWS
[nameof(IView.FlowDirection)] = MapFlowDirection,
[nameof(IView.Background)] = MapBackground,
[nameof(IAllowedDomainsWebView.AllowedDomains)] = MapAllowedDomains,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[critical] Cross-Platform Behavioral Consistency / Security Enforcement Gap — The AllowedDomains mapper entry (and the corresponding iOS/MacCatalyst app-bound-domain configuration) is only wired up for standard Microsoft.Maui.Controls.WebView on Windows (#if WINDOWS). On iOS/MacCatalyst, WebViewHandler.iOS.cs (CreatePlatformView() => new MauiWKWebView(this), left unchanged by this PR) never sets WKWebViewConfiguration.LimitsNavigationsToAppBoundDomains or calls WebViewPlistValidator.ValidateAllowedDomainsAgainstPlist, unlike HybridWebViewHandler.iOS.cs and BlazorWebViewHandler.iOS.cs, which both do this in the config-setup code added by this same PR. Result: setting WebView.AllowedDomains on iOS/MacCatalyst blocks top-level/iframe navigations (via the shared MauiWebViewNavigationDelegate), but provides no protection at all against sub-resource loads (images, scripts, CSS, XHR/fetch) to disallowed domains — the exact guarantee IAllowedDomainsWebView's XML doc promises ("for sub-resource blocking ... the domains must also be declared in Info.plist under WKAppBoundDomains"). Android (MauiWebViewClient.ShouldInterceptRequest) and Windows (WebResourceRequested) both correctly enforce sub-resource blocking for the standard WebView; iOS/MacCatalyst is the sole gap for this control, and it is silent (no warning, no exception) — a developer relying on this for security would not know sub-resources are unprotected. This was previously diagnosed with a concrete fix (reworking WebViewHandler.iOS.cs.CreatePlatformView() to build a WKWebViewConfiguration up front and apply LimitsNavigationsToAppBoundDomains + plist validation, matching the Hybrid/Blazor pattern), but that fix was not carried into this squashed changeset.


/// <summary>Bindable property for <see cref="AllowedDomains"/>.</summary>
#nullable enable
public static readonly BindableProperty AllowedDomainsProperty = BindableProperty.Create(nameof(AllowedDomains), typeof(IList<string>), typeof(WebView), null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Public API Surface / Cross-Platform ConsistencyAllowedDomains is typed as mutable IList<string>? (same on HybridWebView/BlazorWebView). Because it's exposed via a BindableProperty, in-place mutation of the list instance (e.g. webView.AllowedDomains.Add("x.com") or .Clear()) does not raise a property-changed notification. On Windows, WebViewHandler.Windows.cs's MapAllowedDomains (which subscribes/unsubscribes WebResourceRequested for sub-resource filtering) only re-evaluates on a BindableProperty change, so mutating the list contents in place silently fails to (un)subscribe the WebResourceRequested-based sub-resource filter on Windows — even though the same mutation is picked up immediately by Android/iOS/Tizen navigation-level checks, which read the live list directly on every request. This produces a real, silent, platform-specific behavioral inconsistency for the same API usage pattern.

if (maker is null)
return;

if (WebViewDomainAllowlist.IsUrlAllowed(maker.Url, VirtualView))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[moderate] Cross-Platform Behavioral Consistency — On Tizen, AllowedDomains is only enforced at the navigation level (OnNavigationPolicyDecided); there is no sub-resource interception equivalent to Android's ShouldInterceptRequest or Windows' WebResourceRequested, so scripts/images/XHR/fetch to disallowed domains are not blocked on Tizen even though they are on Android/Windows. This platform gap isn't documented anywhere — IAllowedDomainsWebView's XML doc only calls out the iOS/MacCatalyst WKAppBoundDomains sub-resource limitation, giving the impression Tizen (and, per the iOS finding above, iOS itself for the standard WebView) fully enforces sub-resource blocking when it does not.

if (string.IsNullOrEmpty(url))
return base.ShouldOverrideUrlLoading(view, request);

if (!Microsoft.Maui.Handlers.WebViewDomainAllowlist.IsUrlAllowed(url, Handler?.VirtualView, HybridWebViewHandler.AppOriginUri))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Regression Prevention / Test Coverage — This PR adds AllowedDomains navigation and sub-resource blocking to HybridWebView on Android/iOS/Windows and to BlazorWebView on Android/iOS/Windows/Tizen, but none of the new device/UI tests exercise these platform interceptors: WebViewAllowedDomainsTests.cs (device tests) and Issue35470.cs (UI test) only cover the plain Microsoft.Maui.Controls.WebView, and the unit tests only check bindable-property get/set for HybridWebView/BlazorWebView, never that a blocked/allowed URL is actually enforced by MauiHybridWebViewClient/WebKitWebViewClient/IOSWebViewManager/WinUIWebViewManager. HybridWebViewTests_Interception.cs already exists as a device-test pattern for HybridWebView request interception and would be a natural place to add an equivalent AllowedDomains regression test.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 18, 2026

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you please check the ai's suggestions and resolve conflicts?

@mattleibow
mattleibow marked this pull request as draft July 27, 2026 12:45
@mattleibow

Copy link
Copy Markdown
Member Author

Closing to revist later.

@mattleibow mattleibow closed this Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

5 participants