PowerShell Constrained Language Mode: Locking Down the #1 Living-off-the-Land Tool
How PowerShell Constrained Language Mode neuters the most-abused attacker tool on Windows, why it only works alongside WDAC/AppLocker, and how WinSentinel audits your enforcement posture.
PowerShell is on every modern Windows box, it is signed, it is trusted, and it can reach directly into the .NET runtime and the Win32 API. That combination is exactly why it tops nearly every incident report's living-off-the-land list. Attackers do not need to drop a binary that your allowlist might reject; they pipe a one-liner into a shell you already trust. Constrained Language Mode (CLM) is the built-in feature that turns that full-power shell into a blunt instrument — but only if you deploy it correctly, and it is one of the most commonly misunderstood controls on Windows.
What Constrained Language Mode actually blocks
PowerShell has four language modes. The default is FullLanguage. CLM (ConstrainedLanguage) keeps the interactive experience mostly intact — cmdlets, pipelines, variables, and loops still work — while stripping out the primitives that offensive tooling depends on:
- Direct .NET type invocation, e.g.
[System.Reflection.Assembly]::Load()or[Runtime.InteropServices.Marshal], which is how in-memory loaders and shellcode injectors work. - Add-Type / P/Invoke to call arbitrary Win32 functions like
VirtualAllocandCreateThread. - COM object creation via
New-Object -ComObjectfor many high-risk classes. - Dynamic method invocation and non-allowlisted script blocks.
The practical effect: frameworks that reflectively load assemblies in memory — the backbone of most fileless payloads — simply throw an error instead of running. You do not lose the ability to write admin scripts; you lose the ability to weaponize the runtime.
The trap: CLM is not a standalone control
This is where teams get burned. Setting the __PSLockdownPolicy environment variable to force CLM is trivially bypassable — any user who can set an environment variable, downgrade to PowerShell v2, or run a full-language host can escape it. CLM is not designed to be enforced by an environment variable at all.
Constrained Language Mode is a byproduct of application control, not a policy you flip on its own. If you have not deployed WDAC or AppLocker in enforce mode, your CLM is a speed bump, not a wall.
The supported path is to deploy Windows Defender Application Control (WDAC) or AppLocker in enforcement mode. When an application-control policy is active, PowerShell automatically drops untrusted scripts — anything not covered by your allowlist — into Constrained Language Mode, while your signed, approved scripts continue to run in Full Language. That is the design: trusted code stays powerful, everything else is caged. Enforcing CLM without application control gives you the illusion of protection with none of the durability.
Don't forget PowerShell v2 and AMSI
Two adjacent gaps routinely defeat an otherwise-good CLM rollout:
- The PowerShell v2 engine predates AMSI and script-block logging and does not honor the same lockdown semantics. If the optional feature is still installed, an attacker runs
powershell -version 2and walks right past your controls. Remove it. - AMSI is the runtime scanner that inspects script content before execution. CLM and AMSI are complementary — AMSI catches obfuscated payloads in trusted contexts, CLM caps what untrusted code can do. A serious hardening posture wants both, plus script-block and module logging feeding your telemetry.
Verify the current session's mode with a one-liner:
$ExecutionContext.SessionState.LanguageMode
# Check the legacy engine is gone:
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
How WinSentinel audits your CLM posture
The hard part of this control is not turning it on — it is proving it stays on and that no bypass has crept back in. WinSentinel checks the whole chain, not just the surface toggle:
- Whether an application-control policy (WDAC or AppLocker) is actually in enforce mode — the precondition that makes CLM real.
- Whether the PowerShell v2 optional feature is still present as a downgrade escape hatch.
- Whether AMSI, script-block logging, and module logging are enabled to catch what CLM does not.
- Whether a misconfigured
__PSLockdownPolicyis giving a false sense of security without application control behind it.
On a single machine this runs free, with the full depth of every module and no artificial limits — the same complete audit a senior engineer would run by hand, minus the hours. For teams running many endpoints, WinSentinel Pro rolls those findings up across the fleet: it flags the one laptop where someone reinstalled PowerShell v2, alerts on drift when an application-control policy silently falls out of enforce mode, and turns "we think CLM is deployed everywhere" into a verifiable, per-node compliance answer.
The takeaway
Constrained Language Mode is one of the highest-leverage hardening steps you can take against fileless, script-based attacks — but it is a consequence of application control, not a substitute for it. Deploy WDAC or AppLocker in enforce mode, rip out the PowerShell v2 engine, keep AMSI and logging on, and then audit continuously so the posture cannot silently regress. Attackers reach for PowerShell first because it is trusted; CLM is how you make that trust expensive to abuse.