The Windows node's exec-approval policy limits what a connected gateway/agent may run on the host to a small read-only/diagnostic set. system.execApprovals.set lets a remote caller update that policy, and ValidateExecApprovalRules is the hard backstop that rejects dangerous Allow rules so a compromised or malicious caller cannot weaken the policy into arbitrary execution — its own states the threat model: "A remote .set call should never be able to whitelist a specific binary by path — that would be a two-step EoP (compromise MCP token → whitelist attacker binary → invoke it)." But the validator is a denylist of ~16 dangerous command fragments , and it misses the entire living-off-the-land binary (LOLBin) class. A .set caller can add an Allow rule for mshta , rundll32 , regsvr32 , certutil , bitsadmin , wmic , curl , etc. — each of which achieves code execution or remote download-and-run — then invoke it via system.run . Same "compromise token → whitelist → invoke" EoP the check exists to stop, via a command name instead of a path (CWE-863 / CWE-184).
No per-call admin gate. HandleExecApprovalsSet is guarded only by (a) the tray "Run system tools" permission toggle (whether the capability is exposed at all), (b) an optimistic-concurrency baseHash (readable via system.execApprovals.get ), and (c) ValidateExecApprovalRules . There is no operator-scope/admin authorization on the call itself.
The backstop is a denylist. DangerousAllowPatternFragments = { remove-item, rm , del , erase , rd , rmdir , format-, stop-computer, restart-computer, shutdown, invoke-webrequest, invoke-restmethod, start-process, set-executionpolicy, reg , net }. ValidateExecApprovalRules also blocks all-wildcard patterns ( * , ?* ), shell-blanket patterns ( powershell * ), and absolute-path patterns ( C:\… , \\… ). It does NOT constrain the command an Allow rule names to a safe set.
The gap: LOLBins. None of mshta , rundll32 , regsvr32 , certutil , bitsadmin , wmic , curl , wget , msbuild , installutil , mshta.exe , … are fragments, so an Allow rule like mshta * passes. mshta , regsvr32 /s /n /u /i: scrobj.dll (Squiblydoo), certutil -urlcache -f p.exe , and rundll32 all run remote code.
Once whitelisted, the policy allows it. After the accepted .set , ExecApprovalPolicy.Evaluate matches the LOLBin command against the new Allow rule, so system.run executes it.
PoC (benign — nothing is executed to prove it; the accept + classification are read)
Against the compiled node (default policy), driving the real system.execApprovals.set + system.run handlers:
CWE-863 (Incorrect Authorization) / CWE-184 (Incomplete List of Disallowed Inputs). On a node where "Run system tools" is enabled, a gateway/agent that can call system.execApprovals.set — a lower-trust or prompt-injected/compromised caller, exactly the "compromised MCP token" the validator names — can weaken the exec-approval policy from "read-only diagnostics only" to "runs an attacker-chosen LOLBin," then execute arbitrary code on the user's Windows host (download-and-run, remote script execution). This defeats the purpose of the exec-approval boundary. Confidentiality, integrity, and availability of the host are all at risk.
Suggested fix (design decision — recommended direction; the LOLBin gap is verified)
A denylist of dangerous command fragments cannot enumerate the LOLBin set (hundreds, per the LOLBAS project, and growing), so extending DangerousAllowPatternFragments is a stopgap, not a fix. Invert the control to fail closed :
Preferred: a remote .set may only add an Allow rule whose command resolves to a curated safe, read-only command allowlist (the same set the built-in default rules already encode — echo, hostname, whoami, systeminfo, ipconfig, ping, dir, type, cat, Get-* read cmdlets, …). Anything else is rejected; broadening the policy to a non-safe command requires the local Permissions UI, not a remote call.
Alternative: a remote .set may tighten the policy (remove Allow rules, add Deny rules) but may not broaden it (add new Allow rules); allow-broadening is a local-user decision only.
Either makes the backstop sound regardless of which binary an attacker names. (The exact safe-command set is the maintainer's product call — happy to implement and verify whichever direction you choose, with a regression test that the LOLBins above are rejected and the safe defaults still accepted.)
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
