← All posts

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:

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:

  1. 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 2 and walks right past your controls. Remove it.
  2. 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:

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.