Hardening PowerShell: Execution Policy Is a Safety Belt, Constrained Language Is the Real Boundary
Treat PowerShell’s execution policy as a safety belt, not a security perimeter. Enforce Constrained Language Mode via WDAC/AppLocker, signed scripts, and least‑privilege identities to build a defensible automation stack.
16 Apr 2026, 07:23 UTC

Problem Statement
When administrators enable PowerShell automation, the first line of defense is often the ExecutionPolicy. Microsoft documents it as a “safety belt” that stops accidental script runs, but any user can override it with -ExecutionPolicy Bypass, a scope override, or by pasting code into an interactive session. The real boundary that prevents malicious code from running is Constrained Language Mode, which is only activated when the machine’s application‑control policy (WDAC or AppLocker) denies a script from running in FullLanguage.
Smallest Suitable Design
To harden a fleet of Windows PowerShell 5.1 or PowerShell 7 hosts, adopt the following minimal architecture:
- Deploy an internal code‑signing certificate and sign all trusted automation scripts.
- Configure WDAC (preferred) or AppLocker to allow only signed scripts or scripts from a dedicated trust path.
- Run scheduled automation under a dedicated service identity with the least privileges needed.
- Grant
FullLanguageonly to a documented exception set, such as a JEA endpoint for privileged operators.
Sample AppLocker Rule (XML)
<AppLockerPolicy version="1.0">
<RuleCollection type="Exe" groupRelation="And">
<FilePathRule name="AllowSignedPS" action="Allow" userOrGroupSid="S-1-5-32-544">
<Conditions>
<FilePathCondition path="C:\\Program Files\\WindowsPowerShell\\v1.0\\powershell.exe"/>
<FileSignatureCondition signatureType="TrustedPublisher"/>
</Conditions>
</FilePathRule>
</RuleCollection>
</AppLockerPolicy>
Apply the XML via Set-AppLockerPolicy -PolicyFile .\policy.xml -Merge. This rule only permits PowerShell executables signed by the organization’s publisher.
Trust Boundary Placement
Content from external sources (e.g., GitHub, vendor media, user downloads) is untrusted. The boundary is enforced at process start: the machine policy dictates whether the session enters FullLanguage or ConstrainedLanguage. Process‑ or CurrentUser‑scope execution policy settings are user‑configurable and must never be treated as a security control.
Operational Checks
- Verify Language Mode: In every new session, run:
Expect$ExecutionContext.SessionState.LanguageModeConstrainedLanguagefor standard users. - Audit AppLocker/WDAC Events: Monitor
CodeIntegrity(Event ID 1102) andAppLocker(Event ID 8004, 8005, 8006) for allow/deny entries.Get-WinEvent -LogName "Microsoft-Windows-AppLocker/Operational" | Where-Object {$_.Id -in 8004,8005,8006} - Inventory Language Modes: Periodically run:
and flag any processes running inGet-Process -Name powershell | Select-Object Id, Path, @{Name='LanguageMode';Expression={$_.SessionState.LanguageMode}}FullLanguage.
Failure Modes
- Scripts that attempt
Add-Typeor create .NET type literals will throwInvalidOperationExceptionunder Constrained Language. Test these scenarios in a sandbox before deployment. - Automation that relies on COM automation or arbitrary Win32 calls will be blocked. Consider refactoring to use only approved cmdlets or moving the workload to a container where the host policy is different.
- If the code‑signing private key is compromised, all signed scripts become trustable. Store the key in an HSM or a restricted key store and rotate it annually.
Design Change Conditions
- Switching to PowerShell 7 on Linux/macOS removes WDAC/AppLocker support; you must rely on OS‑level file permissions, container isolation, or user‑level execution policies.
- If a legitimate workload requires
FullLanguage(e.g., dynamic module loading), isolate it in a dedicated JEA endpoint or run it under a privileged service account that has a separate WDAC rule allowing FullLanguage. - When the organization adopts a new code‑signing authority or changes the trust path, update the AppLocker/WDAC policy accordingly.
Version Sensitivity
Windows PowerShell 5.1 derives Constrained Language Mode from MachinePolicy and UserPolicy only. PowerShell 7 adds a ConstrainedLanguage switch in PowerShell.exe -ConstrainedLanguage and respects the same policy sources but also allows ConstrainedLanguage to be enforced via ConstrainedLanguageMode in powershell.exe.config. Validate the exact behavior on the builds you manage before rolling out fleet‑wide.
Practical Checklist
- Confirm
ExecutionPolicyis set toRemoteSignedorAllSignedat the machine level. - Ensure WDAC/AppLocker is enabled and that the policy allows only signed scripts.
- Sign all internal scripts with the organization’s certificate and verify the signature with
Get-AuthenticodeSignature. - Run automation under a dedicated service account with the
Log on as a batch jobright. - Schedule a quarterly audit of
Event Viewerfor AppLocker/CodeIntegrity events and review anyFullLanguagesessions.
By treating execution policy as a safety belt and enforcing Constrained Language Mode through application control, you create a defensible boundary that reduces the attack surface of PowerShell automation while maintaining operational flexibility.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.