PowerShell Constrained Language Mode
Constrained Language Mode (CLM) is the PowerShell language tier that actually is a security boundary — unlike the execution policy, which Microsoft describes as a safety rail. Introduced in PowerShell 3.0, CLM strips the dangerous parts of the language: Add-Type, arbitrary .NET type access, COM objects, and direct Windows API calls are all blocked, leaving only an allowlist of “safe” core types ([string], [int], [datetime], [hashtable], [xml], [adsi], [wmi], and a few dozen others) whose properties and methods remain usable.
How it gets enabled
CLM is not something an administrator toggles directly. Per about_Language_Modes, PowerShell automatically drops into ConstrainedLanguage when it detects a system application-control policy — AppLocker or Windows Defender Application Control (WDAC, now “App Control for Business”):
- Any script, module, or manifest allowed by the policy runs with
FullLanguagerights. - Any script not allowed still runs, but only in
ConstrainedLanguage. - If any WDAC UMCI policy with script enforcement is active — even in audit mode — interactive PowerShell is forced into CLM. Full interactive language requires disabling script enforcement on all active policies.
Check the current mode:
$ExecutionContext.SessionState.LanguageMode
# FullLanguage → unconstrained
# ConstrainedLanguage → CLM activeMicrosoft’s guidance is emphatic: CLM is only trustworthy under System Lockdown with App Control for Business. Without a lockdown policy, CLM can be bypassed (e.g., by spawning a new runspace or exploiting allowed-type quirks), so it must be paired with the policy that enforces it.
Offensive implications
CLM breaks the standard “download into memory and IEX” playbook: cradles that rely on New-Object Net.WebClient, reflection, or in-memory compilation fail because those types aren’t in the allowlist. The typical red-team adaptations:
- Script-as-application, not module: restructure tooling so functions are invoked by running the PS1 file directly rather than dot-sourcing/loading into memory — allowed scripts permitted by the AppLocker/WDAC policy run with FullLanguage rights.
- Write to disk in an allowed directory: execution from disk means the file hits Microsoft Defender’s on-access scanning, and AppLocker rules dictate which directories are trusted. Enumerate the effective policy to find writable + executable paths:
Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollectionsThe trade-off is a classic evasion triangle: CLM pushes payloads to disk → disk triggers Defender/AMSI → AMSI bypass (T1562.001) re-enables in-memory execution only if FullLanguage can be recovered. See invoke-webrequest-download-cradles for how the download side interacts with AMSI.
MITRE ATT&CK lists PowerShell Constrained Language mode explicitly as mitigation M1038 (Execution Prevention) for T1059.001 — one of the few controls the framework calls out by name for PowerShell abuse.
Detection
- PowerShell EID 4103/4104 logs expose
$ExecutionContext.SessionState.LanguageModeprobes and failed type-resolution errors characteristic of tooling breaking under CLM - AppLocker EID 8004 / WDAC EID 3077 (script blocked) or 3076 (audit-mode would-block) reveal both attempted script execution and which directories attackers are probing
Get-AppLockerPolicy -Effectiveexecution by non-admin tooling is itself a strong recon signal
Sources
- about_Language_Modes
- Script enforcement with App Control for Business
- PowerShell Constrained Language Mode
- Command and Scripting Interpreter: PowerShell
Related
- powershell-execution-policy-bypass — the control CLM replaces as the “real” boundary
- invoke-webrequest-download-cradles — download-to-memory techniques that CLM specifically defeats
- windows-scripting-host — alternate script interpreter attackers pivot to when PowerShell is locked down