Skip to content

Route Android HybridWebView JS->.NET messages through HTTP intercept - #35850

Merged
kubaflo merged 5 commits into
inflight/currentfrom
harden-hybridwebview-android-bridge-origin
Jun 30, 2026
Merged

kubaflo merged 5 commits into
inflight/currentfrom
harden-hybridwebview-android-bridge-origin

Conversation

@jonathanpeppers

@jonathanpeppers jonathanpeppers commented Jun 10, 2026 •

Copy link
Copy Markdown
Member

HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement.

Background

The Android handler used WebView.AddJavascriptInterface("hybridWebViewHost") to expose HybridWebViewJavaScriptInterface.SendMessage to JavaScript. addJavascriptInterface injects the bridge into every frame in the WebView regardless of origin, and @JavascriptInterface does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it.

Fix

Route JS → .NET messages through the same HTTP intercept channel that __hwvInvokeDotNet already uses. The endpoint is gated by:

  • HybridWebViewHandler.HasExpectedHeaders (browser-set Origin / Referer), and
  • the X-Maui-Invoke-Token custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one validated entry point:

JS API Endpoint Validated by
HybridWebView.SendRawMessage(...) __hwvSendMessage TryValidateBridgeRequest
HybridWebView.InvokeDotNet(...) __hwvInvokeDotNet TryValidateBridgeRequest
__InvokeJavaScriptCompleted / __InvokeJavaScriptFailed callbacks __hwvSendMessage TryValidateBridgeRequest

window.hybridWebViewHost no longer exists.

Changes

  • Remove AddJavascriptInterface and the HybridWebViewJavaScriptInterface inner class.
  • Add __hwvSendMessage path handled in MauiHybridWebViewClient.
  • Extract TryValidateBridgeRequest helper shared with __hwvInvokeDotNet (same 400/405 gate).
  • HybridWebView.ts / HybridWebView.js: send via fetch() chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge.

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

HybridWebView generally expects all hosted code to be local app content,
so this is general hardening -- it's just for correctness, not security
enforcement.

The Android handler used WebView.AddJavascriptInterface("hybridWebViewHost")
to expose HybridWebViewJavaScriptInterface.SendMessage to JavaScript.
addJavascriptInterface injects the bridge into every frame in the WebView
regardless of origin, and @JavascriptInterface does not surface the calling
frame to the C# side, so any non-app-origin sub-frame loaded by the page
can reach it.

Route JS->.NET messages through the same HTTP intercept channel that
__hwvInvokeDotNet already uses. The endpoint is gated by HasExpectedHeaders
(browser-set Origin/Referer) and the X-Maui-Invoke-Token custom header
that forces a CORS preflight on cross-origin callers, which we never
satisfy.

- Remove AddJavascriptInterface and HybridWebViewJavaScriptInterface
- Add __hwvSendMessage path handled in MauiHybridWebViewClient
- Extract TryValidateBridgeRequest helper shared with __hwvInvokeDotNet
- HybridWebView.ts/.js: send via fetch() chained on a single promise to
  preserve FIFO ordering callers had with the synchronous bridge

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

github-actions Bot commented Jun 10, 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 -- 35850

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35850"

The TS lib used by the in-CI Microsoft.TypeScript.MSBuild target does
not expose Promise as a runtime value, so 'let sendQueue: Promise<unknown>
= Promise.resolve()' failed with TS2585. Initialize the queue lazily
and seed it from the first fetch() call instead -- same FIFO behavior,
no Promise constructor reference.

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

Copy link
Copy Markdown
Member Author

/azp run maui-pr-devicetests

@jonathanpeppers

Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jonathanpeppers

Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jonathanpeppers

Copy link
Copy Markdown
Member Author

/azp run maui-pr-devicetests

@jonathanpeppers
jonathanpeppers requested a review from Copilot June 12, 2026 16:36
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens Android HybridWebView by removing the AddJavascriptInterface JavaScript bridge and routing JS → .NET messages through the existing HTTP interception mechanism, so requests can be validated (origin/referer + token headers) before being handled.

Changes:

  • Add a dedicated __hwvSendMessage intercept endpoint on Android and share request validation logic with __hwvInvokeDotNet.
  • Remove the Android AddJavascriptInterface-based bridge (window.hybridWebViewHost) from the handler.
  • Update HybridWebView.ts / generated HybridWebView.js to send messages via fetch() with a FIFO promise chain.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Core/src/Platform/Android/MauiHybridWebViewClient.cs Adds __hwvSendMessage handling and extracts shared request validation for bridge endpoints.
src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs Introduces SendMessagePath constant for the new Android bridge endpoint.
src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs Removes AddJavascriptInterface bridge and relies on HTTP intercept for JS → .NET messaging.
src/Core/src/Handlers/HybridWebView/HybridWebView.ts Switches Android message sending to fetch() and introduces endpoint constants.
src/Core/src/Handlers/HybridWebView/HybridWebView.js Generated output reflecting the updated Android fetch()-based message sending.

…essages for Android header transport

- Remove dead 'window.hybridWebViewHost' interface declaration and the matching else-if branches in HostApp fixture HTMLs (the host JS interface was removed by this PR).

- URL-encode the raw message string in HybridWebView.ts/js sendRawMessage and decode it in HybridWebViewHelper.ProcessRawMessage. Android's fetch transport puts the body in the X-Maui-Request-Body header, which rejects CR/LF/NUL bytes; encoding only the __RawMessage payload (not the __hwvInvokeDotNet wire format) keeps the fix targeted and symmetric across platforms.

- Add SendRawMessageRoundTripsSpecialCharacters theory covering newline, CR/LF, NUL, '%', and non-ASCII / emoji to lock in the round-trip.

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

Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@jonathanpeppers

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).

1 similar comment
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added s/agent-ready-for-rerun AI review has a new PR-author comment or commit and is ready for rerun s/agent-review-in-progress AI review is currently running for this PR and removed s/agent-ready-for-rerun AI review has a new PR-author comment or commit and is ready for rerun labels Jun 29, 2026
@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 29, 2026
@kubaflo

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

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

Overall Needs human investigation Failures 14 Baseline 8 on base Platform android

Test Failure Review: Needs human investigation - click to expand

Overall verdict: Needs human investigation

Four failures are pre-existing PublicAPI errors (RS0016/RS0017/RS0025) confirmed on the base branch, and the Android SDK platform-tools;35.0.2 provisioning failure appears across all platforms with base legs also-red — but 53 unexplained build legs, 10 indeterminate failures, and 2 unverified green device-test checks prevent a cleaner verdict.

Coverage: 49 checks · 6 passing/neutral · 43 failing · 0 pending · 0 inaccessible · 1 unmapped · 53 unexplained build legs · 0 unaccounted failing checks · 0 aborted failing checks · 0 canceled-build checks · 2 device-test unverified · 10 unattributed · 0 regressed-vs-base. Deterministic ceiling: Needs human investigation — 1 failing check has no inspectable AzDO build evidence (Build Analysis); 53 failed build legs produced no extractable failure (Integration Tests, Helix, Build Microsoft.Maui.sln); 10 failures could not be attributed deterministically; 2 device-test checks read GREEN but Failed==0 could not be positively confirmed.

Failure Verdict On base? Evidence
to find package 'platform-tools;35.0.2' (macos, 2 occurrences) Needs human investigation also-red attribution=indeterminate; avdmanager exited with error; leg also-red on base but no exact test+platform base match; provisioning failures are filtered from regressed-vs-base
to find package 'platform-tools;35.0.2' (android, 10 occurrences) Needs human investigation also-red attribution=indeterminate; Android SDK provisioning warning across multiple legs; leg also-red on base; no exact test+platform base match
to find package 'platform-tools;35.0.2' (unknown platform, 7 occurrences) Needs human investigation also-red attribution=indeterminate; avdmanager error; leg also-red on base; no exact base match
to find package 'platform-tools;35.0.2' (windows) Needs human investigation also-red attribution=indeterminate; avdmanager error; leg also-red on base; no exact base match
PublishTestResults - build error (unknown, 2 instances) Needs human investigation also-red attribution=indeterminate; no test result files found; leg also-red on base; no exact base test match; likely downstream of provisioning failure
Build Controls.DeviceTests (Windows Unpackaged) - RS0016 (windows) Likely unrelated also-red attribution=pre-existing-on-base; RS0016 SwipeItemView.IsEnabledCore.get not in declared public API; exact same test+platform also fails on base build 1488039
DeviceTestsAndroid_CoreCLR (Windows) - build error (windows) Needs human investigation also-red attribution=indeterminate; PowerShell exited with code 1; leg also-red on base; no exact base test match
DeviceTestsAndroid (Windows) - build error (windows) Needs human investigation also-red attribution=indeterminate; PowerShell exited with code 1; leg also-red on base; no exact base test match
DeviceTestsIOS (Windows) - build error (windows) Needs human investigation also-red attribution=indeterminate; PowerShell exited with code 1; leg also-red on base; no exact base test match
Build the samples - RS0017 (android) Likely unrelated also-red attribution=pre-existing-on-base; RS0017 MauiAppCompatActivity.OnBackPressed removed from public API; exact same test+platform also fails on base
Build the samples - RS0025 (macos) Likely unrelated also-red attribution=pre-existing-on-base; RS0025 StructuredItemsViewController2.UpdateFlowDirection duplicated in PublicAPI.Unshipped.txt for net-maccatalyst; exact match on base
Build the samples - RS0025 (ios) Likely unrelated also-red attribution=pre-existing-on-base; RS0025 StructuredItemsViewController2.UpdateFlowDirection duplicated in PublicAPI.Unshipped.txt for net-ios; exact match on base
Build the samples - RS0016 (unknown platform) Needs human investigation also-red attribution=indeterminate; RS0016 SwipeItemView.IsEnabledCore.get on Windows; leg also-red on base; no exact test+platform base match (distinct from the Windows-specific pre-existing entry above)

Recommended action

A human should open the 53 unexplained build legs (Integration Tests, Helix, Build) and confirm whether they are pre-existing on the base branch. The 4 pre-existing PublicAPI failures (RS0016/RS0017/RS0025) and the Android SDK provisioning failures appear unrelated to this PR's HybridWebView Android bridge routing changes; if the unexplained legs and indeterminate failures are confirmed as pre-existing base-branch issues, the PR may be a merge candidate once those base issues are resolved.

Evidence details

PR builds: maui-pr 1483665 · maui-pr-devicetests 1483668 · maui-pr-uitests 1483667

Base builds used for comparison (all failed; base branch is actively broken):

  • maui-pr: 1488039 — failed, 9 baseline failures extracted (first 8 of 90 failed logs inspected; baseline list may be incomplete)
  • maui-pr-devicetests: 1488079 — failed, 2 baseline failures extracted (8 of 15 logs inspected)
  • maui-pr-uitests: 1488524 — failed, 10 baseline failures extracted (8 of 12 logs inspected)

Pre-existing failures confirmed on base (4 distinct test+platform entries):

  • RS0016 SwipeItemView.IsEnabledCore.get on Windows: exact test+platform match on base 1488039
  • RS0017 MauiAppCompatActivity.OnBackPressed on Android: exact test+platform match on base
  • RS0025 StructuredItemsViewController2.UpdateFlowDirection on macOS and iOS: exact match on base (duplicate PublicAPI.Unshipped.txt entry for maccatalyst and ios)

Android SDK provisioning (indeterminate, 4 platform groups): The Failed to find package 'platform-tools;35.0.2' error and avdmanager exit failures appear across macos (2), android (10), unknown (7), and windows (1) occurrences. Base legs are also-red. These are held at indeterminate because provisioning/infrastructure failures are nondeterministic and explicitly excluded from regressed-vs-base. Helix job aggregation returned 404, so Helix-based device-test results could not be confirmed.

Windows device-test build errors (indeterminate): DeviceTestsAndroid_CoreCLR, DeviceTestsAndroid, and DeviceTestsIOS on Windows all show PowerShell exited with code '1'. Base legs are also-red but no exact test+platform match was found on the base build.

Unexplained build legs (53): Legs including Run Integration Tests - RunOniOS_BlazorRelease, Run Integration Tests - Build, Run Integration Tests - Blazor, Run Integration Tests - AOT, Run Helix Tests, PublishTestResults, Build Microsoft.Maui.sln, DeviceTestsMacCatalyst (Windows), and others produced no extractable test failure name in their logs. Each must be opened individually before the verdict can improve.

Unverified device-test green checks: maui-pr-devicetests (net10.0 Android CoreCLR Helix Tests Build Device Tests (CoreCLR)) and maui-pr-devicetests (net10.0 ios/catalyst/android Helix Tests (Mono) Build Device Tests (Mono)) read GREEN, but XHarness exits 0 even on failure and no AzDO token was available to confirm Failed==0.

Unmapped check: Build Analysis links to the Arcade Build Analysis landing page with no inspectable AzDO build evidence; review it directly.

Scope note: This PR touches HybridWebView Android bridge routing (11 changed files, 6 test files, platform/android label). None of the extracted failures appear related to HybridWebView changes.

Limitation: No AzDO authentication token was available; authenticated test-run APIs were skipped. Build metadata, timelines, and logs were queried as public. Baseline inspection was capped at the first 8 failed logs per build, so the baseline failure list may be incomplete.

@kubaflo

kubaflo commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Note

🔍 AI-generated comment — this CI failure triage was produced by an AI agent (GitHub Copilot CLI) on request. Please verify before acting.

CI failure triage — failures are unrelated to this PR

All three pipelines are red, but the redness is inherited from a broken inflight/current base branch, not caused by the changes in this PR. The strongest evidence: this PR's maui-pr build and the latest base-branch build have an identical failure set (46 distinct patterns / 137 total, 0 unique to the PR), and no timeline record across the 1,219 maui-pr + 162 device-test records even mentions HybridWebView.

Pipeline Root cause Verdict
maui-pr 1483665 SimpleTemplateTest.cs CS0117 build break + Helix unit-test fails Unrelated — baseline
maui-pr-uitests 1483667 PublicAPI analyzer errors (SwipeItemView etc.) Unrelated — baseline
maui-pr-devicetests 1483668 Same PublicAPI break + pre-existing device-test crashes Unrelated — baseline

Evidence

  • maui-pr — A compile break in a file this PR does not touch poisons the whole solution build:
    src/TestUtils/src/Microsoft.Maui.IntegrationTests/SimpleTemplateTest.cs(359,10): error CS0117: 'Assert' does not contain a definition for 'IsTrue' (Assert.IsTrue is NUnit syntax; that project is xUnit → should be Assert.True). This breaks Build Microsoft.Maui.sln + the Windows/macOS build legs and cascades to every integration-test leg (No .trx files found → exit 1). Identical 137-failure set on baseline build 1488523.

  • maui-pr-uitests — "Build the samples" fails on PublicAPI analyzer errors in SwipeItemView.cs (RS0016), StructuredItemsViewController2.UpdateFlowDirection (RS0025), and MauiAppCompatActivity.OnBackPressed (RS0017) — none of which are files in this PR. The sample app never builds, so no UI test runs.

  • maui-pr-devicetests — The Windows build leg fails on the same SwipeItemView.cs error RS0016. The Android/iOS/MacCatalyst test runs show only ReturnType Initializes Correctly + two Helix work-item crash markers — byte-for-byte identical to baseline build 1488525. This PR's own HybridWebViewTests_SendRawMessage does not appear in any failure list.

Context

  • The base branch inflight/current is independently broken right now: all 8 recent maui-pr and all 4 recent maui-pr-devicetests post-merge builds on it are red.
  • This PR targets inflight/current, so the maui-copilot reviewer won't run (it only supports main/netN.0), but the standard PR pipelines did run — hence these checks.

Bottom line

No failure is attributable to this PR. The red checks are gated entirely by the broken base branch (the SimpleTemplateTest.cs Assert.IsTrue → Assert.True fix and the PublicAPI entries), not by anything here. A rebase once inflight/current is green should clear them.

— Generated by GitHub Copilot CLI 🔍

@kubaflo
kubaflo merged commit 0b9c07d into inflight/current Jun 30, 2026
6 of 49 checks passed
@kubaflo
kubaflo deleted the harden-hybridwebview-android-bridge-origin branch June 30, 2026 22:36
@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jun 30, 2026
kubaflo pushed a commit that referenced this pull request Jul 3, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo kubaflo mentioned this pull request Jul 6, 2026
kubaflo pushed a commit that referenced this pull request Jul 6, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 10, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 15, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 18, 2026
…st interception order and Windows NUL-character handling (#36544)

### Failed Test
### Android:

1. RequestsCanBeInterceptedAndCustomDataReturned
2. RequestsCanBeInterceptedAndAsyncCustomDataReturned

### Windows:

1. SendRawMessageRoundTripsSpecialCharacters [InlineData("with\0nul")]

### Root Cause

### Android – Request interception regression

- PR #35850 replaced the native `window.hybridWebViewHost.sendMessage`
bridge with an HTTP `fetch()`-based transport that communicates through
reserved framework endpoints (`__hwvSendMessage` and
`__hwvInvokeDotNet`), which are processed by
`MauiHybridWebViewClient.ShouldInterceptRequest`.
- With the bridge now using standard HTTP requests,
`ShouldInterceptRequest` invoked the application's
`WebResourceRequested` interception before handling the framework's own
bridge requests. As a result, internal framework requests that were
previously invisible to application code became exposed to custom
request handlers.
- Applications that assumed every intercepted request contained
application-specific query parameters could throw exceptions (for
example, `KeyNotFoundException`). Because these exceptions propagated
across the JNI boundary on the WebView thread, they could terminate the
application.

### Windows – Existing WebView2 NUL-character limitation

HybridWebView communicates through WebView2's `PostWebMessageAsString`
and `TryGetWebMessageAsString` APIs, which internally marshal
null-terminated native strings (`LPCWSTR`/`LPWSTR`).

Consequently, an embedded NUL (`\0`) character is treated as the end of
the string, truncating any remaining content. This limitation existed
before PR #35850 and was not introduced by the Android changes. However,
the newly added `SendRawMessageRoundTripsSpecialCharacters` device
test—specifically the `"with\0nul"` test case—exposed the issue. Other
special-character scenarios (Unicode, `%`, and newline) continued to
work correctly.

### Description of Change
### Android
The Android bridge was updated to clearly separate framework-internal
bridge traffic from application-defined request interception.

- **Corrected request routing:** `ShouldInterceptRequest` now checks
`IsFrameworkInternalRequest` before invoking the application's
`WebResourceRequested` callback. Framework bridge requests are handled
entirely within the framework and are no longer exposed to application
interception, restoring the behavior that existed before PR #35850.
- **Stronger framework request validation:** Internal requests are
identified using the reserved bridge endpoint paths together with the
expected `X-Maui-Invoke-Token` header where applicable, preventing
unrelated application requests from being treated as framework traffic.
- **Framework exception isolation:** Only the framework's internal
`Handler.MessageReceived` dispatch is wrapped in a `try/catch`, ensuring
unexpected framework exceptions cannot escape across the JNI boundary
and crash the WebView thread. Application-defined request interception
remains unchanged, preserving existing application behavior.
- Common URI parsing and application-relative path resolution logic was
consolidated into a shared `TryGetAppRelativePath` helper, reducing
duplication and simplifying request processing.

### Windows
The Windows fix applies URL encoding at the WebView2 transport boundary
so embedded NUL characters survive native string marshalling.

- **.NET → JavaScript:** Messages are encoded with
`Uri.EscapeDataString` before calling `PostWebMessageAsString`, and
JavaScript decodes them using `decodeURIComponent`.
- **JavaScript → .NET:** JavaScript encodes outgoing messages with
`encodeURIComponent`, and `OnWebMessageReceived` decodes them using
`Uri.UnescapeDataString` before dispatching the message.
- **Shared raw-message path:** The existing shared raw-message pipeline
continues to perform a single decode in
`HybridWebViewHandler.MessageReceived` via `Uri.UnescapeDataString`,
while `sendRawMessage` now URL-encodes its payload with
`encodeURIComponent`. This preserves embedded NUL characters and other
special characters without changing the shared handler logic or the
overall JavaScript bridge design.


### Validated the behaviour in the following platforms
- [x] Android
- [x] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #36469 

### Screenshots
| Before  | After |
|---------|--------|
| <video
src="https://github.com/user-attachments/assets/ceac9179-e177-4597-b98f-377b6c60d8c8">
| <video
src="https://github.com/user-attachments/assets/a2b5387e-a0a4-4af4-9331-8c51d2f04178">
|
kubaflo pushed a commit that referenced this pull request Jul 22, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 28, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 29, 2026
…35850)

> HybridWebView generally expects all hosted code to be local app
content, so this is general hardening — it's just for correctness, not
security enforcement.

## Background

The Android handler used
`WebView.AddJavascriptInterface("hybridWebViewHost")` to expose
`HybridWebViewJavaScriptInterface.SendMessage` to JavaScript.
`addJavascriptInterface` injects the bridge into **every frame** in the
WebView regardless of origin, and `@JavascriptInterface` does not
surface the calling frame to the C# side, so any non-app-origin
sub-frame loaded by the page can reach it.

## Fix

Route JS → .NET messages through the same HTTP intercept channel that
`__hwvInvokeDotNet` already uses. The endpoint is gated by:

- `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` /
`Referer`), and
- the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight
on cross-origin callers — and we never return permissive CORS headers.

After this change, every JS → .NET call on Android flows through one
validated entry point:

| JS API | Endpoint | Validated by |
|---|---|---|
| `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` |
`TryValidateBridgeRequest` |
| `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` |
`TryValidateBridgeRequest` |
| `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks |
`__hwvSendMessage` | `TryValidateBridgeRequest` |

`window.hybridWebViewHost` no longer exists.

## Changes

- Remove `AddJavascriptInterface` and the
`HybridWebViewJavaScriptInterface` inner class.
- Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`.
- Extract `TryValidateBridgeRequest` helper shared with
`__hwvInvokeDotNet` (same 400/405 gate).
- `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on
a single promise to preserve the FIFO ordering callers had with the
synchronous bridge.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 31, 2026
@github-actions github-actions Bot modified the milestones: .NET 10 SR9, .NET 10 SR10 Aug 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-controls-hybridwebview HybridWebView control platform/android s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants