AdminToolKit runs with Administrator privileges and executes PowerShell scripts locally and against remote machines via WinRM. This document describes the security model, known considerations, and how to report vulnerabilities.
A formal CISO-level security review was conducted prior to v1.12. All findings were remediated. See CISO Review Findings (v1.12) for the full record.
| Version | Supported |
|---|---|
| 1.13 | Yes |
| 1.12 | Yes |
| < 1.12 | No |
AdminToolKit requests elevation via UAC on launch (app.manifest requires
requireAdministrator). This is necessary for:
- Reading the Security event log (Last Logins)
- Managing Windows services (Restart Spooler, Fix WMI)
- Modifying system files and registry (Fix Windows Update, Clean SCCM)
- Running DISM and SFC repairs
The app does not bypass UAC. If the user declines the UAC prompt, the application will not start.
AdminToolKit sets Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
for the current process only before running each script. This change:
- Applies only to the PowerShell runspace created for that script execution
- Does not modify the system-wide or user-level execution policy
- Is not persistent across reboots or sessions
- Does not affect other PowerShell sessions running on the machine
For remote executions, a dedicated Runspace is opened via
RunspaceFactory.CreateRunspace() and the bypass is applied to that runspace
only before the user script is loaded. The runspace is disposed after execution.
When alternate credentials are provided for remote execution:
- The password is stored as
System.Security.SecureStringinRemoteScriptsViewModel— never as a plainstring - The
PasswordBoxin the UI syncs toSecureStringviaSetPassword()on every keystroke; the plain password is never accessible via a property - Credentials are passed to the PowerShell engine via
Runspace.SessionStateProxy.SetVariable("__cred", psCred)— aPSCredentialobject is constructed from theSecureStringdirectly and injected into the runspace. The password never appears as a string literal in script text at any point. - The
SecureStringis cleared and disposed immediately after each script run - Credentials are never written to disk, logged, or stored in the registry
- Credentials are not persisted between application restarts
Recommendation: Use Windows integrated authentication (Kerberos/Negotiate) wherever possible. Only use the alternate credentials feature for non-domain machines or cross-domain scenarios.
All user-supplied input is validated and sanitised before use in script execution:
Hostname validation (ValidateHostname() in PowerShellRunner.cs):
- Strict allowlist: letters, digits, hyphens, dots, and underscores only
- Maximum 253 characters (RFC 1035 DNS limit)
- Any hostname failing validation blocks script execution before it starts
- An error line is written to the output panel with no script executed
Script parameter injection (InjectParameters() in PowerShellRunner.cs):
- Parameter values are single-quote escaped (
'→'') before injection - Parameter names (variable names) are stripped of all non-alphanumeric and
non-underscore characters via
EscapeVarName()
Every script execution is written to an append-only audit log at:
%ProgramData%\AdminToolKit\audit.log
Each log entry contains:
| Field | Description |
|---|---|
| Timestamp | ISO-8601 UTC (yyyy-MM-ddTHH:mm:ssZ) |
| User | Windows identity (DOMAIN\username) |
| Machine | Source machine hostname |
| Category | Local / Remote / ActiveDirectory |
| Script | Script name |
| Target | Target hostname, or LOCAL for local scripts |
| Result | SUCCESS or FAILURE |
| Duration | Execution time in seconds |
| Error | First error message if result is FAILURE |
The log uses pipe (|) as a field delimiter. Pipe characters in field values
are replaced with / to prevent log injection. Log writes use a
SemaphoreSlim lock to prevent race conditions on concurrent executions.
Logging failures are silently swallowed — they can never crash the application.
The log is accessible via File → View Audit Log in the application menu.
Remote scripts run via Invoke-Command -ComputerName $TargetHost. The
following WinRM configuration is required and should be understood:
- TrustedHosts set to
*in the lab setup guides is convenient but not recommended for production — it permits connections to any hostname without certificate validation. Restrict TrustedHosts to specific IP ranges in production environments (see Hardening Recommendations). - Remote scripts run in the security context of the connecting user (or the alternate credential if provided). They do not gain additional privileges on the remote machine beyond what that account has.
- All remote commands are executed over WinRM (port 5985 HTTP by default). Enable WinRM over HTTPS (port 5986) in sensitive environments.
AdminToolKit executes .ps1 scripts stored in the Scripts\ subfolder of the
installation directory. These scripts are:
- Installed to
C:\Program Files\AdminToolKit\Scripts\by the installer - Readable (but not writable) by standard users after installation
- Not Authenticode-signed — they rely on the process-scoped Bypass policy described above
Risk: If an attacker can write to the Scripts\ installation folder, they
could replace scripts with malicious versions that execute with Administrator
privileges. Ensure the installation directory retains its default ACL
(Administrators: Full Control, Users: Read & Execute).
Build-Installer.bat supports optional Authenticode signing of the installer
itself via the SIGN_CERT variable — see Code Signing.
The Remote Control feature launches CmRcViewer.exe (SCCM Remote Control
Viewer), a proprietary Microsoft/ConfigMgr binary:
- The target hostname is resolved from registry
(
HKLM\SOFTWARE\WOW6432Node\Microsoft\ConfigMgr10\AdminUI) before falling back to the hard-coded default path — it is not constructed from user input - AdminToolKit does not intercept, log, or modify the remote session
- The SCCM Remote Tools Operator role is required on the connecting account
- Remote Control sessions are subject to SCCM's own audit logging
The Remote Desktop feature launches mstsc.exe with the validated target
hostname. If alternate credentials are configured:
- Credentials are temporarily stored in Windows Credential Manager via
cmdkey /generic:"TERMSRV/hostname"to allow mstsc to connect without prompting - A background PowerShell job automatically removes the stored credentials from Credential Manager after 15 seconds
- The plain-text password is extracted from
SecureStringonly within the local PowerShell process for thecmdkeycall, and the reference is immediately zeroed after use
AD scripts use the ActiveDirectory PowerShell module (RSAT). Operations
performed (password resets, account disables, group modifications) are:
- Executed in the security context of the logged-in user
- Logged by Active Directory's own audit infrastructure (if auditing is enabled)
- Logged by AdminToolKit's own
AuditLoggerwith user, target, and result - Irreversible in some cases — Disable Account and Delete Profile operations cannot be undone from within AdminToolKit
The script output console may display:
- Usernames, email addresses, phone numbers (from AD queries)
- Last logon times, group memberships
- IP addresses, MAC addresses, DNS records
- Installed software names and versions
Use File → Save Output and Export HTML with care. Output files are saved in plain text or HTML with no encryption. Do not leave output files containing sensitive user data in shared or unprotected locations.
The Copy to Clipboard function copies all current output as plain text. Be mindful when pasting into ticketing systems, emails, or chat tools.
A formal security review was conducted against the codebase before internal deployment approval. Six findings were identified and all were remediated in v1.12. The findings and resolutions are summarised below.
| # | Finding | Severity | Status |
|---|---|---|---|
| 1 | Plain-text password embedded as string literal in PowerShell script text | HIGH | ✅ Remediated |
| 2 | No audit trail of administrative actions | HIGH | ✅ Remediated |
| 3 | Hostname injection attack surface (insufficient input validation) | MEDIUM | ✅ Remediated |
| 4 | Script parameter variable names not sanitised | MEDIUM | ✅ Remediated |
| 5 | Execution policy bypass not clearly scoped/documented | LOW | ✅ Remediated |
| 6 | WinRM TrustedHosts = * in setup guidance |
LOW | ✅ Documented |
Before: PowerShellRunner.ExecuteRemote() built the credential injection
block as a string literal:
$__pass = ConvertTo-SecureString 'PLAINTEXT_PASSWORD' -AsPlainText -ForceThis meant the password existed verbatim in managed heap memory as part of the script string and would appear in any heap dump or crash report.
After: A Runspace is created via RunspaceFactory.CreateRunspace(). A
PSCredential is constructed directly from the SecureString and injected
via Runspace.SessionStateProxy.SetVariable("__cred", psCred). The password
never appears in script text. File: Helpers\PowerShellRunner.cs.
Before: No logging of any kind. Impossible to answer "who ran what, against which machine, and when" in a GDPR data subject request or ISO 27001 audit.
After: AuditLogger.cs writes an append-only structured log to
%ProgramData%\AdminToolKit\audit.log on every script execution. All fields
are sanitised to prevent log injection. Files: Helpers\AuditLogger.cs,
Helpers\PowerShellRunner.cs, Views\MainWindow.xaml, Views\MainWindow.xaml.cs.
Before: Target hostname was injected into script text with only single-quote escaping, leaving PowerShell metacharacters as an attack surface.
After: ValidateHostname() applies a strict allowlist (letters, digits,
hyphens, dots, underscores only, max 253 chars). Invalid input blocks
execution before any script text is constructed. File: Helpers\PowerShellRunner.cs.
Before: Script parameter names were used as PowerShell variable names without sanitisation — only values were escaped.
After: EscapeVarName() strips all non-alphanumeric and non-underscore
characters from parameter names before injection. File: Helpers\PowerShellRunner.cs.
Before: Bypass was applied via ps.AddScript() with no documentation of
scope.
After: SetBypassPolicyOnRunspace() applies the bypass to the dedicated
runspace only, with explicit documentation that this is Scope Process and
does not affect system-wide policy. File: Helpers\PowerShellRunner.cs.
Before: Setup documentation recommended TrustedHosts = * without
production caveats.
After: SECURITY.md and setup guides updated with explicit production
hardening recommendation to restrict TrustedHosts to specific subnets and
to use WinRM over HTTPS. File: SECURITY.md.
| Area | Limitation |
|---|---|
| Audit log integrity | Log file is append-only but not cryptographically signed — a local Administrator could modify it |
| No MFA support | Alternate credentials use username/password only; no FIDO2 or smart card support |
| Script signing | .ps1 files are not Authenticode-signed; integrity relies on install directory ACL |
| RDP credential window | Credentials exist in Credential Manager for up to 15 seconds during RDP session launch |
| WinRM transport | HTTP (port 5985) used by default — enable HTTPS in production |
The official AdminToolKit-Setup.exe installer is unsigned by default.
Enterprise environments may encounter a Windows SmartScreen warning
("Unknown publisher") on first run.
To sign the installer with your organisation's code-signing certificate:
- Set
SIGN_CERT=YOUR_CERT_THUMBPRINTinBuild-Installer.bat - Set
SIGN_TSAto your timestamp authority URL (e.g.http://timestamp.digicert.com) - Run
Build-Installer.bat—signtool.exeruns automatically after NSIS
Obtaining a trusted certificate requires purchasing one from a Certificate Authority (DigiCert, Sectigo, GlobalSign). Self-signed certificates do not eliminate the SmartScreen warning.
If you discover a security vulnerability in AdminToolKit, please do not open a public GitHub issue.
Report vulnerabilities by:
- Opening a GitHub Security Advisory (Repository → Security → Advisories → New draft security advisory)
- Or emailing the repository owner directly via the contact on their GitHub profile
Please include:
- A description of the vulnerability and its potential impact
- Steps to reproduce
- Affected version(s)
- Any suggested mitigation or fix
You can expect an initial response within 5 business days. We will work with you to understand and address the issue before any public disclosure.
For organisations deploying AdminToolKit in production environments:
- Restrict TrustedHosts — replace
*with specific IP ranges or hostnames, e.g.Set-Item WSMan:\localhost\Client\TrustedHosts -Value "10.0.1.*" - Enable WinRM HTTPS — use port 5986 with a valid certificate to encrypt all remote traffic; prevents credential interception on untrusted networks
- Sign the installer — use
Build-Installer.batwithSIGN_CERTset to eliminate SmartScreen warnings and verify installer integrity - Use dedicated service accounts — run AdminToolKit under a dedicated admin account, not personal domain admin credentials; limits blast radius if credentials are compromised
- Enable AD auditing — ensure Active Directory audit policies log password resets, account lockouts, and group membership changes independently of AdminToolKit's own log
- Protect the install directory — verify
C:\Program Files\AdminToolKit\is not writable by standard users; prevents script replacement attacks - Review the audit log regularly —
%ProgramData%\AdminToolKit\audit.logshould be ingested into your SIEM or reviewed periodically for anomalous activity (unexpected targets, failure spikes, out-of-hours usage) - Use application allowlisting — add
AdminToolKit.exeto your allowlist rather than disabling execution policy system-wide; enforce via AppLocker or WDAC - Rotate credentials — if alternate credentials are used, treat them as privileged service account credentials and rotate per your PAM policy