Skip to content

PSAuthorizationManager should check LocalMachine\TrustedPublisher for AllSigned execution #27556

@mthompson83

Description

@mthompson83

Prerequisites

Steps to reproduce

Summary of the new feature / enhancement

PSAuthorizationManager.IsTrustedPublisher() only checks CurrentUser\TrustedPublisher (StoreLocation.CurrentUser). It does not consult LocalMachine\TrustedPublisher. This causes validly signed scripts to be blocked under GPO-enforced AllSigned when:

  • The process runs as LocalSystem or another service account (whose CurrentUser\TrustedPublisher is not the enterprise-managed store and is typically empty/unpopulated)
  • A Group Policy enforces AllSigned at MachinePolicy or UserPolicy scope
  • An administrator or GPO has deployed the signing certificate to LocalMachine\TrustedPublisher

PowerShell treats the publisher as unknown, calls AuthenticodePrompt(), and because the host is non-interactive (host == null || host.UI == null), returns DoNotRun — blocking the script with AuthorizationManager check failed.

This bug also exists in Windows PowerShell 5.1

The same IsTrustedPublisher() code path exists in Windows PowerShell 5.1 (System.Management.Automation.dll). The behavior is identical — only CurrentUser\TrustedPublisher is consulted. Since PS 5.1 is in maintenance mode, we understand it is unlikely to receive this fix, but the issue should be documented here as it affects the majority of enterprise Windows systems that still rely on PS 5.1 for automation and VM extension execution.

This bug prevents customers from securing their systems

Enterprise security teams deploy AllSigned execution policy via Group Policy specifically to prevent execution of unsigned or untrusted scripts — this is a fundamental security best practice recommended by Microsoft, CIS benchmarks, and DISA STIGs.

However, this bug creates an impossible choice for security-conscious organizations:

  1. Enforce AllSigned (as security policy requires) → Microsoft's own signed scripts break because PowerShell ignores the machine-wide TrustedPublisher store where enterprise admins deploy certificates. Azure VM extensions, Windows services, and automation all fail silently.
  2. Relax execution policy to RemoteSigned or Bypass → automation works, but the security posture is weakened, violating compliance requirements.

There is no workaround that preserves both security and functionality:

  • Adding certs to SYSTEM's CurrentUser\TrustedPublisher is fragile and non-standard — no enterprise tooling (GPO, Intune, SCCM) targets per-user stores for service accounts.
  • The only enterprise-standard mechanism for deploying trusted publisher certificates — LocalMachine\TrustedPublisher via GPO — is the one store PowerShell doesn't check.

The net effect is that PowerShell's AllSigned policy is incompatible with non-interactive execution of Microsoft-signed scripts, which undermines customers' ability to both secure their systems and use Azure/Microsoft services.

Real-world impact

This affects any software that ships signed .ps1 scripts and runs them non-interactively under AllSigned:

  • Azure VM extensions (Dependency Agent, Custom Script Extension, Log Analytics, Azure Monitor Agent) — all run as SYSTEM with no interactive session
  • Windows services that execute PowerShell scripts
  • Scheduled tasks running as LocalSystem or a service account
  • Intune/SCCM-deployed PowerShell scripts

We are seeing this on ~279 enterprise Azure VMs where GPO enforces AllSigned. The scripts are ESRP-signed with valid Authenticode signatures (Get-AuthenticodeSignature reports Valid), but PowerShell blocks them because the signer cert is not in CurrentUser\TrustedPublisher for the SYSTEM account.

Steps to reproduce

Prerequisites: Download the attached Exit0.ps1 (a signed Microsoft script that simply calls exit 0).

# 1. Verify the script has a valid Authenticode signature
Get-AuthenticodeSignature .\Exit0.ps1

# 2. Confirm the signer cert is NOT in CurrentUser\TrustedPublisher
#    (it won't be unless you've manually added it)
Get-ChildItem Cert:\CurrentUser\TrustedPublisher

# 3. Run under AllSigned in non-interactive mode — this simulates GPO-enforced AllSigned
pwsh -NonInteractive -ExecutionPolicy AllSigned -File .\Exit0.ps1
# Result: "AuthorizationManager check failed"

To demonstrate that LocalMachine\TrustedPublisher is ignored, add the signer cert there and retry:

# 4. Extract the signer cert and add it to LocalMachine\TrustedPublisher (requires admin)
$sig = Get-AuthenticodeSignature .\Exit0.ps1
Export-Certificate -Cert $sig.SignerCertificate -FilePath signer.cer
Import-Certificate -FilePath signer.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisher

# 5. Retry — still fails because PowerShell only checks CurrentUser\TrustedPublisher
pwsh -NonInteractive -ExecutionPolicy AllSigned -File .\Exit0.ps1
# Result: STILL "AuthorizationManager check failed"

# 6. Now add to CurrentUser\TrustedPublisher — this is what PowerShell actually checks
Import-Certificate -FilePath signer.cer -CertStoreLocation Cert:\CurrentUser\TrustedPublisher

# 7. Retry — now it works
pwsh -NonInteractive -ExecutionPolicy AllSigned -File .\Exit0.ps1
# Result: Success (exit 0)

Steps 4-7 prove that LocalMachine\TrustedPublisher is ignored and only CurrentUser\TrustedPublisher is consulted.

To reproduce as LocalSystem (the real-world scenario), use PsExec -s or a scheduled task running as SYSTEM.

Expected behavior

A validly signed script whose signer certificate is trusted in LocalMachine\TrustedPublisher should execute under AllSigned, regardless of whether the certificate is also in CurrentUser\TrustedPublisher.

Actual behavior

PowerShell only checks CurrentUser\TrustedPublisher. The certificate in LocalMachine\TrustedPublisher is ignored. In non-interactive mode, AuthenticodePrompt() returns DoNotRun immediately, and the script is blocked with:

AuthorizationManager check failed.
---> PowerShell is in NonInteractive mode. Read and Prompt functionality is not available.

Relevant source code

In src/System.Management.Automation/security/SecurityManager.cs:

  • IsTrustedPublisher() — Opens new X509Store(StoreName.TrustedPublisher, StoreLocation.CurrentUser) only. Never opens StoreLocation.LocalMachine.
  • AuthenticodePrompt() — When host == null || host.UI == null, returns RunPromptDecision.DoNotRun with no fallback to check additional stores.

Proposed technical implementation details

  1. In IsTrustedPublisher(), after checking CurrentUser\TrustedPublisher, also check LocalMachine\TrustedPublisher if the certificate was not found.
  2. Preserve deny precedence: if the certificate is in CurrentUser\Disallowed or LocalMachine\Disallowed (Untrusted stores), it should remain blocked regardless of its presence in LocalMachine\TrustedPublisher.
  3. The check order should be: CU\Disallowed → LM\Disallowed → CU\TrustedPublisher → LM\TrustedPublisher.

Relationship to #21550 / PR #25824

This is orthogonal to #21550 and PR #25824. Those address what identity material should be trusted (OID/EKU identity for Azure Trusted Signing cert rotation). This issue addresses which Windows certificate stores PowerShell consults when deciding whether a valid Authenticode signer is trusted. Even with OID support from PR #25824, PowerShell will still fail enterprise/SYSTEM scenarios if it only consults CurrentUser.

Expected behavior

The script executes.

Actual behavior

The script's signature is rejected. The script does not run.

Error details

Environment data

Name                           Value
----                           -----
PSVersion                      7.5.5
PSEdition                      Core
OS                             Microsoft Windows 10.0.26200
Platform                       Win32NT

Visuals

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-TriageThe issue is new and needs to be triaged by a work group.WG-Securitysecurity related areas such as JEA

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions