From edcb598f44899b62f07dfe51ac78d55e02bfb35c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 14 May 2026 15:46:36 -0500 Subject: [PATCH 1/2] Block insecure HTTP Maven repository URLs unless AllowInsecureHttp is set When a `` item specifies an `http://` repository URL, the build now fails with error XA4251 unless `AllowInsecureHttp="true"` metadata is explicitly set on the item. HTTPS URLs are unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Properties/Resources.Designer.cs | 9 +++++ .../Properties/Resources.resx | 5 +++ .../Tasks/MavenDownload.cs | 6 ++++ .../Tasks/MavenDownloadTests.cs | 35 +++++++++++++++++++ 4 files changed, 55 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs index ff837114b9d..4b9f5d53407 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs @@ -1462,6 +1462,15 @@ public static string XA4250 { return ResourceManager.GetString("XA4250", resourceCulture); } } + + /// + /// Looks up a localized string similar to Insecure HTTP Maven repository URL '{0}' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check.. + /// + public static string XA4251 { + get { + return ResourceManager.GetString("XA4251", resourceCulture); + } + } /// /// Looks up a localized string similar to Native library '{0}' will not be bundled because it has an unsupported ABI. Move this file to a directory with a valid Android ABI name such as 'libs/armeabi-v7a/'.. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index 214d4b89df3..05d61cfa2d7 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -1079,6 +1079,11 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS Manifest-referenced type '{0}' was not found in any scanned assembly. It may be a framework type. The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + + Insecure HTTP Maven repository URL '{0}' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL Command '{0}' failed.\n{1} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs index 0521682daf6..0cb3b59241b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs @@ -147,6 +147,12 @@ public async override System.Threading.Tasks.Task RunTaskAsync () }; if (repo is null && type.StartsWith ("http", StringComparison.OrdinalIgnoreCase)) { + if (type.StartsWith ("http://", StringComparison.OrdinalIgnoreCase) && + !string.Equals (item.GetMetadataOrDefault ("AllowInsecureHttp", "false"), "true", StringComparison.OrdinalIgnoreCase)) { + Log.LogCodedError ("XA4251", Properties.Resources.XA4251, type); + return null; + } + using var hasher = SHA256.Create (); var hash = hasher.ComputeHash (Encoding.UTF8.GetBytes (type)); var cache_name = Convert.ToBase64String (hash); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs index 55d81bb69c3..dd872091211 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using NUnit.Framework; @@ -72,6 +73,40 @@ public async Task UnknownRepository () Assert.AreEqual ("Unknown Maven repository: 'bad-repo'.", engine.Errors [0].Message); } + [Test] + public async Task InsecureHttpRepository_Blocked () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownload { + BuildEngine = engine, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material:material", "1.0.0", "http://repo.example.com/maven2/")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Insecure HTTP Maven repository URL 'http://repo.example.com/maven2/' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp=\"true\" metadata on the item to override this check.", engine.Errors [0].Message); + } + + [Test] + public async Task InsecureHttpRepository_AllowedWithOptIn () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var item = CreateMavenTaskItem ("com.example:dummy", "1.0.0", "http://repo.example.com/maven2/"); + item.SetMetadata ("AllowInsecureHttp", "true"); + + var task = new MavenDownload { + BuildEngine = engine, + MavenCacheDirectory = Path.GetTempPath (), + AndroidMavenLibraries = [item], + }; + + await task.RunTaskAsync (); + + // Should not have the XA4251 insecure HTTP error; it will fail with a download error instead + Assert.IsFalse (engine.Errors.Any (e => e.Code == "XA4251"), "Should not have XA4251 error when AllowInsecureHttp is set"); + } + [Test] public async Task UnknownArtifact () { From 6bc6fb813565a7e9912ba4ac37d6e1a520e3b4ba Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 15 May 2026 08:33:50 -0500 Subject: [PATCH 2/2] Address code review feedback - Use Uri.TryCreate + scheme check instead of string prefix matching to properly handle all HTTP URI variations (defense in depth). - Make opt-in test assertion positive: assert XA4236 download error instead of just checking absence of XA4252. - Add XA4252 error documentation (xa4252.md) and update message index. - Document AllowInsecureHttp metadata in both build-items.md files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../msbuild-reference/build-items.md | 1 + .../docs-mobile/building-apps/build-items.md | 3 ++ Documentation/docs-mobile/messages/index.md | 1 + Documentation/docs-mobile/messages/xa4252.md | 43 +++++++++++++++++++ .../Tasks/MavenDownload.cs | 5 ++- .../Tasks/MavenDownloadTests.cs | 6 +-- 6 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 Documentation/docs-mobile/messages/xa4252.md diff --git a/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md b/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md index 12c9c97eef5..14ea33bfb9c 100644 --- a/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md +++ b/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md @@ -173,6 +173,7 @@ hosted in Maven. | - | - | | Version | Required string. The version of the Java library that should be downloaded from Maven. Defaults to `true`. | | Repository | Optional string. Specifies Maven repository to use. Supported values are `Central`, `Google`, or an `https` URL to a Maven repository. Defaults to `Central`. | +| AllowInsecureHttp | Optional boolean. When `Repository` is an `http://` URL, this must be set to `true` to allow the insecure connection. Defaults to `false`. Using HTTPS is strongly recommended for supply-chain security. | | Bind | Optional boolean. Specifies whether the Java library should have a C# binding generated for it. Defaults to `true`. | | Pack | Optional boolean. Specifies whether the Java library should be included in the project output. Defaults to `true`. | diff --git a/Documentation/docs-mobile/building-apps/build-items.md b/Documentation/docs-mobile/building-apps/build-items.md index a978ac0e318..46bd157c07b 100644 --- a/Documentation/docs-mobile/building-apps/build-items.md +++ b/Documentation/docs-mobile/building-apps/build-items.md @@ -296,6 +296,9 @@ The following MSBuild metadata are supported: - `%(Version)`: Required version of the Java library referenced by `%(Include)`. - `%(Repository)`: Optional Maven repository to use. Supported values are `Central` (default), `Google`, or an `https` URL to a Maven repository. +- `%(AllowInsecureHttp)`: Optional boolean. When `%(Repository)` is an `http://` URL, this must be + set to `true` to allow the insecure connection. Defaults to `false`. Using HTTPS is strongly + recommended for supply-chain security. The `` item is translated to [`AndroidLibrary`](#androidlibrary), so any metadata supported by diff --git a/Documentation/docs-mobile/messages/index.md b/Documentation/docs-mobile/messages/index.md index 14d5a6c0442..cef18eb294e 100644 --- a/Documentation/docs-mobile/messages/index.md +++ b/Documentation/docs-mobile/messages/index.md @@ -208,6 +208,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + [XA4248](xa4248.md): Could not find NuGet package '{nugetId}' version '{version}' in lock file. Ensure NuGet Restore has run since this `` was added. + [XA4235](xa4249.md): Maven artifact specification '{artifact}' is invalid. The correct format is 'group_id:artifact_id:version'. + [XA4250](xa4250.md): Manifest-referenced type '{type}' was not found in any scanned assembly. It may be a framework type. ++ [XA4252](xa4252.md): Insecure HTTP Maven repository URL '{url}' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check. + XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI. + [XA4301](xa4301.md): Apk already contains the item `xxx`. + [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex} diff --git a/Documentation/docs-mobile/messages/xa4252.md b/Documentation/docs-mobile/messages/xa4252.md new file mode 100644 index 00000000000..72effe92fac --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4252.md @@ -0,0 +1,43 @@ +--- +title: .NET for Android error XA4252 +description: XA4252 error code +ms.date: 05/15/2026 +f1_keywords: + - "XA4252" +--- + +# .NET for Android error XA4252 + +## Example message + +``` +error XA4252: Insecure HTTP Maven repository URL 'http://repo.example.com/maven2/' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check. +``` + +## Issue + +An `` item specifies a Maven repository URL using `http://` instead of `https://`. +Downloading artifacts over plain HTTP is a security risk because the connection is not encrypted and is +vulnerable to man-in-the-middle attacks and supply-chain compromise. + +This check aligns with default behavior in Gradle (`allowInsecureProtocol`) and Maven (`http://*`) +for defense in depth and supply-chain hardening. + +## Solution + +Use an HTTPS URL for the Maven repository whenever possible: + +```xml + + + +``` + +If the repository does not support HTTPS and you understand the security implications, +set `AllowInsecureHttp="true"` to explicitly opt in to insecure HTTP: + +```xml + + + +``` diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs index a16110e1245..96eced16660 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs @@ -146,8 +146,9 @@ public async override System.Threading.Tasks.Task RunTaskAsync () _ => null }; - if (repo is null && type.StartsWith ("http", StringComparison.OrdinalIgnoreCase)) { - if (type.StartsWith ("http://", StringComparison.OrdinalIgnoreCase) && + if (repo is null && Uri.TryCreate (type, UriKind.Absolute, out var uri) && + (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) { + if (uri.Scheme == Uri.UriSchemeHttp && !string.Equals (item.GetMetadataOrDefault ("AllowInsecureHttp", "false"), "true", StringComparison.OrdinalIgnoreCase)) { Log.LogCodedError ("XA4252", Properties.Resources.XA4252, type); return null; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs index 0bb5f2c9116..5f17e684cd6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using NUnit.Framework; @@ -103,8 +102,9 @@ public async Task InsecureHttpRepository_AllowedWithOptIn () await task.RunTaskAsync (); - // Should not have the XA4252 insecure HTTP error; it will fail with a download error instead - Assert.IsFalse (engine.Errors.Any (e => e.Code == "XA4252"), "Should not have XA4252 error when AllowInsecureHttp is set"); + // Should bypass the XA4252 insecure HTTP check and attempt the download, which fails with XA4236 + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("XA4236", engine.Errors [0].Code, "Expected a download error (XA4236), not a security error (XA4252)"); } [Test]