Fix Start-Job failing under WDAC Constrained Language audit mode (#25109)#27703
Open
jagilber wants to merge 3 commits into
Open
Fix Start-Job failing under WDAC Constrained Language audit mode (#25109)#27703jagilber wants to merge 3 commits into
Start-Job failing under WDAC Constrained Language audit mode (#25109)#27703jagilber wants to merge 3 commits into
Conversation
Start-Job threw CannotStartJobInconsistentLanguageMode when the runspace was in ConstrainedLanguage solely because the system-wide App Control (WDAC) policy is in audit mode. Audit mode enforces no restrictions and the child process re-evaluates policy independently, so there is no boundary to protect. Track whether ConstrainedLanguage was audit-induced via ExecutionContext.LanguageModeWasSetByAppControlAudit (set in Utils.EnforceSystemLockDownLanguageMode) and allow Start-Job in that case, while still blocking explicitly configured/JEA ConstrainedLanguage sessions. Adds positive and negative tests, including an InitialSessionState-configured ConstrainedLanguage runspace that must remain blocked under audit.
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes Start-Job incorrectly throwing CannotStartJobInconsistentLanguageMode when PowerShell is running under WDAC/App Control audit mode (where the session reports ConstrainedLanguage but restrictions are intended to be log-only). The change threads through an execution-context flag to distinguish audit-induced constrained language from explicitly configured constrained language, and uses that to relax the Start-Job preflight guard only for the audit-induced case.
Changes:
- Track whether
ConstrainedLanguagewas applied due to WDAC/App Control audit mode viaExecutionContext.LanguageModeWasSetByAppControlAudit. - Update
StartJobCommand.CreateHelpersForSpecifiedComputerNames()guard to allow Start-Job when constrained language is audit-induced. - Add Pester regression coverage for Start-Job behavior under simulated audit policy, including explicit constrained-language scenarios remaining blocked.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/System.Management.Automation/engine/remoting/commands/StartJob.cs |
Adjusts the preflight guard for out-of-proc jobs to exempt audit-induced constrained language. |
src/System.Management.Automation/engine/Utils.cs |
Sets the new execution-context flag when audit policy forces FullLanguage → ConstrainedLanguage. |
src/System.Management.Automation/engine/ExecutionContext.cs |
Introduces LanguageModeWasSetByAppControlAudit to carry audit-induced CL provenance. |
test/powershell/Modules/Microsoft.PowerShell.Security/ConstrainedLanguageRestriction.Tests.ps1 |
Adds regression tests to ensure Start-Job succeeds under audit-induced CL and remains blocked under explicitly configured CL. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Allow
Start-Jobto run under an App Control for Business (WDAC) policy in auditmode, instead of failing with a terminating language-mode error.
Under an App Control for Business (WDAC) policy in audit mode, PowerShell 7
launches with the banner
[Constrained Language AUDIT Mode : No Restrictions]and then fails any
Start-Jobwith:Start-ThreadJobandNew-PSSessionare unaffected. This is issue #25109.The out-of-process job preflight guard in
StartJobCommand.CreateHelpersForSpecifiedComputerNames()blocks the job wheneverthe parent is
ConstrainedLanguageand the system lockdown policy is anything otherthan
Enforce. That predicate wrongly captures the WDAC audit state(
SystemEnforcementMode.Audit), which is meant to log, not block.Root cause
SystemEnforcementModehas three states(
src/System.Management.Automation/security/wldpNativeMethods.cs):The guard (
StartJob.cs, ~line 650):The guard was written for a two-state world (manual constrain vs. enforce). Its
intent is to stop a
ConstrainedLanguageparent from launching aFullLanguageout-of-process child that would escape the CL security boundary. That is only a
real risk when the constraint is local/manual and the system has no lockdown at
all (
None). UnderEnforce, the child process is itself locked down; underAudit, the child independently callsWldpGetLockdownPolicy, enters the sameconstrained-audit state, and audit is log-only. Both are consistent, but the
!= Enforcetest lumpsAuditin withNoneand throws.Why a genuine audit policy reaches this guard at all (the keystone):
Utils.EnforceSystemLockDownLanguageMode(engine/Utils.cs) maps the audit stateinto
ConstrainedLanguagefor the session itself —So a real system-wide WDAC audit policy — with no manual language-mode change —
is sufficient to satisfy the guard's
LanguageMode == ConstrainedLanguageclause(this is the
[Constrained Language AUDIT Mode : No Restrictions]banner). Theout-of-process child
pwshruns this identical path at its own startup, so itenters the same constrained-audit state and parent/child modes stay consistent —
there is no boundary to escape, which is why allowing the job is correct. (This is
also why no
LogWDACAuditMessagebranch belongs here: unlike theCompiledScriptBlockdot-source path,Start-Jobwith a script block is permittedunder
Enforce, so there is no enforcement-restricted operation to audit.)The enum even carries a maintainer note that any code consuming it assumes
"anything but
Enforcemeans the script is allowed" and that new states requirereviewing all callers — this call site was not updated for the audit state.
Fix
Fire the guard only when the system reports no lockdown at all:
EnforceAuditNoneThe existing regression test that sets
ConstrainedLanguagewith no systemlockdown and expects
CannotStartJobInconsistentLanguageMode(
ConstrainedLanguageRestriction.Tests.ps1~line 221) still passes, because thatscenario is
None.Tests
Added an audit-mode regression test asserting
Start-Jobsucceeds when the systemlockdown policy resolves to
Audit(simulated via the__PSLockdownPolicydebughook with the UMCI audit flag
0x8). See the patch.Related / out of scope
The sibling guard
CannotCreateRunspaceInconsistentState(
RemotingErrorIdStrings.resx) uses the same two-state assumption on the runspacecreation path and likely warrants the same treatment. Flagged for maintainer
direction; not changed here to keep this fix focused on #25109.
Files changed
src/System.Management.Automation/engine/remoting/commands/StartJob.cs— guard predicate + comment.test/powershell/Modules/Microsoft.PowerShell.Security/ConstrainedLanguageRestriction.Tests.ps1— audit-mode regression test.Bug impact
Component: PowerShell 7 (
Microsoft.PowerShell.Commands.StartJobCommand, out-of-process job creation)Affected versions: Confirmed 7.5.5 and 7.6.3; issue #25109 also reports 7.5.0. Present on current
master.Trigger: Any active WDAC / App Control for Business policy that resolves to audit mode (
WLDP_LOCKDOWN_UMCIAUDIT_FLAG), which forces interactive sessions intoConstrainedLanguagewhile system enforcement isAudit.Defect class: Functional regression / correctness — audit mode incorrectly enforced. Terminating error, not silent.
Customer-visible impact
Start-Job { ... }fails 100% of the time withCannotStartJobInconsistentLanguageModeon any machine where WDAC is in audit mode. Audit mode is specifically the state organizations use to evaluate an App Control policy before enforcing it — so the break hits precisely the fleets doing safe, staged WDAC rollouts.{ "Hello" }and any real workload fail identically; script complexity is irrelevant.Start-Jobbreaks. Background parallelism viaStart-Jobis unavailable on affected devices.What is and is not affected
Start-Job(out-of-process) under WDAC auditStart-Jobunder WDAC enforceStart-Jobwith no lockdown, session manually set to CLStart-ThreadJob(in-process)New-PSSession/ remotingNonecase still blocked,Enforce/Auditchildren remain constrainedBlast radius
Proven
Auditand aScriptBlock/InitializationScriptis supplied.Potential but not individually measured
Start-Jobon such devices.Severity interpretation
Start-ThreadJobis a partial, non-equivalent workaroundStart-JobNone) boundaryNot a confidentiality/privilege/exploit issue and not data loss — it is a functional availability defect for
Start-Jobon audit-mode devices, plus a diagnosis-misdirection cost.Workarounds (until fixed)
Start-ThreadJobwhere in-process isolation semantics are acceptable (not a security-equivalent drop-in forStart-Job).New-PSSession/Invoke-Command) where a real process/session boundary is required.FullLanguagerestoresStart-Job(demonstrates audit-as-enforce); do not present manual language-mode mutation as a production workaround.Start-Job.Fix disposition
WG-Security,Needs-Triage); no maintainer fix/servicing date published.PR Checklist
Start-Jobfailing under WDAC Constrained Language audit mode" (present tense, imperative; describes the change, not the issue)..cs/.ps1/.psm1files added; both edited files already carry the correct header. N/A.CannotStartJobInconsistentLanguageModeunderAuditis removed (a bug), so no Public Contract surface is broken.about_*topics;Start-Jobbehavior is restored to what documentation already implies for audit (log-only) mode. (If a Maintainer disagrees, they will addDocumentation Needed; we can file a PowerShell-Docs issue on request.)ConstrainedLanguageRestriction.Tests.ps1assertingStart-Jobsucceeds when system lockdown resolves toAudit(simulated via the__PSLockdownPolicyUMCI audit flag0x8). The existingNone-case denial test is unchanged and still passes.