diff --git a/eng/Signing.props b/eng/Signing.props index 249203f1fee..78734d6e53d 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -69,12 +69,14 @@ + + diff --git a/tests/Infrastructure.Tests/Pipelines/NpmCliPackageTests.cs b/tests/Infrastructure.Tests/Pipelines/NpmCliPackageTests.cs index 34ed923b48f..156f663f0f7 100644 --- a/tests/Infrastructure.Tests/Pipelines/NpmCliPackageTests.cs +++ b/tests/Infrastructure.Tests/Pipelines/NpmCliPackageTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Xml.Linq; using Xunit; namespace Infrastructure.Tests; @@ -200,6 +201,23 @@ public async Task NpmInstallValidationJobsUseExplicitJobsSharedStepsTemplateAndC Assert.Contains("$(NPM_VALIDATION_SUMMARY_OSX_ARTIFACT)", releasePipeline); } + [Fact] + public async Task NpmSigningScopeCoversNestedTarballPayloads() + { + var signingProps = XDocument.Parse(await ReadRepoFileAsync("eng/Signing.props")); + + AssertScopedSigningRule(signingProps, "FileExtensionSignInfo", ".tgz", "LinuxSign500180PGP"); + AssertScopedSigningRule(signingProps, "FileSignInfo", "aspire.js", "MicrosoftDotNet500"); + + // The native npm packages are built from already-signed native archives. + // The main Windows build should only produce the detached npm tarball + // signature; it must still provide scoped rules for nested native + // executables because Arcade resolves nested file certificates inside + // the ItemsToSign collision scope. + AssertScopedSigningRule(signingProps, "FileSignInfo", "aspire.exe", "None"); + AssertScopedSigningRule(signingProps, "FileSignInfo", "aspire", "None"); + } + [Fact] public async Task ReleasePipelinePreflightsScheduledNpmPackagesBeforePublishing() { @@ -265,6 +283,21 @@ private static int CountOccurrences(string value, string substring) return count; } + private static void AssertScopedSigningRule(XDocument document, string elementName, string include, string certificateName) + { + var matchingRules = document + .Descendants(elementName) + .Where(element => + (string?)element.Attribute("CollisionPriorityId") == "AspireCliNpmPackage" && + ((string?)element.Attribute("Include") == include || (string?)element.Attribute("Update") == include) && + (string?)element.Attribute("CertificateName") == certificateName) + .ToArray(); + + Assert.True( + matchingRules.Length == 1, + $"Expected exactly one {elementName} for '{include}' using '{certificateName}' in the AspireCliNpmPackage signing scope, but found {matchingRules.Length}."); + } + private static string FindRepoRoot() { string? current = AppContext.BaseDirectory;