Consolidate file path resolution for WebView controls#34620
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34620Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34620" |
There was a problem hiding this comment.
Pull request overview
This PR refactors WebView-related URL/path resolution across HybridWebView and BlazorWebView to use shared helpers, aiming to prevent Path.Combine from dropping the intended root when given separator-prefixed “relative” paths, and to harden against path traversal.
Changes:
- Added shared helpers for request-URI → relative-path resolution (
WebUtils.ResolveRelativePath) and for validating/combining root + relative paths (FileSystemUtils.IsValidRelativePath/Combine). - Updated HybridWebView handlers (Android/iOS/Windows) and BlazorWebView asset file providers/managers (Android/iOS/Windows/Tizen) to use the shared helpers.
- Added device tests and test assets covering relative paths, rooted paths, dot-dot segments, and encoded separators.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Essentials/src/Types/Shared/WebUtils.shared.cs | Adds ResolveRelativePath for validating app-origin-relative request paths. |
| src/Essentials/src/FileSystem/FileSystemUtils.shared.cs | Adds IsValidRelativePath and Combine helper for safe root+relative path combination. |
| src/Essentials/src/Email/Email.windows.cs | Switches attachment path normalization to FileSystemUtils.NormalizePath. |
| src/Core/src/Platform/Android/MauiHybridWebViewClient.cs | Uses shared helpers for HybridWebView Android request path resolution + asset lookup. |
| src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.iOS.cs | Uses shared helpers for iOS scheme-task request path resolution + asset lookup. |
| src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs | Uses shared helpers for Windows request path resolution + asset lookup. |
| src/Controls/tests/DeviceTests/Resources/Raw/HybridTestRoot/urlresolution.html | Adds test page used to fetch URLs and report results to device tests. |
| src/Controls/tests/DeviceTests/Resources/Raw/HybridTestRoot/safe-file.txt | Adds a known “safe” file for positive resolution assertions. |
| src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests_ContentRootResolution.cs | Adds HybridWebView device tests covering path resolution edge cases. |
| src/Controls/src/Core/Controls.Core.csproj | Includes the shared FileSystemUtils.shared.cs source into Controls.Core for reuse. |
| src/BlazorWebView/tests/DeviceTests/Elements/BlazorWebViewTests.ContentRootResolution.cs | Adds BlazorWebView device tests covering path resolution edge cases. |
| src/BlazorWebView/src/Maui/iOS/iOSMauiAssetFileProvider.cs | Uses shared combine helper when serving bundled static assets (iOS). |
| src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs | Uses shared ResolveRelativePath and combine helper for local folder serving (Windows). |
| src/BlazorWebView/src/Maui/Tizen/TizenMauiAssetFileProvider.cs | Uses shared combine helper when serving packaged static assets (Tizen). |
| src/BlazorWebView/src/Maui/Android/AndroidMauiAssetFileProvider.cs | Uses shared combine helper when serving APK assets (Android). |
2b033cf to
e19a666
Compare
Refactored scattered file-path resolution logic into shared utilities (FileSystemUtils.Combine, WebUtils.ResolveRelativePath) to fix edge cases where Path.Combine drops the root directory when a relative path starts with a separator. Added device tests for path resolution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e19a666 to
2b1b2b8
Compare
🧪 PR Test EvaluationOverall Verdict: The device tests cover the critical path traversal attack vectors end-to-end, but the new security-critical utility methods (
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34620 — Consolidate file path resolution for WebView controls Overall VerdictThe device tests provide solid end-to-end coverage of the path traversal scenarios, but the core security utility methods introduced in 1. Fix Coverage — ✅The tests exercise the actual URL-to-file resolution pipeline that was refactored, including the new 2. Edge Cases & Gaps —
|
| Platform | Fix Files | Test Coverage |
|---|---|---|
| Android | AndroidMauiAssetFileProvider.cs, MauiHybridWebViewClient.cs |
✅ Via device tests |
| iOS | iOSMauiAssetFileProvider.cs, HybridWebViewHandler.iOS.cs |
✅ Via device tests |
| Windows | WinUIWebViewManager.cs, HybridWebViewHandler.Windows.cs, Email.windows.cs |
✅ Via device tests |
| MacCatalyst | Shared with iOS | ✅ Via iOS device tests (.ios.cs compiles for both) |
| Tizen | TizenMauiAssetFileProvider.cs |
❌ No Tizen tests |
| Shared/Cross-platform | FileSystemUtils.shared.cs, WebUtils.shared.cs, Email.windows.cs |
The Essentials/src/Email/Email.windows.cs change also uses the new FileSystemUtils.Combine — this is only exercised if Windows email attachment tests exist elsewhere.
8. Assertion Quality — ⚠️
| Test | Assertion | Quality |
|---|---|---|
RelativePaths_ResolveToContent |
Assert.Equal(200, ...) + body length |
✅ Specific |
KnownFile_ReturnsExpectedContent |
Checks actual body content | ✅ Excellent |
RootedPath_DoesNotResolve (HybridWebView) |
Assert.NotEqual(200, ...) |
✅ Clear |
DotDotSegments_DoNotResolveAboveRoot (HybridWebView) |
Assert.NotEqual(200, ...) |
✅ Clear |
Blazor_RootedPath_DoesNotResolve |
`status != 200 | |
Blazor_DotDotSegments_DoNotResolveAboveRoot |
`status != 200 |
9. Fix-Test Alignment — ✅
Fix files and test files align well: BlazorWebViewTests.ContentRootResolution.cs tests the BlazorWebView asset provider changes, and HybridWebViewTests_ContentRootResolution.cs tests the HybridWebView handler changes. Both exercise the shared FileSystemUtils and WebUtils logic through the request pipeline.
Recommendations
-
Add unit tests for
FileSystemUtils.IsValidRelativePathandFileSystemUtils.Combineinsrc/Essentials/test/UnitTests/FileSystem_Tests.cs. These methods are the security foundation of this PR — test them directly with edge cases like empty string, single dot./, null, backslash-only paths, and null byte injection (\0). -
Tighten BlazorWebView negative assertions — replace
Assert.True(result.status != 200 || IsSpaFallback(result))withAssert.NotEqual(200, result.status)where possible, or add a comment explaining why SPA fallback behavior is intentionally acceptable for these paths. As written, the assertion could silently pass even if a traversal attack succeeds but returns HTML. -
Add a Windows case-sensitivity test — verify that
FileSystemUtils.Combinebehaves correctly when root and path have different casing on Windows (e.g.,C:\Root\Contentvsc:\root\content\file.txt), sinceStringComparison.Ordinalis case-sensitive and Windows paths are not.
Warning
⚠️ Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
dc.services.visualstudio.com
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
network:
allowed:
- defaults
- "dc.services.visualstudio.com"See Network Configuration for more information.
Note
🔒 Integrity filtering filtered 2 items
Integrity filtering activated and filtered the following items during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.
- pr:Consolidate file path resolution for WebView controls #34620 (
pull_request_read: Resource 'pr:Consolidate file path resolution for WebView controls #34620' has lower integrity than agent requires. Agent would need to drop integrity tags [approved:all unapproved:all] to trust this resource.) - pr:Consolidate file path resolution for WebView controls #34620 (
pull_request_read: Resource 'pr:Consolidate file path resolution for WebView controls #34620' has lower integrity than agent requires. Agent would need to drop integrity tags [unapproved:all approved:all] to trust this resource.)
🧪 Test evaluation by Evaluate PR Tests
- Fix IsValidRelativePath to check '..' per-segment instead of substring - Fix Combine to preserve relative roots for Android/package asset paths - Fix Combine to use case-insensitive comparison on Windows - Remove unused using directive in WebUtils.shared.cs - Rename test methods and comments for clarity - Add FileSystemUtils and WebUtils unit tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: ✅ Tests are adequate The PR adds strong, well-layered test coverage for the path resolution security fix — unit tests cover the logic exhaustively, and device tests validate the fix end-to-end inside real WebView instances. One minor assertion in
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34620 — Consolidate file path resolution for WebView controls Overall Verdict✅ Tests are adequate The PR provides excellent layered coverage: pure unit tests for the new 1. Fix Coverage — ✅The tests directly exercise the fixed code paths:
2. Edge Cases & Gaps — ✅Covered:
Potential minor gap:
3. Test Type Appropriateness — ✅
No UI tests (Appium) were added, which is correct — the fix is in file-serving infrastructure, not visual UI. 4. Convention Compliance — ✅Automated script found 0 convention issues.
5. Flakiness Risk — ✅ Low
6. Duplicate Coverage — ✅ No duplicatesThe script identified 7. Platform Scope — ✅The fix touches all four MAUI platforms (Android, iOS, MacCatalyst, Windows). Coverage:
The Tizen gap is acceptable; Tizen has no device test infrastructure in this repo. 8. Assertion Quality — ✅Most assertions are well-targeted:
Minor concern: The Blazor device test assertions use 9. Fix-Test Alignment — ✅
Recommendations
Warning
|
…ompat - Revert Tizen file provider changes (Tizen is no longer supported) - Replace OperatingSystem.IsWindows() with OrdinalIgnoreCase comparison to fix CS0117 on netstandard2.0 builds - Add null-forgiving operators for netstandard2.0 nullable analysis Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: ✅ Tests are adequate The PR includes 22 unit tests and 14 device tests with comprehensive coverage of the core fix (path traversal prevention), edge cases, and cross-platform integration. Minor improvements noted below.
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34620 — Consolidate file path resolution for WebView controls Overall Verdict✅ Tests are adequate The PR has comprehensive unit tests for the new 1. Fix Coverage — ✅The tests directly exercise the two new shared utilities that form the heart of the fix:
The tests would fail if the fix were reverted. 2. Edge Cases & Gaps — ✅Covered:
Missing (minor):
None of these are blocking. 3. Test Type Appropriateness — ✅Current: Unit Tests + Device Tests The new utilities ( 4. Convention Compliance — ✅
5. Flakiness Risk — ✅ Low
6. Duplicate Coverage — ✅ No duplicatesExisting 7. Platform Scope — ✅Fix touches Windows, Android, iOS, and MacCatalyst handlers. Unit tests run on all platforms (no platform-specific code). Device tests cover:
MacCatalyst is covered by the iOS device tests (same 8. Assertion Quality — ✅
9. Fix-Test Alignment — ✅
All fix components have corresponding tests. Recommendations
Warning
|
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 3 pipeline(s). |
The Blazor host page is only served for navigation requests (ResourceContext.Document),
not for JavaScript fetch() requests. Calling fetch('') from within a BlazorWebView either
throws (unpackaged Windows) or hangs indefinitely (packaged Windows), causing CI failures.
The host page loading is already verified by the RunTest infrastructure which waits for
the Blazor component to render before running any test code.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: The unit tests are thorough and well-structured. The main gap is that BlazorWebView device tests lack a positive (happy-path) test confirming valid files still resolve correctly after the fix — without it, a regression that accidentally blocks legitimate requests would go undetected.
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34620 — Consolidate file path resolution for WebView controls Overall VerdictThe unit tests for 1. Fix Coverage — ✅The fix replaces unsafe
The 2. Edge Cases & Gaps —
|
| Platform | Fix files | Device test coverage |
|---|---|---|
| Android | AndroidMauiAssetFileProvider.cs, MauiHybridWebViewClient.cs |
✅ HybridWebView device tests; BlazorWebView device tests |
| iOS | iOSMauiAssetFileProvider.cs, HybridWebViewHandler.iOS.cs |
✅ HybridWebView device tests; BlazorWebView device tests |
| MacCatalyst | iOSMauiAssetFileProvider.cs (.ios.cs compiles for both) |
.ios.cs; no explicit MacCatalyst test case |
| Windows | WinUIWebViewManager.cs, HybridWebViewHandler.Windows.cs, Email.windows.cs |
✅ HybridWebView device tests; BlazorWebView device tests |
MacCatalyst is covered transitively via the .ios.cs file extension (which compiles for both iOS and MacCatalyst) and the Helix CI infra, but there are no explicit MacCatalyst test assertions.
8. Assertion Quality — ⚠️
- Unit tests: ✅ Specific —
Assert.True/False,Assert.Null/NotNull,Assert.Equalwith concrete expected values - HybridWebView device tests: ✅ Specific —
Assert.Equal(200, result.status),Assert.NotEqual(200, result.status),Assert.Contains("content directory", ...)directly tests the known file content - BlazorWebView device tests:
⚠️ Slightly permissive — assertions useresult.status != 200 || IsSpaFallback(result). This is intentional (SPA fallback behavior) but means a traversal attack that returns the host page would pass the test. Consider adding an assertion onresult.bodyPreviewthat it does NOT contain the actual traversed file's content, to harden the negative cases.
9. Fix-Test Alignment — ✅
All changed files map correctly to the tests:
FileSystemUtils.shared.cs→FileSystemUtils_Tests.cs(complete coverage)WebUtils.shared.cs→WebUtils_Tests.cs(good coverage)- HybridWebView handlers (iOS, Android, Windows) →
HybridWebViewTests_ContentRootResolution.cs - BlazorWebView file providers (iOS, Android) and Windows manager →
BlazorWebViewTests.ContentRootResolution.cs Email.windows.cs→ covered byNormalizePathunit test (transitively)
Recommendations
-
Add a positive test for BlazorWebView — add a test case in
BlazorWebViewTests.ContentRootResolution.csthat fetches a known-good static asset (e.g., the Blazor wwwroot index or a test.txtfile) and asserts it returns 200 with expected content. This ensures the fix doesn't accidentally block legitimate requests. -
Harden BlazorWebView negative assertions — in
Blazor_DotDotSegments_DoNotResolveAboveRootetc., consider additionally asserting thatresult.bodyPreviewdoes not contain sensitive content (even ifIsSpaFallbackis true), to guard against a case where traversal returns the host page content instead of the intended blocked response. -
Consider an explicit
Email.windows.csintegration test (low priority) — the change is a minor refactor, but a quick unit test assertingFileSystemUtils.NormalizePathbehaves identically to the old inline method on the specific patterns used by Email attachments would make the refactor provably safe.
Warning
⚠️ Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
dc.services.visualstudio.com
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
network:
allowed:
- defaults
- "dc.services.visualstudio.com"See Network Configuration for more information.
Note
🔒 Integrity filtering filtered 2 items
Integrity filtering activated and filtered the following items during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.
- pr:Consolidate file path resolution for WebView controls #34620 (
pull_request_read: Resource 'pr:Consolidate file path resolution for WebView controls #34620' has lower integrity than agent requires. Agent would need to drop integrity tags [approved:all unapproved:all] to trust this resource.) - pr:Consolidate file path resolution for WebView controls #34620 (
pull_request_read: Resource 'pr:Consolidate file path resolution for WebView controls #34620' has lower integrity than agent requires. Agent would need to drop integrity tags [unapproved:all approved:all] to trust this resource.)
🧪 Test evaluation by Evaluate PR Tests
|
Azure Pipelines successfully started running 1 pipeline(s). |
1 similar comment
|
Azure Pipelines successfully started running 1 pipeline(s). |
Fetches _framework/blazor.webview.js (always available in a BlazorWebView) and asserts status 200 with content. This exercises ResolveRelativePath with a valid multi-segment relative path and ensures the path hardening doesn't accidentally block legitimate requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🧪 PR Test EvaluationOverall Verdict: ✅ Tests are adequate This PR adds a well-layered test suite (unit tests for the shared utilities + device tests for real WebView end-to-end behavior) that directly exercises the path traversal and rooted-path security fixes.
📊 Expand Full EvaluationPR Test Evaluation ReportPR: #34620 — Consolidate file path resolution for WebView controls Overall Verdict✅ Tests are adequate The PR takes a strong, layered approach: lightweight xUnit unit tests for the two new shared utilities ( 1. Fix Coverage — ✅The unit tests in The device tests in 2. Edge Cases & Gaps — ✅Covered:
Minor gaps (non-blocking):
3. Test Type Appropriateness — ✅
No UI tests were added (correct — no Appium-level interaction changed). The device tests are the lightest type that can verify actual HTTP response codes from the WebView interceptors. 4. Convention Compliance — ✅No convention issues found by automated script.
5. Flakiness Risk — ✅ Low
6. Duplicate Coverage — ✅ No duplicatesThe existing 7. Platform Scope — ✅The fix touches all 4 platforms (Android, iOS, Windows, MacCatalyst) plus shared utilities.
8. Assertion Quality — ✅
Minor concern: 9. Fix-Test Alignment — ✅
Recommendations
Warning
|
|
Azure Pipelines successfully started running 1 pipeline(s). |
) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Adds automated milestone management for PRs and issues. A single workflow handles both automatic milestoning on PR merge and manual tag-based reconciliation after releases ship. ## Problem When PRs merge, they often get milestoned for the wrong service release or not milestoned at all. The actual release a PR ships in depends on which Candidate PR carries the commits and when the SR branch is cut. This creates milestone drift that makes release queries and notes inaccurate. ## Solution ### Files | File | Purpose | |------|---------| | `.github/scripts/Fix-MilestoneDrift.ps1` | Core engine — version detection, milestone mapping, correction logic | | `.github/scripts/Fix-MilestoneDrift.Tests.ps1` | 77 Pester unit tests for all pure functions | | `.github/workflows/fix-milestone-drift.yml` | Single workflow with dual triggers (auto on merge + manual dispatch) | --- ## How It Works ### Version Detection The source of truth is `eng/Versions.props`. For single-PR mode, the script reads `MajorVersion` and `PatchVersion` **at the PR's merge commit SHA** to determine what version the branch was building when the PR merged. ### Milestone Mapping | Patch | Example | Milestone | Rule | |-------|---------|-----------|------| | `0` | `10.0.0` | `.NET 10.0 GA` | Exact zero | | `1-9` | `10.0.5` | `.NET 10.0 SR1` | Early patches | | `N0` | `10.0.60` | `.NET 10 SR6` | patch / 10 | | `N0+x` | `10.0.41` | `.NET 10 SR4.1` | Sub-patch = distinct milestone | Sub-patches are distinct — `.NET 10 SR4.1` != `.NET 10 SR4`. Script warns and skips gracefully if the milestone doesn't exist on GitHub yet. ### Milestone Normalization GitHub milestones have inconsistent naming (`.NET 10.0 SR4` vs `.NET 10 SR4`, `.NET 10.0 GA` vs `.NET 10 GA`). The script normalizes both SR and GA forms as equivalent. ### Branch Ownership Detection Reads `<MajorVersion>` from `eng/Versions.props` on `origin/main`. No hardcoded version numbers — automatically handles version transitions when `main` moves from .NET 10 to .NET 11. ### Merge-Up Protection Checks each PR's `base.ref` to skip PRs from different .NET versions (prevents merge-up commits from causing incorrect milestoning): | `base.ref` | MainBranch=`main` | MainBranch=`net11.0` | |------------|-------------------|---------------------| | `main` | ✅ | ❌ | | `inflight/*`, `darc/*` | ✅ | ❌ | | `net11.0` | ❌ | ✅ | | `release/10.*` | ✅ | ❌ | | `release/11.*` | ❌ | ✅ | ### Linked Issue Detection Scans PR title and body for: - `fix/fixes/fixed/close/closes/closed/resolve/resolves/resolved #N` - Same keywords + `https://github.com/dotnet/maui/issues/N` Bare informational URLs are ignored. Results are deduplicated. --- ## Single Workflow, Two Triggers ### Auto: On PR Merge (`pull_request_target`) Triggers on every PR merge to `main`, `net*.0`, `inflight/*`, or `release/*`. Reads `Versions.props` at the merge commit, sets the milestone on the PR and its linked issues. If the milestone doesn't exist yet on GitHub, warns and skips gracefully (no red CI). ### Manual: Tag Reconciliation (`workflow_dispatch`) | Input | Type | Default | Description | |-------|------|---------|-------------| | `pr_number` | string | _(empty)_ | Single PR to check | | `tag` | string | _(empty)_ | Release tag to audit (e.g. `10.0.60`) | | `apply` | boolean | `false` | Apply changes (default: dry-run) | | `create_issue` | boolean | `false` | Create GitHub issue with report | --- ## Safety | Rule | Purpose | |------|---------| | Dry-run by default (manual) | `workflow_dispatch` requires explicit `apply` checkbox | | Date cutoff (2026-01-01) | Never touches PRs merged before 2026 | | Branch check | Skips merge-up PRs from different .NET versions | | Deduplication | Same issue corrected once even if linked from multiple PRs | | Fixing-keyword URLs | Bare informational issue references ignored | | Milestone must exist | Warns and skips gracefully if milestone not found | | Merge commit fetch | Fetches commit on demand for PRs merged to inflight | | Warning diagnostics | Silent failures in version detection log warnings | | GA tag support | Tag mode handles first release tag (no previous tag) | | Input sanitization | Workflow inputs flow through `env:` vars, not inline interpolation | | Error propagation | `Invoke-ApplyCorrections` throws on failure; CI goes red | | Zero-check guard | Tag mode throws when all PRs fail to fetch (prevents false "all correct") | --- ## Known Limitations - **Preview/RC milestones**: The script currently maps `PatchVersion=0` to `.NET X.0 GA`. It does not read `PreReleaseVersionLabel` or `PreReleaseVersionIteration` from `Versions.props`, so PRs merged to `net11.0` during the preview phase will not be automatically milestoned (the workflow gracefully skips with a warning). Preview milestone support is planned as a follow-up. - **GA tag rate limit**: Running `-Tag X.0.0` (the very first release tag) in manual mode walks the full git history, which could exhaust GitHub API rate limits on large repos. --- ## Test Suite 77 Pester unit tests covering all pure functions: | Block | Tests | Coverage | |-------|-------|----------| | `ConvertTo-Milestone` | 17 | GA, SR1-SR10, sub-patches (SR4.1, SR10.1), early patches, preview/invalid | | `Test-MilestoneMatch` | 12 | Exact, normalization (SR + GA), sub-patch distinction, null/empty | | `Find-MatchingMilestone` | 5 | Direct, normalized, alternate format, non-existent | | `Find-PreviousTag` | 9 | Ordered traversal, boundaries, major-version isolation | | `Get-LinkedIssues` | 11 | Keywords (incl. close/resolve bare), URLs, dedup, case | | `Get-PatchVersion` | 4 | Valid, GA, large, invalid | | `Test-IsReleaseTag` | 5 | Valid release, wrong major, preview, invalid | | `Test-PrBelongsToVersion` | 14 | main/net11.0 contexts, inflight, darc, release, feature | --- ## Local Usage ```powershell # Dry-run: check a single PR ./Fix-MilestoneDrift.ps1 -PrNumber 34620 -RepoPath . -Verbose # Dry-run: audit all PRs in a tagged release ./Fix-MilestoneDrift.ps1 -Tag 10.0.50 -RepoPath . -Verbose # Apply fixes ./Fix-MilestoneDrift.ps1 -PrNumber 34620 -Apply # Full tag audit + apply + report issue ./Fix-MilestoneDrift.ps1 -Tag 10.0.50 -Apply -CreateIssue # Run tests Invoke-Pester ./Fix-MilestoneDrift.Tests.ps1 -Output Detailed ``` ## Validated Results - 77 Pester tests pass - Single-PR: #34047 → `PatchVersion=50` at merge → `.NET 10 SR5` ✅ - Single-PR: #34620 (untagged) → `PatchVersion=60` at merge → `.NET 10 SR6` ✅ - Single-PR: #34553 (merged to inflight) → fetches commit, reads `PatchVersion=60` ✅ - Tag mode: `10.0.50` → 78 PRs checked, 90 corrections, 0 duplicates, 0 false positives ✅ - 50-PR sample against 10.0.60 range → correctly identifies SR7→SR6 drift ✅ - .NET 11 preview PR #34252 → gracefully warns "milestone not found", exits 0 ✅ --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Get-VersionFromGitRef now reads PreReleaseVersionLabel and PreReleaseVersionIteration from eng/Versions.props. ConvertTo-Milestone maps these to preview/RC milestone names: 11.0.0 + preview + 3 → .NET 11.0-preview3 12.0.0 + rc + 1 → .NET 12.0-rc1 10.0.60 (stable) → .NET 10 SR6 (unchanged) Stable labels (ci.main, ci.inflight, servicing) are treated as non-prerelease and continue to use the GA/SR mapping. 6 new tests for preview/RC mapping (83 total, all passing). Backward compatible — .NET 10 stable PRs and tag mode unaffected. Validated: - PR #34252 (net11.0, preview1) → .NET 11.0-preview1 ✅ - PR #34620 (.NET 10 stable) → .NET 10 SR6 ✅ - Tag 10.0.50 → 78 PRs, 90 corrections ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Follow-up to #34686. Adds preview/RC milestone support and release branch detection to the milestone drift fixer. ## Problem PRs merged to \`net11.0\` were not being milestoned because \`Versions.props\` on that branch always has \`PreReleaseVersionIteration=1\` regardless of which preview the PR actually ships in. The iteration is only bumped on release branches. ## Solution ### Release Branch Detection (Primary) New detection step checks release branches first using \`merge-base --is-ancestor\`. For each PR, it finds the **earliest** release branch containing the merge commit: | Release Branch | Milestone | |---|---| | \`release/10.0.1xx\` | \`.NET 10.0 GA\` | | \`release/10.0.1xx-sr5\` | \`.NET 10 SR5\` | | \`release/11.0.1xx-preview1\` | \`.NET 11.0-preview1\` | | \`release/11.0.1xx-preview3\` | \`.NET 11.0-preview3\` | | \`release/12.0.1xx-rc1\` | \`.NET 12.0-rc1\` | ### Detection Order 1. **Explicit \`-Tag\`** — if provided 2. **Release branches** — \`merge-base --is-ancestor\` against \`release/{Major}.0.1xx-*\` branches, earliest match wins 3. **Versions.props** at merge commit — fallback for PRs not yet on any release branch 4. **Tag range search** — last resort ### Preview/RC Milestone Mapping \`ConvertTo-Milestone\` now accepts optional pre-release label and iteration: | Input | Milestone | |---|---| | \`11.0.0 + preview + 3\` | \`.NET 11.0-preview3\` | | \`12.0.0 + rc + 1\` | \`.NET 12.0-rc1\` | | \`10.0.60\` (stable) | \`.NET 10 SR6\` (unchanged) | ## Validated Results | PR | Base | Current Milestone | Script Result | Method | |---|---|---|---|---| | #33524 | net11.0 | .NET 11.0-preview1 | .NET 11.0-preview1 ✅ | Release branch | | #33233 | net11.0 | .NET 11.0-preview1 | .NET 11.0-preview1 ✅ | Release branch | | #30132 | net11.0 | .NET 11.0-preview3 | .NET 11.0-preview3 ✅ | Release branch | | #33834 | net11.0 | .NET 11.0-preview3 | .NET 11.0-preview3 ✅ | Release branch | | #34214 | net11.0 | .NET 11.0-preview2 | .NET 11.0-preview3 ✅ | Release branch (drift caught!) | | #34945 | net11.0 | .NET 11.0-preview4 | preview1 (fallback) | Versions.props (no p4 branch yet) | | #34620 | main | .NET 10 SR6 | .NET 10 SR6 ✅ | Release branch | | #34047 | main | .NET 10 SR4.1 | .NET 10 SR5 ✅ | Release branch (drift caught!) | PR #34214 is a real drift example: milestoned preview2 by a human, but actually on the preview3 release branch. ## Test Suite 88 Pester tests (11 new): - 6 for \`ConvertTo-Milestone\` preview/RC mapping - 5 for \`ConvertBranchToMilestone\` (GA, SR, preview, RC, non-release) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a PR's merge commit is not on any release branch, the script was reading Versions.props at the merge commit. This gives stale values — e.g. a PR merged to inflight/current when PatchVersion=60 would get milestoned SR6, even though main has since moved to 70. Now uses the PR's base.ref to determine which branch to read from: main, inflight/*, darc/* → origin/main net11.0 → origin/net11.0 release/* → that branch directly This ensures .NET 11 PRs always read from net11.0 (not main), and inflight PRs read from main (not the stale merge commit). Release branch detection still takes priority. Validated: - #34228 (inflight/current) → reads main → SR7 (was incorrectly SR6) - #34969 (net11.0) → reads net11.0 → preview1 - #34620 (main, on SR6 branch) → release branch → SR6 (unchanged) 91 Pester tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…commit (#35023) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Fixes milestone fallback to use `Versions.props` from `origin/main` instead of the merge commit when no release branch contains the PR. ## Problem When a PR merges to `inflight/current`, the script was reading `Versions.props` at the merge commit. This gives stale values — e.g., PR #34228 merged when `PatchVersion=60` (SR6), but `main` has since moved to `PatchVersion=70` (SR7). The PR will ship in SR7, not SR6. ## Fix When no release branch contains the commit, read `Versions.props` from the development branch HEAD (`origin/main` for .NET 10, `origin/net11.0` for .NET 11) instead of the merge commit. This reflects where the PR is actually heading. Release branch detection still takes priority — PRs already on a release branch get the correct milestone from that branch. ## Validated | PR | Before | After | Method | |---|---|---|---| | #34228 (inflight, stale) | .NET 10 SR6 (wrong) | .NET 10 SR7 (correct) | main's Versions.props | | #34620 (on SR6 branch) | .NET 10 SR6 | .NET 10 SR6 (unchanged) | Release branch | | #30132 (on preview3 branch) | .NET 11.0-preview3 | .NET 11.0-preview3 (unchanged) | Release branch | 91 Pester tests pass. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…commit (#35023) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Fixes milestone fallback to use `Versions.props` from `origin/main` instead of the merge commit when no release branch contains the PR. ## Problem When a PR merges to `inflight/current`, the script was reading `Versions.props` at the merge commit. This gives stale values — e.g., PR #34228 merged when `PatchVersion=60` (SR6), but `main` has since moved to `PatchVersion=70` (SR7). The PR will ship in SR7, not SR6. ## Fix When no release branch contains the commit, read `Versions.props` from the development branch HEAD (`origin/main` for .NET 10, `origin/net11.0` for .NET 11) instead of the merge commit. This reflects where the PR is actually heading. Release branch detection still takes priority — PRs already on a release branch get the correct milestone from that branch. ## Validated | PR | Before | After | Method | |---|---|---|---| | #34228 (inflight, stale) | .NET 10 SR6 (wrong) | .NET 10 SR7 (correct) | main's Versions.props | | #34620 (on SR6 branch) | .NET 10 SR6 | .NET 10 SR6 (unchanged) | Release branch | | #30132 (on preview3 branch) | .NET 11.0-preview3 | .NET 11.0-preview3 (unchanged) | Release branch | 91 Pester tests pass. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…commit (dotnet#35023) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Fixes milestone fallback to use `Versions.props` from `origin/main` instead of the merge commit when no release branch contains the PR. ## Problem When a PR merges to `inflight/current`, the script was reading `Versions.props` at the merge commit. This gives stale values — e.g., PR dotnet#34228 merged when `PatchVersion=60` (SR6), but `main` has since moved to `PatchVersion=70` (SR7). The PR will ship in SR7, not SR6. ## Fix When no release branch contains the commit, read `Versions.props` from the development branch HEAD (`origin/main` for .NET 10, `origin/net11.0` for .NET 11) instead of the merge commit. This reflects where the PR is actually heading. Release branch detection still takes priority — PRs already on a release branch get the correct milestone from that branch. ## Validated | PR | Before | After | Method | |---|---|---|---| | dotnet#34228 (inflight, stale) | .NET 10 SR6 (wrong) | .NET 10 SR7 (correct) | main's Versions.props | | dotnet#34620 (on SR6 branch) | .NET 10 SR6 | .NET 10 SR6 (unchanged) | Release branch | | dotnet#30132 (on preview3 branch) | .NET 11.0-preview3 | .NET 11.0-preview3 (unchanged) | Release branch | 91 Pester tests pass. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…commit (#35023) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Fixes milestone fallback to use `Versions.props` from `origin/main` instead of the merge commit when no release branch contains the PR. ## Problem When a PR merges to `inflight/current`, the script was reading `Versions.props` at the merge commit. This gives stale values — e.g., PR #34228 merged when `PatchVersion=60` (SR6), but `main` has since moved to `PatchVersion=70` (SR7). The PR will ship in SR7, not SR6. ## Fix When no release branch contains the commit, read `Versions.props` from the development branch HEAD (`origin/main` for .NET 10, `origin/net11.0` for .NET 11) instead of the merge commit. This reflects where the PR is actually heading. Release branch detection still takes priority — PRs already on a release branch get the correct milestone from that branch. ## Validated | PR | Before | After | Method | |---|---|---|---| | #34228 (inflight, stale) | .NET 10 SR6 (wrong) | .NET 10 SR7 (correct) | main's Versions.props | | #34620 (on SR6 branch) | .NET 10 SR6 | .NET 10 SR6 (unchanged) | Release branch | | #30132 (on preview3 branch) | .NET 11.0-preview3 | .NET 11.0-preview3 (unchanged) | Release branch | 91 Pester tests pass. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…commit (#35023) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Fixes milestone fallback to use `Versions.props` from `origin/main` instead of the merge commit when no release branch contains the PR. ## Problem When a PR merges to `inflight/current`, the script was reading `Versions.props` at the merge commit. This gives stale values — e.g., PR #34228 merged when `PatchVersion=60` (SR6), but `main` has since moved to `PatchVersion=70` (SR7). The PR will ship in SR7, not SR6. ## Fix When no release branch contains the commit, read `Versions.props` from the development branch HEAD (`origin/main` for .NET 10, `origin/net11.0` for .NET 11) instead of the merge commit. This reflects where the PR is actually heading. Release branch detection still takes priority — PRs already on a release branch get the correct milestone from that branch. ## Validated | PR | Before | After | Method | |---|---|---|---| | #34228 (inflight, stale) | .NET 10 SR6 (wrong) | .NET 10 SR7 (correct) | main's Versions.props | | #34620 (on SR6 branch) | .NET 10 SR6 | .NET 10 SR6 (unchanged) | Release branch | | #30132 (on preview3 branch) | .NET 11.0-preview3 | .NET 11.0-preview3 (unchanged) | Release branch | 91 Pester tests pass. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes to milestone detection:
1. Fallback reads Versions.props from the development branch (main
for .NET 10, net11.0 for .NET 11) instead of origin/{base.ref}.
Staging branches like inflight/candidate have stale PatchVersion
but their PRs ultimately ship on main's version.
2. Find-ReleaseBranchForCommit falls back to git log --grep when
ancestry check fails. Handles rebases and cherry-picks where the
commit SHA changes but the PR number '(#NNNNN)' is preserved in
the commit message.
Updated live validation guide to match new behavior.
Validated:
- #34667 (rebased to SR6) → found via grep → .NET 10 SR6 ✅
- #34959 (inflight/candidate) → reads main → .NET 10 SR7 ✅
- #34620 (on SR6 branch) → ancestry → .NET 10 SR6 ✅
- #34969 (net11.0) → reads net11.0 → .NET 11.0-preview4 ✅
91 Pester tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ng branch (#35054) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Summary Fix milestone fallback for `inflight/*` and `darc/*` branches to read from `origin/main` instead of the staging branch itself. ## Problem PRs merged to `inflight/candidate` were milestoned as SR6 because `inflight/candidate` has `PatchVersion=60`. But those PRs will ultimately ship in SR7 (when the Candidate merges to `main`, which is at `PatchVersion=70`). ## Fix For staging branches (`inflight/*`, `darc/*`), read `Versions.props` from `origin/main` instead of `origin/{base.ref}`. These branches always feed into main, so main's version is the correct target. All other branches continue reading from `origin/{base.ref}` directly. ## Validated | PR | Base | Before | After | |---|---|---|---| | #34959 | inflight/candidate | SR6 (wrong) | SR7 ✅ | | #35040 | inflight/current | SR7 | SR7 ✅ | | #34969 | net11.0 | preview1 | preview4 ✅ | | #34620 | main (on SR6 branch) | SR6 | SR6 ✅ | 91 Pester tests pass. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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
Refactored scattered file-path resolution logic in HybridWebView and BlazorWebView into shared utilities (
FileSystemUtils.Combine,WebUtils.ResolveRelativePath) to fix edge cases wherePath.Combinedrops the root directory when a relative path starts with a separator.Changes
FileSystemUtils.shared.cs— AddedIsValidRelativePath()andCombine()methods that validate relative paths and ensure the resolved path stays within the expected root directoryWebUtils.shared.cs— AddedResolveRelativePath()to resolve request URIs against an app origin into validated relative pathsFileSystemUtils.Combine()for path resolutionEmail.windows.cs— Consolidated duplicateNormalizePathintoFileSystemUtils.NormalizePath