[Feature] Add AllowedDomains URL allowlisting for WebView, HybridWebView, and BlazorWebView - #35470
[Feature] Add AllowedDomains URL allowlisting for WebView, HybridWebView, and BlazorWebView#35470mattleibow wants to merge 19 commits into
Conversation
… 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>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35470Or
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>
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>
|
/azp run maui-pr-devicetests |
|
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>
|
/review -b feature/regression-check -p android |
|
/review -b feature/refactor-copilot-yml |
This comment has been minimized.
This comment has been minimized.
AI code review for net11.0 targetVerdict: 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
Non-blocking findings
Confidence / CIConfidence: 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 |
kubaflo
left a comment
There was a problem hiding this comment.
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)
- ❌ Sub-resource bypass (Android BlazorWebView + Windows). The allowlist is enforced for top-level navigation (
ShouldOverrideUrlLoading/NavigationStarting) but not in the resource-request paths (ShouldInterceptRequeston Android,WebResourceRequested/HandleWebResourceRequeston 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) - ❌ Tizen is a security no-op. The API is exposed in the Tizen PublicAPI but no Tizen handler enforces it. Setting
AllowedDomainson Tizen silently restricts nothing. (gpt-5.5 + gemini + verified) ⚠️ javascript:/data:/blob:/about:unconditionally allowed even with a non-empty allowlist — a real script/content-execution bypass vector. (gemini + verified)⚠️ iOS/MacCatalyst relies solely on app-configuredWKAppBoundDomains(plist validation + warnings) with no code-levelIsUrlAllowednav 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 TFMs —
WebView.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. IWebViewis now: IView, IAllowedDomainsWebView— adding a requiredAllowedDomainsmember to the publicIWebViewis a binary/source breaking change for any externalIWebViewimplementer (gpt-5.5). IfIWebViewis considered non-externally-implementable this may be acceptable — confirm.- IDN/punycode:
Uri.Hostreturns 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)) |
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
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) || |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.)
… 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>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: Insufficient data - click to expandOverall verdict: Insufficient data — 15 checks are failing, but every one of them was uninspectable (the backing AzDO builds
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 actionA human with AzDO |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
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.
🗂️ 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.cssrc/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cssrc/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cssrc/BlazorWebView/src/Maui/BlazorWebView.cssrc/BlazorWebView/src/Maui/PublicAPI/net-android/PublicAPI.Unshipped.txtsrc/BlazorWebView/src/Maui/PublicAPI/net-ios/PublicAPI.Unshipped.txtsrc/BlazorWebView/src/Maui/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txtsrc/BlazorWebView/src/Maui/PublicAPI/net-tizen/PublicAPI.Unshipped.txtsrc/BlazorWebView/src/Maui/PublicAPI/net-windows/PublicAPI.Unshipped.txtsrc/BlazorWebView/src/Maui/PublicAPI/net/PublicAPI.Unshipped.txtsrc/BlazorWebView/src/Maui/Tizen/BlazorWebViewHandler.Tizen.cssrc/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cssrc/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cssrc/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cssrc/BlazorWebView/src/SharedSource/WebView2WebViewManager.cssrc/Controls/src/Core/HybridWebView/HybridWebView.cssrc/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txtsrc/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txtsrc/Controls/src/Core/WebView/WebView.cssrc/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cssrc/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cssrc/Core/src/Handlers/WebView/WebViewHandler.Android.cssrc/Core/src/Handlers/WebView/WebViewHandler.Tizen.cssrc/Core/src/Handlers/WebView/WebViewHandler.Windows.cssrc/Core/src/Handlers/WebView/WebViewHandler.cssrc/Core/src/Platform/Android/MauiHybridWebViewClient.cssrc/Core/src/Platform/Android/MauiWebViewClient.cssrc/Core/src/Platform/iOS/MauiWebViewNavigationDelegate.cssrc/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/net/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txtsrc/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
New files (not reverted):
src/Core/src/Core/IAllowedDomainsWebView.cssrc/Core/src/Handlers/WebViewDomainAllowlist.cssrc/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
AllowedDomainsallowlisting 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;
ghCLI 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:150only blocks navigation; standard iOS/MacCatalyst WebView construction does not enableLimitsNavigationsToAppBoundDomainsfor sub-resources. - ✗
src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs:471can leave Windows subresource filtering disabled after in-place mutation of a mutable allowlist. - ✗
src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs:45readsAllowedDomainsonly at WKWebView creation; runtime changes do not update iOS Hybrid navigation policy. - ⚠
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35470.cs:51verifies 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
AllowedDomainsis non-empty, but matcher is on shared webview request paths. - Startup impact: Medium; iOS/Blazor/Hybrid configure
WKWebViewat creation time. - Static/shared state: Yes, shared matcher; iOS
MauiWKWebViewshared configuration path remains unchanged.
CI Status
- Required-check result:
gh pr checks --requiredunavailable becauseghis 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-analysisskill 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
AllowedDomainsafter 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
left a comment
There was a problem hiding this comment.
Expert Review — 4 findings
See inline comments for details.
| #if WINDOWS | ||
| [nameof(IView.FlowDirection)] = MapFlowDirection, | ||
| [nameof(IView.Background)] = MapBackground, | ||
| [nameof(IAllowedDomainsWebView.AllowedDomains)] = MapAllowedDomains, |
There was a problem hiding this comment.
🔍 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); |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Public API Surface / Cross-Platform Consistency — AllowedDomains 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)) |
There was a problem hiding this comment.
🔍 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)) |
There was a problem hiding this comment.
🔍 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.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions and resolve conflicts?
|
Closing to revist later. |
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
AllowedDomainsproperty (IList<string>?) toWebView,HybridWebView, andBlazorWebViewthat restricts navigation and sub-resource loading to only the listed domains (and their subdomains).When
AllowedDomainsisnullor 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 return403.Opt-in, non-breaking API
AllowedDomainsis defined by a new optional interfaceIAllowedDomainsWebView, implemented byWebView,HybridWebViewandBlazorWebView. It is intentionally not a base interface ofIWebView/IHybridWebView/IBlazorWebView, so existing implementers of those public interfaces are not source/binary broken. Handlers read the allowlist by pattern-matchingIAllowedDomainsWebView.Platform enforcement
ShouldOverrideUrlLoadingShouldInterceptRequest(403)WKNavigationDelegate.DecidePolicyWKAppBoundDomainsin Info.plistNavigationStarting+FrameNavigationStartingWebResourceRequested(403, wired only when an allowlist is set)NavigationPolicyDecidedSub-resource blocking now also covers the standard
WebViewon Windows, andBlazorWebViewon Android and Windows (these previously enforced navigation only).BlazorWebViewon iOS additionally gets a code-level navigation check in addition toWKAppBoundDomains.Domain matching
.-suffix subdomain match), so the classic bypasses (allowed.com.evil.com,evil.com/?x=allowed.com, userinfohttps://allowed.com@evil.com) are rejected.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://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.plistunderWKAppBoundDomainsand enableLimitsNavigationsToAppBoundDomains = true. The implementation validates consistency betweenAllowedDomainsand the plist at runtime and logs warnings for mismatches.New types
IAllowedDomainsWebView— optional interface defining theAllowedDomainscontractWebViewDomainAllowlist— shared utility with theIsUrlAllowed()domain-matching logicWebViewPlistValidator(iOS/MacCatalyst) — validatesWKAppBoundDomainsplist entriesTests
WebViewDomainAllowlistTests(Core unit tests) — matcher logic, including the newjavascript:and IDN/punycode casesWebViewAllowedDomainsTests(Controls unit + device tests) — property behavior and navigation blockingIssue35470HostApp + UI test — end-to-end navigation allow/block and runtime removal of the allowlist