From c5cc972f726a9538966309948c4ec1dafcbb57ce Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 2 Nov 2026 18:24:00 -0500 Subject: [PATCH 1/7] Sanitize remote package name before install operations (#10) --- src/code/InstallHelper.cs | 13 +++++++++++++ src/code/Utils.cs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 45b400489..5d5ec3e8a 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -898,6 +898,12 @@ private ConcurrentDictionary InstallParentAndDependencyPackag var depPkgName = depPkg.Name; var depPkgVersion = Utils.GetFullVersionString(depPkg.Version.ToString(), depPkg.Prerelease); + if (Utils.IsUnsafeRemoteName(depPkgName, "Package name", out ErrorRecord error)) + { + Utils.EnqueueIfNotNull(errorMsgs, error); + return; + } + verboseMsgs.Enqueue($"Installing package '{depPkgName}' version '{depPkgVersion}'"); //Stream responseStream = currentServer.InstallPackage(depPkgName, depPkgVersion, true, out ErrorRecord installNameErrRecord); // add async @@ -939,6 +945,13 @@ private ConcurrentDictionary InstallParentAndDependencyPackag { var pkgToInstallName = pkgToBeInstalled.Name; var pkgToInstallVersion = Utils.GetFullVersionString(pkgToBeInstalled.Version.ToString(), pkgToBeInstalled.Prerelease); + + if (Utils.IsUnsafeRemoteName(pkgToInstallName, "Package name", out ErrorRecord error)) + { + Utils.EnqueueIfNotNull(errorMsgs, error); + return packagesHash; + } + // Runs on worker threads when parent installs are parallelized; use the async overload to avoid cross-thread cmdlet stream writes. // Dispose the stream once done: for local repositories this is a FileStream on the repository's .nupkg and leaving it open locks the file. using Stream responseStream = currentServer.InstallPackageAsync(pkgToInstallName, pkgToInstallVersion, true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs).GetAwaiter().GetResult(); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 3e05f1089..a0a14dce1 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -48,6 +48,12 @@ public enum MetadataFileType public static readonly string[] EmptyStrArray = Array.Empty(); public static readonly char[] WhitespaceSeparator = new char[] { ' ' }; + private static readonly char[] PathTraversalChars = +#if UNIX + new char[] { '\\', '/' }; +#else + new char[] { '\\', '/', ':' }; +#endif public const string PSDataFileExt = ".psd1"; public const string PSScriptFileExt = ".ps1"; private const string ConvertJsonToHashtableScript = @" @@ -1050,6 +1056,36 @@ public static NetworkCredential SetCredentialProviderNetworkCredential( #endregion + #region Sanitization methods + public static bool IsUnsafeRemoteName(string name, string propertyName, out ErrorRecord error) + { + error = null; + + if (string.IsNullOrEmpty(name)) + { + error = new ErrorRecord( + new ArgumentException($"{propertyName} cannot be null or empty."), + "InvalidRemoteName", + ErrorCategory.InvalidArgument, + propertyName); + return true; + } + + if (name.IndexOfAny(PathTraversalChars) >= 0 || string.Equals(name, ".", StringComparison.Ordinal) || string.Equals(name, "..", StringComparison.Ordinal)) + { + error = new ErrorRecord( + new ArgumentException($"{propertyName} contains invalid characters: '{name}'."), + "InvalidRemoteName", + ErrorCategory.InvalidArgument, + propertyName); + return true; + } + + return false; + } + + #endregion + #region Path methods public static string[] GetSubDirectories(string dirPath) From b4285b2b7ea7807106679275eceb9f44078e9fcf Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 2 Nov 2026 18:26:09 -0500 Subject: [PATCH 2/7] Validate XML response from server --- src/code/V2ResponseUtil.cs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/code/V2ResponseUtil.cs b/src/code/V2ResponseUtil.cs index e62d15e77..74828f88f 100644 --- a/src/code/V2ResponseUtil.cs +++ b/src/code/V2ResponseUtil.cs @@ -5,6 +5,7 @@ using NuGet.Versioning; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Xml; @@ -72,15 +73,32 @@ public override IEnumerable ConvertToPSResourceResult(FindResu #region V2 Specific Methods + private static readonly XmlReaderSettings XmlDocumentReaderSettings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, // Disallow any DTD elements + XmlResolver = null, // Do not resolve external links + CheckCharacters = true, + IgnoreComments = true, + IgnoreProcessingInstructions = true, + IgnoreWhitespace = true, + MaxCharactersFromEntities = 1024, + MaxCharactersInDocument = 512 * 1024 * 1024, // 512M characters = 1GB + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None + }; + public XmlNode[] ConvertResponseToXML(string httpResponse) { NuGetVersion emptyVersion = new NuGetVersion("0.0.0.0"); NuGetVersion firstVersion = emptyVersion; NuGetVersion lastVersion = emptyVersion; bool shouldFixOrder = true; - //Create the XmlDocument. XmlDocument doc = new XmlDocument(); - doc.LoadXml(httpResponse); + using (StringReader stringReader = new StringReader(httpResponse)) + using (XmlReader xmlReader = XmlReader.Create(stringReader, XmlDocumentReaderSettings)) + { + doc.Load(xmlReader); + } XmlNodeList entryNode = doc.GetElementsByTagName("entry"); From d8ace080010405ee432bd8f520a7fdb8c3ad631e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 2 Nov 2026 18:27:05 -0500 Subject: [PATCH 3/7] Repository credentials are checked for individual repositories --- src/code/FindHelper.cs | 16 ++++++++-------- src/code/FindPSResource.cs | 11 ++++++++++- src/code/InstallHelper.cs | 12 ++++++------ src/code/InstallPSResource.cs | 9 ++++++++- src/code/PublishPSResource.cs | 9 ++++++++- src/code/SavePSResource.cs | 8 +++++++- src/code/UpdatePSResource.cs | 9 ++++++++- 7 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs index abb8c773f..4b9cdad35 100644 --- a/src/code/FindHelper.cs +++ b/src/code/FindHelper.cs @@ -41,7 +41,7 @@ internal class FindHelper private string[] _tag; private bool _includeDependencies = false; private bool _repositoryNameContainsWildcard = true; - private NetworkCredential _networkCredential; + private NetworkCredential _explicitCredential; // explicitly passed in credential via Credential parameter. // Gets intantiated each time a cmdlet is run. // If running 'Install-PSResource Az, TestModule, NewTestModule', it will contain one parent and its dependencies. @@ -62,7 +62,7 @@ public FindHelper(CancellationToken cancellationToken, PSCmdlet cmdletPassedIn, { _cancellationToken = cancellationToken; _cmdletPassedIn = cmdletPassedIn; - _networkCredential = networkCredential; + _explicitCredential = networkCredential; _packagesFound = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); _knownLatestPkgVersion = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); _type = ResourceType.None; @@ -223,9 +223,9 @@ public IEnumerable FindByResourceName( repositoryNamesToSearch.Add(currentRepository.Name); // Set network credentials via passed in credentials, AzArtifacts CredentialProvider, or SecretManagement. - _networkCredential = currentRepository.SetNetworkCredentials(_networkCredential, _cmdletPassedIn); + NetworkCredential repoCredential = currentRepository.SetNetworkCredentials(_explicitCredential, _cmdletPassedIn); - ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, _networkCredential); + ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, repoCredential); if (currentServer == null) { // this indicates that PSRepositoryInfo.APIVersion = PSRepositoryInfo.APIVersion.unknown @@ -411,9 +411,9 @@ public IEnumerable FindByCommandOrDscResource( repositoryNamesToSearch.Add(currentRepository.Name); // Set network credentials via passed in credentials, AzArtifacts CredentialProvider, or SecretManagement. - _networkCredential = currentRepository.SetNetworkCredentials(_networkCredential, _cmdletPassedIn); + NetworkCredential repoCredential = currentRepository.SetNetworkCredentials(_explicitCredential, _cmdletPassedIn); - ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, _networkCredential); + ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, repoCredential); if (currentServer == null) { // this indicates that PSRepositoryInfo.APIVersion = PSRepositoryInfo.APIVersion.unknown @@ -618,9 +618,9 @@ public IEnumerable FindByTag( repositoryNamesToSearch.Add(currentRepository.Name); // Set network credentials via passed in credentials, AzArtifacts CredentialProvider, or SecretManagement. - _networkCredential = currentRepository.SetNetworkCredentials(_networkCredential, _cmdletPassedIn); + NetworkCredential repoCredential = currentRepository.SetNetworkCredentials(_explicitCredential, _cmdletPassedIn); - ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, _networkCredential); + ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, repoCredential); if (currentServer == null) { // this indicates that PSRepositoryInfo.APIVersion = PSRepositoryInfo.APIVersion.unknown diff --git a/src/code/FindPSResource.cs b/src/code/FindPSResource.cs index a0ef9f496..5c2b1e889 100644 --- a/src/code/FindPSResource.cs +++ b/src/code/FindPSResource.cs @@ -18,7 +18,9 @@ namespace Microsoft.PowerShell.PSResourceGet.Cmdlets /// [Cmdlet(VerbsCommon.Find, "PSResource", - DefaultParameterSetName = NameParameterSet)] + DefaultParameterSetName = NameParameterSet, + SupportsShouldProcess = true, + ConfirmImpact = ConfirmImpact.Medium)] [Alias("fdres")] [OutputType(typeof(PSResourceInfo), typeof(PSCommandResourceInfo))] public sealed class FindPSResource : PSCmdlet @@ -99,6 +101,7 @@ public sealed class FindPSResource : PSCmdlet /// /// Specifies optional credentials to be used when accessing a repository. + /// Confirmation is requested before using the same credential when zero or multiple repository names are specified. /// [Parameter()] public PSCredential Credential { get; set; } @@ -142,6 +145,12 @@ protected override void EndProcessing() protected override void ProcessRecord() { + if (Credential != null && (Repository == null || Repository.Length != 1) && + !ShouldProcess("Potentially search multiple repositories using the supplied Credential")) + { + return; + } + switch (ParameterSetName) { case NameParameterSet: diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 5d5ec3e8a..f235696f4 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -56,7 +56,7 @@ internal class InstallHelper List _pathsToSearch; List _pkgNamesToInstall; private string _tmpPath; - private NetworkCredential _networkCredential; + private NetworkCredential _explicitCredential; private HashSet _packagesOnMachine; private FindHelper _findHelper; @@ -69,9 +69,9 @@ public InstallHelper(PSCmdlet cmdletPassedIn, NetworkCredential networkCredentia CancellationTokenSource source = new(); _cancellationToken = source.Token; _cmdletPassedIn = cmdletPassedIn; - _networkCredential = networkCredential; + _explicitCredential = networkCredential; - _findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn, _networkCredential); + _findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn, _explicitCredential); } /// @@ -269,7 +269,7 @@ private List ProcessRepositories( var yesToAll = false; var noToAll = false; - var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn, _networkCredential); + var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn, _explicitCredential); List repositoryNamesToSearch = new(); bool sourceTrusted = false; @@ -295,9 +295,9 @@ private List ProcessRepositories( sourceTrusted = currentRepository.Trusted || trustRepository; // Set network credentials via passed in credentials, AzArtifacts CredentialProvider, or SecretManagement. - _networkCredential = currentRepository.SetNetworkCredentials(_networkCredential, _cmdletPassedIn); + NetworkCredential repoCredential = currentRepository.SetNetworkCredentials(_explicitCredential, _cmdletPassedIn); - ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, _networkCredential); + ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, repoCredential); if (currentServer == null) { diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 7beb61a56..b5459cdf9 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -20,7 +20,8 @@ namespace Microsoft.PowerShell.PSResourceGet.Cmdlets [Cmdlet(VerbsLifecycle.Install, "PSResource", DefaultParameterSetName = "NameParameterSet", - SupportsShouldProcess = true)] + SupportsShouldProcess = true, + ConfirmImpact = ConfirmImpact.Medium)] [Alias("isres")] public sealed class InstallPSResource : PSCmdlet @@ -277,6 +278,12 @@ protected override void BeginProcessing() protected override void ProcessRecord() { + if (Credential != null && (Repository == null || Repository.Length != 1) && + !ShouldProcess("Potentially search multiple repositories for install operation using the supplied Credential")) + { + return; + } + switch (ParameterSetName) { case NameParameterSet: diff --git a/src/code/PublishPSResource.cs b/src/code/PublishPSResource.cs index cbf558482..15680d316 100644 --- a/src/code/PublishPSResource.cs +++ b/src/code/PublishPSResource.cs @@ -15,7 +15,8 @@ namespace Microsoft.PowerShell.PSResourceGet.Cmdlets /// [Cmdlet(VerbsData.Publish, "PSResource", - SupportsShouldProcess = true)] + SupportsShouldProcess = true, + ConfirmImpact = ConfirmImpact.Medium)] [Alias("pbres")] public sealed class PublishPSResource : PSCmdlet { @@ -148,6 +149,12 @@ protected override void BeginProcessing() _networkCredential = Credential != null ? new NetworkCredential(Credential.UserName, Credential.Password) : null; + if (Credential != null && string.IsNullOrEmpty(Repository) && + !ShouldProcess("Use repository registered with highest priority for publish operation using the supplied Credential")) + { + return; + } + // Create the repository store file (PSResourceRepository.xml) if it does not already exist // This is to create a better experience for those who have just installed v3 and want to get up and running quickly RepositorySettings.CheckRepositoryStore(); diff --git a/src/code/SavePSResource.cs b/src/code/SavePSResource.cs index 2a20aa021..d109e0a81 100644 --- a/src/code/SavePSResource.cs +++ b/src/code/SavePSResource.cs @@ -16,7 +16,7 @@ namespace Microsoft.PowerShell.PSResourceGet.Cmdlets /// The Save-PSResource cmdlet saves a resource to a machine. /// It returns nothing. /// - [Cmdlet(VerbsData.Save, "PSResource", DefaultParameterSetName = "IncludeXmlParameterSet", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low)] + [Cmdlet(VerbsData.Save, "PSResource", DefaultParameterSetName = "IncludeXmlParameterSet", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium)] [Alias("svres")] public sealed class SavePSResource : PSCmdlet { @@ -202,6 +202,12 @@ protected override void BeginProcessing() protected override void ProcessRecord() { + if (Credential != null && (Repository == null || Repository.Length != 1) && + !ShouldProcess("Potentially search multiple repositories for save operation using the supplied Credential")) + { + return; + } + switch (ParameterSetName) { case AsNupkgParameterSet: diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 4d44d2b52..13b841a72 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -19,7 +19,8 @@ namespace Microsoft.PowerShell.PSResourceGet.Cmdlets /// [Cmdlet(VerbsData.Update, "PSResource", - SupportsShouldProcess = true)] + SupportsShouldProcess = true, + ConfirmImpact = ConfirmImpact.Medium)] [Alias("udres")] public sealed class UpdatePSResource : PSCmdlet { @@ -173,6 +174,12 @@ protected override void BeginProcessing() protected override void ProcessRecord() { + if (Credential != null && (Repository == null || Repository.Length != 1) && + !ShouldProcess("Potentially search multiple repositories for update operation using the supplied Credential")) + { + return; + } + // determine/parse out Version param if (!Utils.TryGetVersionType( version: Version, From 1f6627346b2d15c9cc50089d9493d0447b44358e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 3 Nov 2026 19:42:58 +0000 Subject: [PATCH 4/7] Merged PR 42524: Update ModuleVersion in module manifest for 1.3.0 release Update ModuleVersion in module manifest for 1.3.0 release ---- #### AI description (iteration 1) #### PR Classification Release preparation (module manifest version/release notes update). #### PR Summary Updates the module manifest to finalize the 1.3.0 release by removing the prerelease tag and adding release notes. - `src/Microsoft.PowerShell.PSResourceGet.psd1`: commented out the `Prerelease = 'rc2'` tag, marking the module as a stable release. - `src/Microsoft.PowerShell.PSResourceGet.psd1`: added a new `## 1.3.0` section header in `ReleaseNotes` above the previous `1.3.0-rc2` notes. --- src/Microsoft.PowerShell.PSResourceGet.psd1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.PSResourceGet.psd1 b/src/Microsoft.PowerShell.PSResourceGet.psd1 index eec729108..6a30d5d96 100644 --- a/src/Microsoft.PowerShell.PSResourceGet.psd1 +++ b/src/Microsoft.PowerShell.PSResourceGet.psd1 @@ -50,7 +50,7 @@ 'usres') PrivateData = @{ PSData = @{ - Prerelease = 'rc2' + # Prerelease = '' Tags = @('PackageManagement', 'PSEdition_Desktop', 'PSEdition_Core', @@ -60,6 +60,8 @@ ProjectUri = 'https://go.microsoft.com/fwlink/?LinkId=828955' LicenseUri = 'https://go.microsoft.com/fwlink/?LinkId=829061' ReleaseNotes = @' +## 1.3.0 + ## 1.3.0-rc2 ### New Features From ab554c5be6711a4fb206fc7c27b64acaee90a81c Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 5 Nov 2026 15:17:46 -0500 Subject: [PATCH 5/7] Add XML validation when parsing XML response from nuspec and NuGetServer (#15) --- src/code/LocalServerApiCalls.cs | 20 +++++++++++++++++++- src/code/NuGetServerAPICalls.cs | 19 ++++++++++++++++++- src/code/NuGetServerResponseUtil.cs | 21 +++++++++++++++++++-- src/code/Utils.cs | 20 +++++++++++++++++++- src/code/V2ResponseUtil.cs | 1 - src/code/V2ServerAPICalls.cs | 19 ++++++++++++++++++- 6 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/code/LocalServerApiCalls.cs b/src/code/LocalServerApiCalls.cs index 1ebb72dcb..6d43909fd 100644 --- a/src/code/LocalServerApiCalls.cs +++ b/src/code/LocalServerApiCalls.cs @@ -1037,6 +1037,19 @@ private NuGetVersion GetInfoFromFileName(string packageFullName, string packageN return nugetVersion; } + private static readonly XmlReaderSettings XmlDocumentReaderSettings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, // Disallow any DTD elements + XmlResolver = null, // Do not resolve external links + CheckCharacters = true, + IgnoreComments = true, + IgnoreProcessingInstructions = true, + MaxCharactersFromEntities = 1024, + MaxCharactersInDocument = 512 * 1024 * 1024, // 512M characters = 1GB + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None + }; + /// /// Method that loads file content into XMLDocument. Used when reading .nuspec file. /// @@ -1045,7 +1058,12 @@ private XmlDocument LoadXmlDocument(string filePath, out ErrorRecord errRecord) errRecord = null; XmlDocument doc = new XmlDocument(); doc.PreserveWhitespace = true; - try { doc.Load(filePath); } + try { + using (XmlReader xmlReader = XmlReader.Create(filePath, XmlDocumentReaderSettings)) + { + doc.Load(xmlReader); + } + } catch (Exception e) { errRecord = new ErrorRecord( diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs index 4786d150c..b79ffad5e 100644 --- a/src/code/NuGetServerAPICalls.cs +++ b/src/code/NuGetServerAPICalls.cs @@ -1185,6 +1185,19 @@ private Stream InstallVersionAsync(string packageName, string version, Concurren return response.ReadAsStreamAsync().Result; } + private static readonly XmlReaderSettings XmlDocumentReaderSettings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, // Disallow any DTD elements + XmlResolver = null, // Do not resolve external links + CheckCharacters = true, + IgnoreComments = true, + IgnoreProcessingInstructions = true, + MaxCharactersFromEntities = 1024, + MaxCharactersInDocument = 512 * 1024 * 1024, // 512M characters = 1GB + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None + }; + /// /// Helper method that makes gets 'count' property from http response string. /// The count property is used to determine the number of total results found (for pagination). @@ -1199,7 +1212,11 @@ public int GetCountFromResponse(string httpResponse, out ErrorRecord errRecord) try { - doc.LoadXml(httpResponse); + using (StringReader stringReader = new StringReader(httpResponse)) + using (XmlReader xmlReader = XmlReader.Create(stringReader, XmlDocumentReaderSettings)) + { + doc.Load(xmlReader); + } } catch (XmlException e) { diff --git a/src/code/NuGetServerResponseUtil.cs b/src/code/NuGetServerResponseUtil.cs index 19b7499d4..3e45d382a 100644 --- a/src/code/NuGetServerResponseUtil.cs +++ b/src/code/NuGetServerResponseUtil.cs @@ -3,6 +3,7 @@ using Microsoft.PowerShell.PSResourceGet.UtilClasses; using System; +using System.IO; using System.Collections.Generic; using System.Xml; @@ -69,11 +70,27 @@ public override IEnumerable ConvertToPSResourceResult(FindResu #region NuGet.Server Specific Methods - public XmlNode[] ConvertResponseToXML(string httpResponse) { + private static readonly XmlReaderSettings XmlDocumentReaderSettings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, // Disallow any DTD elements + XmlResolver = null, // Do not resolve external links + CheckCharacters = true, + IgnoreComments = true, + IgnoreProcessingInstructions = true, + MaxCharactersFromEntities = 1024, + MaxCharactersInDocument = 512 * 1024 * 1024, // 512M characters = 1GB + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None + }; + public XmlNode[] ConvertResponseToXML(string httpResponse) { //Create the XmlDocument. XmlDocument doc = new XmlDocument(); - doc.LoadXml(httpResponse); + using (StringReader stringReader = new StringReader(httpResponse)) + using (XmlReader xmlReader = XmlReader.Create(stringReader, XmlDocumentReaderSettings)) + { + doc.Load(xmlReader); + } XmlNodeList elemList = doc.GetElementsByTagName("m:properties"); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index a0a14dce1..aa1cf49b8 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -2216,6 +2216,19 @@ public static Hashtable GetMetadataFromNuspec(string nuspecFilePath, PSCmdlet cm return nuspecHashtable; } + private static readonly XmlReaderSettings XmlDocumentReaderSettings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, // Disallow any DTD elements + XmlResolver = null, // Do not resolve external links + CheckCharacters = true, + IgnoreComments = true, + IgnoreProcessingInstructions = true, + MaxCharactersFromEntities = 1024, + MaxCharactersInDocument = 512 * 1024 * 1024, // 512M characters = 1GB + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None + }; + /// /// Method that loads file content into XMLDocument. Used when reading .nuspec file. /// @@ -2224,7 +2237,12 @@ public static XmlDocument LoadXmlDocument(string filePath, PSCmdlet cmdletPassed errRecord = null; XmlDocument doc = new XmlDocument(); doc.PreserveWhitespace = true; - try { doc.Load(filePath); } + try { + using (XmlReader xmlReader = XmlReader.Create(filePath, XmlDocumentReaderSettings)) + { + doc.Load(xmlReader); + } + } catch (Exception e) { errRecord = new ErrorRecord( diff --git a/src/code/V2ResponseUtil.cs b/src/code/V2ResponseUtil.cs index 74828f88f..010111332 100644 --- a/src/code/V2ResponseUtil.cs +++ b/src/code/V2ResponseUtil.cs @@ -80,7 +80,6 @@ public override IEnumerable ConvertToPSResourceResult(FindResu CheckCharacters = true, IgnoreComments = true, IgnoreProcessingInstructions = true, - IgnoreWhitespace = true, MaxCharactersFromEntities = 1024, MaxCharactersInDocument = 512 * 1024 * 1024, // 512M characters = 1GB ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, diff --git a/src/code/V2ServerAPICalls.cs b/src/code/V2ServerAPICalls.cs index 6ea3361d7..8046d116c 100644 --- a/src/code/V2ServerAPICalls.cs +++ b/src/code/V2ServerAPICalls.cs @@ -1954,6 +1954,19 @@ private string GetTypeFilterForRequest(ResourceType type) { return typeFilterPart; } + private static readonly XmlReaderSettings XmlDocumentReaderSettings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, // Disallow any DTD elements + XmlResolver = null, // Do not resolve external links + CheckCharacters = true, + IgnoreComments = true, + IgnoreProcessingInstructions = true, + MaxCharactersFromEntities = 1024, + MaxCharactersInDocument = 512 * 1024 * 1024, // 512M characters = 1GB + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None + }; + /// /// Helper method that makes gets 'count' property from http response string. /// The count property is used to determine the number of total results found (for pagination). @@ -1968,7 +1981,11 @@ public int GetCountFromResponse(string httpResponse, out string errStr) try { - doc.LoadXml(httpResponse); + using (StringReader stringReader = new StringReader(httpResponse)) + using (XmlReader xmlReader = XmlReader.Create(stringReader, XmlDocumentReaderSettings)) + { + doc.Load(xmlReader); + } bool countSearchSucceeded = false; XmlNodeList elemList = doc.GetElementsByTagName("m:count"); From 9c0152875286bb0a295cbe31f04006a5998e333a Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 5 Nov 2026 15:18:14 -0500 Subject: [PATCH 6/7] Throw error if Publish-PSResource Cred confirm is denied (#17) --- src/code/PublishPSResource.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/code/PublishPSResource.cs b/src/code/PublishPSResource.cs index 15680d316..60148b559 100644 --- a/src/code/PublishPSResource.cs +++ b/src/code/PublishPSResource.cs @@ -152,7 +152,11 @@ protected override void BeginProcessing() if (Credential != null && string.IsNullOrEmpty(Repository) && !ShouldProcess("Use repository registered with highest priority for publish operation using the supplied Credential")) { - return; + ThrowTerminatingError(new ErrorRecord( + new PSInvalidOperationException("Confirm operation when using Credential parameter with no explicit Repository specified, or re-run command with '-Confirm:$false'."), + "CredentialWithoutRepositoryConfirmationDenied", + ErrorCategory.InvalidOperation, + this)); } // Create the repository store file (PSResourceRepository.xml) if it does not already exist From fa3e56b8970fd1f12a63627c7936839a684c4078 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 5 Nov 2026 20:30:31 +0000 Subject: [PATCH 7/7] Merged PR 42596: Update ModuleVersion in module manifest for 1.3.1 release Update ModuleVersion in module manifest for 1.3.1 release --- src/Microsoft.PowerShell.PSResourceGet.psd1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.PSResourceGet.psd1 b/src/Microsoft.PowerShell.PSResourceGet.psd1 index 6a30d5d96..4c8994e7b 100644 --- a/src/Microsoft.PowerShell.PSResourceGet.psd1 +++ b/src/Microsoft.PowerShell.PSResourceGet.psd1 @@ -4,7 +4,7 @@ @{ RootModule = './Microsoft.PowerShell.PSResourceGet.dll' NestedModules = @('./Microsoft.PowerShell.PSResourceGet.psm1') - ModuleVersion = '1.3.0' + ModuleVersion = '1.3.1' CompatiblePSEditions = @('Core', 'Desktop') GUID = 'e4e0bda1-0703-44a5-b70d-8fe704cd0643' Author = 'Microsoft Corporation' @@ -60,6 +60,8 @@ ProjectUri = 'https://go.microsoft.com/fwlink/?LinkId=828955' LicenseUri = 'https://go.microsoft.com/fwlink/?LinkId=829061' ReleaseNotes = @' +## 1.3.1 + ## 1.3.0 ## 1.3.0-rc2