Writing BeyondTrust EPM Rules for Least Privilege on Windows
This guide is for developers and endpoint engineers who need to write BeyondTrust Endpoint Privilege Management rules that actually enforce least privilege on Windows without breaking developer workflows. You’ll get a concrete mental model for how rule matching works, how to scope elevation safely, and what to encode in rules so users can run the one thing they need instead of becoming local admins.
TL;DR — On Windows, good BeyondTrust Endpoint Privilege Management policy is not "allow this app"; it is "allow this exact executable, from this exact path or publisher, with these exact child-process and argument constraints, for this exact user/group, and deny everything else by default." The single biggest improvement is to stop writing broad path-based allow rules like
C:\Users\*\Downloads\*.exeor blanket publisher rules and instead combine identity, file trust, command-line constraints, and child-process control around one real task. Reading time: ~7 min
What it is and where it sits
BeyondTrust Endpoint Privilege Management on Windows is an endpoint-side policy engine that decides, at process launch time, whether a user can run something, whether it runs with standard user rights, whether it is elevated, blocked, or prompted, and under what conditions. In practice, it sits between the user’s action and Windows creating the process with a token.
For least privilege, the architecture context matters more than the product label:
- Windows provides the user token, integrity levels, UAC behavior, file system and registry ACLs, AppLocker/WDAC options, and process creation events.
- EPM adds policy-driven decisions at launch time so you can remove local admin rights but still let users perform tightly scoped admin tasks.
- It replaces the common anti-pattern of putting developers in local Administrators just so one installer, updater, service tool, or MMC snap-in works.
- It does not replace OS hardening, code signing, ACLs, or application control. It complements them.
A typical flow looks like this:
User clicks installer / runs command
|
v
Windows Shell or CreateProcess* API
|
v
EPM agent evaluates policy
- user/group
- executable path
- hash / publisher / signature trust
- parent process
- command line
- hostname / endpoint group
- time / message / approval workflow (if configured)
|
+--> Block
|
+--> Run as standard user
|
+--> Elevate just this process
|
+--> Elevate with restrictions on child processes
v
Windows creates process token and launches process
The important design point: your rule is effectively part of process authorization. If you scope it badly, you have created a local privilege escalation path. If you scope it well, you have replaced permanent admin membership with a narrow, auditable exception.
How it actually works
The mechanism is straightforward: when a process launch is attempted, EPM evaluates matching rules and applies the highest-precedence action. The exact precedence and policy authoring UI vary by deployment, but the concrete ingredients are the same: target users, target applications, trust attributes, and the resulting privilege action.
End-to-end example: allow a developer to update Docker Desktop without local admin
Real problem: your developers are standard users. Docker Desktop update requires admin rights because it writes under C:\Program Files\Docker\Docker, updates services, and touches machine-level settings. You do not want to grant local admin or broadly allow all installers.
We’ll walk the safe version.
Step 1: Observe the exact executable and launch pattern
On a test machine, run the update once while capturing process creation. You can use Sysmon Event ID 1, Process Monitor, or your EDR telemetry. What you are looking for is:
- the executable path actually launched
- whether the updater is a stable binary or a temp-extracted child under
%LOCALAPPDATA%or%TEMP% - the parent process
- the command line
- whether child processes spawn
msiexec.exe,cmd.exe,powershell.exe, or service tools
Typical shapes you might see:
ParentImage: C:\Program Files\Docker\Docker\Docker Desktop.exe
Image: C:\Program Files\Docker\Docker\Docker Desktop Installer.exe
CommandLine: "C:\Program Files\Docker\Docker\Docker Desktop Installer.exe" install --auto-update
User: CORP\alice
IntegrityLevel: Medium
Or the more dangerous pattern:
ParentImage: C:\Program Files\Docker\Docker\Docker Desktop.exe
Image: C:\Users\alice\AppData\Local\Temp\7zS4A1B\installer.exe
CommandLine: "C:\Users\alice\AppData\Local\Temp\7zS4A1B\installer.exe" /silent
The first pattern is rule-friendly. The second is where teams get into trouble, because a broad path rule on %TEMP% becomes an easy privilege escalation path.
Step 2: Pick the narrowest trust anchor that survives updates
Use this order of preference:
- Publisher/certificate trust for a specific vendor binary, if signatures are consistent and verifiable.
- Stable install path under
C:\Program Files\Vendor\...combined with publisher or hash. - Hash only when the file changes rarely; hashes are brittle for auto-updating software.
- Never trust user-writable paths alone (
Downloads,%TEMP%,%LOCALAPPDATA%) for elevation.
For Docker Desktop, a reasonable least-privilege rule is usually: specific publisher + specific product binary path + specific command-line pattern + restricted child process behavior.
Step 3: Scope the subject and endpoint set
Do not target "All Users" unless the task is truly universal. Target:
- an AD group like
CORP\Dev-Docker-Updaters - a device group like
Windows-Developer-Workstations
This matters because least privilege is about blast radius as much as process trust.
Step 4: Define the action
The action should be "elevate this application" rather than "make the user admin" or "allow any child process elevated." If your policy model supports child-process inheritance control, set it to the narrowest option that still lets the installer finish.
What you want semantically is:
- elevate
Docker Desktop Installer.exe - only when launched by approved users on approved endpoints
- only from
C:\Program Files\Docker\Docker\... - only if signed by the expected publisher
- only with expected arguments like
install --auto-update - do not allow arbitrary
cmd.exeorpowershell.exechildren to inherit elevation unless required and explicitly allowed
Step 5: Test the happy path and the abuse path
Happy path:
Start-Process "C:\Program Files\Docker\Docker\Docker Desktop Installer.exe" -ArgumentList "install --auto-update"
Abuse path you should test and expect to fail:
Copy-Item .\evil.exe "$env:TEMP\installer.exe"
Start-Process "$env:TEMP\installer.exe" -ArgumentList "/silent"
Another abuse path:
Start-Process "C:\Program Files\Docker\Docker\Docker Desktop Installer.exe" -ArgumentList "install --auto-update & powershell.exe -enc AAAA"
If command-line matching is loose or absent, you may accidentally elevate unexpected behavior.
Step 6: Read the logs like a policy engineer
When the rule misses, the useful questions are:
- Did the wrong rule win because of precedence?
- Did the file path differ from what you expected?
- Was the signature missing or different after update?
- Did the child process, not the parent installer, need elevation?
- Did a deny rule on script hosts (
powershell.exe,wscript.exe,mshta.exe) correctly block a risky child?
The log/event shape you care about is usually: attempted executable, matched rule, action, reason for deny or mismatch. If the product log says effectively "no matching rule" for a process under a user-writable temp path, that is often a sign your original app launches a helper from temp and your trust model needs redesign, not broadening.
When to use it (and when not to)
Use EPM rules when the business requirement is "users must stay non-admin, but this bounded task still has to work." Do not use it as a substitute for packaging, software distribution, or proper application control.
| Scenario | Recommendation |
|---|---|
| Developers need one admin-requiring tool updater or service manager | Use a tightly scoped EPM elevation rule |
| Users regularly install arbitrary software from the web | Do not solve this with broad EPM allow rules; use software distribution plus application control |
A legacy app writes to HKLM or Program Files during normal use | First try vendor fix, shim, ACL correction, or packaging; use EPM only if the privileged operation is unavoidable |
| You can package the install in Intune, MECM, or another deployment system | Prefer managed deployment over user-triggered elevation |
The executable runs from %TEMP% or %LOCALAPPDATA% and changes every release | Usually avoid elevation; if unavoidable, require stronger trust signals than path alone |
| You want to stop users being local admins but preserve a few support tasks | Good fit for EPM |
You probably do not need this if the real solution is one of these:
- fix file/registry ACLs for the app
- package the software centrally
- use WDAC/AppLocker to allow execution but not elevation
- move the task to a service/API that already runs with the needed rights
Trade-offs
Every benefit costs something.
-
Benefit: removes standing local admin rights
Cost: you now own a policy surface that can create privilege escalation if written too broadly. -
Benefit: users can complete specific admin tasks without helpdesk
Cost: testing burden increases after every app update, signer change, installer repackaging, or command-line change. -
Benefit: more auditability than shared admin accounts
Cost: you need log review discipline and a process for rule expiry, ownership, and recertification. -
Benefit: better user experience than full application blocking
Cost: policy exceptions tend to accumulate; without cleanup, least privilege slowly becomes "many special cases." -
Benefit: faster than re-engineering a legacy app
Cost: vendor lock-in at the policy layer and operational dependence on the endpoint agent being healthy and current. -
Benefit: can be narrower than UAC approval by local admins
Cost: writing narrow rules takes more engineering time than simply granting admin, especially around child processes and temp extractors.
In practice
Below are examples you can adapt immediately. The exact BeyondTrust policy UI or export format varies, so treat these as rule logic templates and validation commands rather than copy-paste product imports.
Example 1: Good rule logic for a signed installer in Program Files
rule_name: Elevate Docker Desktop updater for developer workstations
applies_to_users:
- CORP\Dev-Docker-Updaters
applies_to_devices:
- Windows-Developer-Workstations
match:
file_path: C:\Program Files\Docker\Docker\Docker Desktop Installer.exe
publisher: Docker Inc.
parent_process: C:\Program Files\Docker\Docker\Docker Desktop.exe
command_line_regex: '^"?C:\\Program Files\\Docker\\Docker\\Docker Desktop Installer\.exe"?\s+install\s+--auto-update$'
action:
privilege: elevate
child_process_inheritance: restricted
allow_child_processes:
- C:\Windows\System32\msiexec.exe
- C:\Windows\System32\sc.exe
deny_child_processes:
- C:\Windows\System32\cmd.exe
- C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
- C:\Windows\System32\wscript.exe
- C:\Windows\System32\cscript.exe
This is the shape you want: identity + device scope + stable path + publisher + parent + command line + child-process restrictions. The gotcha is updates that replace the installer path or signer metadata; after a major vendor packaging change, this rule may stop matching until you re-baseline it.
Example 2: Bad rule logic that creates an easy escalation path
rule_name: Elevate anything called installer from user temp
applies_to_users:
- Everyone
match:
file_path: C:\Users\*\AppData\Local\Temp\*installer*.exe
action:
privilege: elevate
child_process_inheritance: unrestricted
Do not write this. Any user who can drop an EXE into their own temp directory can likely get it elevated. The gotcha is that teams often create this kind of rule after seeing a legitimate app unpack to temp; the right fix is to find a stronger trust anchor, not to trust temp.
Example 3: Commands to validate path, signature, and launch behavior on Windows
$exe = 'C:\Program Files\Docker\Docker\Docker Desktop Installer.exe'
Get-Item $exe | Select-Object FullName,Length,CreationTime,LastWriteTime
Get-AuthenticodeSignature $exe | Format-List Status,StatusMessage,SignerCertificate,TimeStamperCertificate
Get-Process | Where-Object { $_.Path -like '*Docker*' } | Select-Object Name,Id,Path
Start-Process $exe -ArgumentList 'install --auto-update' -PassThru
Use this before writing the rule so you know the exact path and signer you are trusting. The gotcha is that Get-AuthenticodeSignature can return Valid for a broadly trusted signer, but your policy still needs to constrain which signed binary and from where.
Example 4: Commands to inspect whether a process launches from a user-writable location
$paths = @(
"$env:TEMP",
"$env:LOCALAPPDATA",
"$env:USERPROFILE\Downloads"
)
$paths | ForEach-Object { Get-ChildItem $_ -File -Recurse -ErrorAction SilentlyContinue | Select-Object FullName,Length,LastWriteTime -First 20 }
whoami /groups
icacls "$env:TEMP"
icacls "$env:LOCALAPPDATA"
This helps answer the least-privilege question directly: can the user write to the location you are about to trust for elevation? If yes, path-only trust is unsafe there.
⚠️ Before broadening a rule to get a failing installer working, test whether the target path is user-writable with
icaclsand whether the binary is stable and signed withGet-AuthenticodeSignature. A single over-broad elevation rule can turn a standard user into an effective local admin path and create fleet-wide exposure.
Further reading
- BeyondTrust Endpoint Privilege Management for Windows documentation: policy and application rule sections
- Microsoft Learn: User Account Control architecture
- Microsoft Learn: Windows access tokens and integrity levels
- Microsoft Learn: AppLocker and Windows Defender Application Control design guides
- Windows Internals, Part 1
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI