Open Source Security

PowerShell Module Supply Chain Security

PowerShell modules are a supply chain people forget exists, and the trust model is weaker than NuGet's. Here is why that matters.

Shadab Khan
Security Engineer
7 min read

PowerShell modules live in a part of the supply chain that almost nobody thinks about until something goes wrong. The modules on PowerShell Gallery (PSGallery) are not subject to the same signing and attestation discipline that NuGet packages on nuget.org receive, even though both are operated by Microsoft. And PowerShell's execution model (trusted by default on many Windows hosts, often run with elevated privilege, very flexible about dynamic code loading) makes a compromised module more dangerous than a compromised NuGet package in several practical ways. I want to walk through the actual trust model and the failure modes that have mattered, because I keep finding customers who inherit a PowerShell supply chain posture from 2016 and have not revisited it since.

What PSGallery actually is

PowerShell Gallery is a module hosting service operated by Microsoft, reachable via Install-Module in Windows PowerShell 5.1 and PowerShell 7.x. Modules are submitted by individual authors or organizations, versioned using SemVer, and downloadable via Install-Module or the newer Install-PSResource from the PSResourceGet module (formerly PowerShellGet v3). The common API is NuGet v2 underneath, which means PSGallery packages are technically NuGet packages with .nupkg extension and some PowerShell-specific metadata in the manifest.

Where the trust model diverges from nuget.org is that PSGallery does not repository-sign modules. A module on PSGallery has whatever signatures its author chose to apply and nothing more. If the author signed the module with an Authenticode certificate, consumers can verify that. If the author did not sign, which is the common case for community modules, consumers have no cryptographic guarantee that the module they installed is the one the author uploaded or that the author is who they claim to be.

This is not unique in the open-source ecosystem; it is closer to the npm or PyPI trust model than to the nuget.org model. The gap between "what PSGallery looks like" and "what nuget.org looks like" catches people out because they assume parity.

The InstallPolicy confusion

PowerShellGet's Install-Module cmdlet has an InstallationPolicy concept that defaults to Untrusted for PSGallery. When you run Install-Module for the first time, PowerShell prompts you asking whether you trust the repository. If you choose yes, it marks PSGallery as trusted, and subsequent installs proceed without prompting.

The common user experience is to click yes once, forget about it, and thereafter install modules without any trust evaluation. The trust flag is per-user and per-host, so a prompt on a developer's laptop does not trust PSGallery on a CI agent, but in practice CI agents get set up once with PSGallery trusted and stay that way.

The gap is that "trusted" in this model means "do not prompt me" not "verify signatures." A module from a trusted repository is installed without signature checks regardless of its signature state, unless the host's execution policy also requires signed scripts, and even then the check is on the module files rather than the package manifest.

The execution policy interaction

PowerShell execution policy is a separate control that governs whether scripts can run at all. The values are Restricted, AllSigned, RemoteSigned, Unrestricted, and Bypass. The default on modern Windows clients is Restricted; on servers it is often RemoteSigned. CI agents are commonly set to Bypass to avoid any prompting.

An AllSigned policy requires all scripts, including those loaded from modules, to be signed with a trusted publisher certificate. This is the closest PowerShell gets to NuGet-style signature enforcement, and it is strong but disruptive. Most community modules on PSGallery are not signed by a publisher that would be in a Windows host's trusted publisher store, so AllSigned effectively prevents installing most PSGallery modules without manual trust decisions for each publisher.

The middle-ground policy most shops use is RemoteSigned, which requires signatures on downloaded scripts but not on locally created ones. For PSGallery modules, RemoteSigned is slightly weaker than it sounds because once a module is installed to a local path it is no longer "remote," so its scripts can be loaded without signature checks on subsequent imports.

The attack patterns that have mattered

A few incidents worth pointing at. In 2021 researchers published modules on PSGallery with names similar to legitimate administrative modules (typosquatting), and some of those modules did telemetry on install. PSGallery removed them after the disclosure, but the mechanism of name squatting against common module names remained a concern.

In July 2023 a researcher at ReversingLabs published findings about PSGallery package names that could be spoofed via unicode look-alikes, which Microsoft acknowledged and began remediating. The fix involved tightening the name uniqueness rules on publish and rejecting certain unicode character classes.

The Unitrix and similar hidden-character naming attacks on NuGet-style registries have recurred across ecosystems. PSGallery's guardrails against them are reasonable in 2024 but are not as mature as nuget.org's because the operational attention has historically been lower.

The "Install-Module in CI" anti-pattern

A specific pattern I flag on every audit is CI pipelines that run Install-Module -Force -Scope CurrentUser SomeModule as part of every build. The -Force flag skips the trust prompt and the confirmation. The module comes from PSGallery, which is trusted, and the version is not pinned in most cases, so every build pulls the current version.

This is a supply chain risk because any compromise of the module's author account translates directly into code execution on the CI agent on the next build. Even without compromise, it is a reproducibility risk because module versions drift over time. The mitigation is to either check in the module alongside the repository (not ideal for large modules), mirror the specific versions to an internal PowerShell repository, or use Save-Module with a pinned version to a path that is checked in or cached in a controlled way.

PSResourceGet and the modern path

Microsoft shipped PSResourceGet (PowerShellGet v3) as generally available in 2023 with improved semantics around package integrity and signature verification. Install-PSResource supports better version pinning, supports authenticated private repositories more robustly, and is slowly replacing Install-Module as the recommended interface. PowerShell 7.4 ships with PSResourceGet preinstalled; earlier versions require an explicit Install-Module Microsoft.PowerShell.PSResourceGet.

The migration from PowerShellGet v2 to PSResourceGet is an opportunity to revisit the supply chain posture. PSResourceGet's -InstallPolicy behaviour is similar but less surprising, and its support for private repositories with credential providers is materially better.

Private PowerShell repositories

Enterprise shops often publish their own PowerShell modules to an internal repository. The options are Azure Artifacts (which supports PowerShell modules as a special case of NuGet), Artifactory (which has dedicated PSGallery repository types), Nexus, and ProGet. The security posture question for these is the same as for NuGet private feeds: who can publish, who can consume, are signatures enforced, are upstream proxies configured safely.

The specific PowerShell concern is that modules often contain scripts that are executed as part of the install process ($PSModuleAutoLoadingPreference and module manifest fields that can trigger code execution on module load). This makes a compromised internal module more dangerous than an equivalent NuGet package, because the NuGet install path does not execute arbitrary code while the PowerShell module load path can.

How Safeguard Helps

Safeguard inventories the PowerShell modules imported across a tenant, tracks their PSGallery status and publisher signatures, and flags unsigned modules used in CI or operational paths. It detects the Install-Module -Force anti-pattern in pipeline definitions and surfaces it as a finding tied to the repository and pipeline owner. For organisations running private PowerShell repositories the platform evaluates access controls, signature enforcement, and upstream proxy configuration alongside the equivalent NuGet feed posture. When a PSGallery module the tenant depends on is delisted or flagged the platform routes the alert to every pipeline or host that imports it.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.