Skip to content

Security

Christophe-Rogiers edited this page Oct 16, 2026 · 146 revisions

Table of Contents

  1. Security Overview
    1. The Double-Lock System
  2. How Servy Protects Your Data
    1. Automatic Directory Hardening (ACLs)
    2. Machine-Unique Encryption (Dynamic Entropy)
    3. Cryptographic Key Derivation (HKDF)
    4. Authenticated Encryption (v6.5+)
    5. In-Memory Defense (Memory Zeroing)
    6. Sensitive Command-Line Arguments & Service Account Credentials
  3. Infiltration Guard: Local Import Enforcement
    1. Mitigation: Defense-in-Depth Pipeline
  4. Supply Chain and Trust
  5. The Servy Trust Boundary
    1. Architectural Design & Runtime Permissions
    2. Executable Permission Hardening
      1. What Is Hardened
      2. Servy 10.1 and Earlier
    3. What This Means for Your Deployment
    4. Automatic Permissions Table (v7.9+)
  6. File Locations and Recovery
  7. Critical Warning: Machine Migration
  8. Best Practices
  9. Troubleshooting

Security Overview

Starting from v10.2, Servy is built for enterprise use. Every protection below is applied automatically, with no manual step and no sysadmin intervention:

  • 100% per-service isolation between service accounts: each service account can reach only its own configuration and runtime state (through the Servy service, which checks the caller of every request) and only its own log folder (logs\services\<ServiceName>\). It cannot read or change the configuration database, the encryption key, Servy's own logs or the logs of any service running under another account. See The Servy Trust Boundary.
  • DPAPI and AES encryption: sensitive fields are encrypted with AES-256 and authenticated with HMAC-SHA256, under a key protected by DPAPI and bound to the machine. See How Servy Protects Your Data.
  • Automatic ACLs: the vault, its binaries, its configuration files and each service's log folder get least-privilege access lists when a service is installed, and the grants are revoked when it is removed. See Executable Permission Hardening.
  • Automatic DACL on the named pipe: the Servy service's named pipe is open only to the accounts of the services Servy manages, and the list is rebuilt whenever a service is installed, moved to another account or removed. See Architectural Design & Runtime Permissions.
  • Signed binaries, SBOM and scanned releases. See Supply Chain and Trust.

Servy acts as a secure vault for your Windows service configurations. Servy encrypts sensitive data, including passwords, environment variables, and all execution arguments (Parameters, Password, EnvironmentVariables, FailureProgramParameters, PreLaunchParameters, PreLaunchEnvironmentVariables, PostLaunchParameters, PreStopParameters, and PostStopParameters), using industry-standard AES-256 encryption. If the database is compromised, your actual secrets remain unreadable text.

The Double-Lock System

Starting in version 7.9, Servy uses a two-layer defense strategy to keep your secrets safe:

  • The Locked Room (ACLs): Servy automatically restricts who can even view the Servy folders on your hard drive.
  • The Machine-Unique Key (Dynamic Entropy): Servy ties your encryption key to the unique machine identity of your specific computer. If the files are moved to another PC, they cannot be decrypted without the machine-specific entropy stored in the originating system's registry.

You can verify that the "Locked Room" is active by checking the Access Control List (ACL) of the data directory. Run the following command in an elevated PowerShell window:

(Get-Acl "$env:ProgramData\Servy").Access | Select-Object IdentityReference, IsInherited, AccessControlType, FileSystemRights

What to look for in the output:

  • IdentityReference: You should see two principals: NT AUTHORITY\SYSTEM and BUILTIN\Administrators.

  • Note: The installer grants no personal user ACE, and removes any grant it finds for the installing account, because it always runs elevated (PrivilegesRequired=admin). A third, per-user Full Control ACE appears only when the vault was created or re-hardened by a Servy process running non-elevated and not as SYSTEM (SecurityHelper.ApplySecurityRules). Since v10.2, custom service accounts do not appear here: they get access only to the log folder of each of their own services (logs\services\<ServiceName>\) and to the binaries and configuration files they read (see What Is Hardened). On 10.1 and earlier they appear here with Modify rights once Set-ServyExePermissions.ps1 has been run for them.

  • IsInherited: This must be False for all entries. Servy explicitly breaks inheritance from the %ProgramData% root to prevent "sideways" access from other applications or standard users.

  • AccessControlType: This should be Allow. There should be no entries for broad identity groups such as Everyone, Users, or Authenticated Users, as these are surgically purged during the hardening phase.

  • Executable Hardening Status: When inspecting individual core binaries under %ProgramData%\Servy (such as Servy.Service.exe), the executable permission hardening ensures that custom service runner accounts display ReadAndExecute rights rather than directory-inherited Modify or FullControl rights. See this section for further details.

Implementation Context for Auditors

To reconcile this with the source code, auditors can refer to the following logic gates:

  • Inno Setup (servy.iss): The installer executes with PrivilegesRequired=admin and explicitly grants permissions only to BUILTIN\Administrators and NT AUTHORITY\SYSTEM. It intentionally grants no personal user ACE because the elevated installer's user account is already fully covered by the Administrators group grant.
  • Runtime (SecurityHelper.cs): The ApplySecurityRules method adds an explicit Full Control ACE for the current user only when the process is running non-elevated and is not the LocalSystem account (elevated admins and SYSTEM are already covered by the mandatory Administrators/SYSTEM ACEs). Any non-elevated runtime process invoking vault creation triggers this path.

Subdirectory Inheritance Note

While the root vault (%ProgramData%\Servy) has inheritance explicitly broken, all internal child folders such as db, security and logs are created with inheritance enabled relative to the vault root. This ensures the same "Locked Room" model (SYSTEM and Administrators, plus the installing user only in the non-elevated case described above) is maintained consistently throughout the entire data structure without redundant ACL writes.

How Servy Protects Your Data

Servy has overhauled its security model to be proactive rather than reactive.

1. Automatic Directory Hardening (ACLs)

In previous versions, Servy relied on Windows defaults for the %ProgramData%\Servy folder. In v7.9+, Servy takes control. Upon installation or startup, the application automatically performs the following actions:

  • Breaks Inheritance: Servy disconnects the folder from the open permissions of the parent drive.

  • Explicit Purge: Servy removes access for the Users, Authenticated Users, and Everyone groups.

  • Restricted Entry: Only broad, unprivileged groups are removed - Users, Authenticated Users and Everyone. SYSTEM and Administrators are granted Full Control, and every other explicit entry on the folders Servy secures is removed, Allow and Deny alike; the installing user is preserved only in the non-elevated case (see the table below). This prevents Local Privilege Escalation: the risk of a standard user tampering with a service to gain Admin rights.

  • Downward Inheritance: All subfolders and files within %ProgramData%\Servy automatically inherit these strict parent ACLs, ensuring new service directories, configurations, and logs remain locked down by default.

  • Custom Permissions Are Not Preserved: On every elevated start of the Desktop App, Servy Manager or servy-cli (Release builds), Servy removes every explicit entry for a principal other than Administrators and Local System from the vault and from every file and folder below it, and gives items owned by another principal to BUILTIN\Administrators. Service accounts recorded in the Servy database get their grants back straight afterwards (see What Is Hardened). An ACE you add by hand for any other account is removed at the next elevated start.

2. Machine-Unique Encryption (Dynamic Entropy)

Servy uses the Windows Data Protection API (DPAPI) with an added security layer. To prevent binary analysis (where someone reads the source code to find a secret), Servy derives its encryption entropy from your computer's unique MachineGuid.

  • Why it is safe: Encryption entropy is derived at runtime from your Windows Registry rather than hardcoded in the application binary.
  • Non-Portable: Because every computer has a different ID, your aes_key.dat file is useless if copied to another machine.

If HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid is missing, unreadable, or restricted by registry permissions, Servy logs a critical security degradation error and falls back to deriving entropy from the host's Environment.MachineName. Under this fallback mode, renaming the host computer alters the entropy calculation and permanently breaks decryption for all protected configuration fields.

Caution

DYNAMIC ENTROPY DEPENDENCY & REGISTRY SENSITIVITY

Servy binds its DPAPI master key (%ProgramData%\Servy\security\aes_key.dat) to the system's MachineGuid. If MachineGuid is modified, deleted, corrupted, or blocked by registry permissions on an active installation, Servy will fail to unprotect its keying material; causing Servy Manager, servy-cli, and the background service to immediately fail decryption and halt operations.

If MachineGuid is missing or unreadable during initial key generation, Servy falls back to using the machine's hostname (Environment.MachineName) as its dynamic entropy source and logs a critical security degradation event. If the host is subsequently renamed while running under this fallback state, DPAPI decryption will also fail.

To recover from a decryption failure without re-importing service configurations:

  • Restore the original MachineGuid string in HKLM\SOFTWARE\Microsoft\Cryptography.
  • Fix registry Access Control Lists (ACLs) if permission restrictions prevent Servy from reading MachineGuid.
  • Revert the hostname if running under the fallback state.
  • Otherwise, if a valid pre-existing dump archive is available, purge the stale vault (%ProgramData%\Servy\db and %ProgramData%\Servy\security) and restore configurations using Servy-Restore.ps1 (see Backup/Restore & VM Cloning).

3. Cryptographic Key Derivation (HKDF)

To adhere to strict cryptographic best practices, Servy uses HKDF (RFC 5869) to derive independent sub-keys from your master key. Distinct info context strings (V2_AES_ENCRYPTION and V2_HMAC_AUTHENTICATION) give the encryption and authentication sub-keys domain separation, ensuring neither can be derived from or substitute for the other.

4. Authenticated Encryption (v6.5+)

Servy protects sensitive data using DPAPI-derived keying material combined with machine-specific registry entropy. Authenticated encryption (HMAC-SHA256 + AES-256-CBC) ensures both confidentiality and tamper detection, preventing bit-flipping attacks by refusing to decrypt any modified payload.

5. In-Memory Defense (Memory Zeroing)

Security doesn't stop at the hard drive. To protect against advanced memory scraping attacks, Servy securely handles secrets in RAM. The SecureData class implements IDisposable and utilizes CryptographicOperations.ZeroMemory() to wipe every sensitive buffer as soon as it is no longer needed:

  • Transient Data: Plaintext and ciphertext buffers are zeroed immediately after each encryption/decryption call.
  • Initialization Material: The master key clone passed during construction is wiped as soon as sub-keys are derived.
  • Key Material: All active sensitive buffers are securely zeroed upon the object's disposal. This includes the two HKDF-derived V2 sub-keys required for modern AES encryption and HMAC authentication. (Note: The two legacy V1 buffers, used for master key cloning and static IV retention, remain unallocated in shipped production builds as AllowLegacyV1Decryption is permanently disabled at compile-time).

Unlike standard array clearing methods, this approach ensures that the memory wipe is never elided by the JIT compiler's release optimizations, significantly reducing the window of opportunity for an attacker to extract keying material from a memory dump.

6. Sensitive Command-Line Arguments & Service Account Credentials

While Servy supports CLI flags for configuration convenience (e.g., --password, --envVars, --params), passing sensitive data via command-line arguments is insecure. Command-line arguments are visible to any user or process able to enumerate the process list (e.g., Get-Process, pslist, or Event Tracing for Windows) and are often recorded in shell history files and system audit logs.

The Preferred Methods

Starting in v8.5, Servy provides two secure alternatives to handle sensitive data:

  1. Environment Variables (Recommended for per-service secrets): For each sensitive field, Servy supports an associated environment variable. Servy reads these variables transparently at install time, ensuring the secret never touches the process argument string.
  2. Import Configuration (Recommended for complex deployments): Use the import command with an XML or JSON configuration file. This keeps sensitive values entirely out of the command line and allows for structured, version-controlled configuration management.

Sensitive Fields Reference

The following parameters are considered sensitive and should be provided via environment variables (v8.5+) or configuration files:

Parameter Environment Variable Description
--password SERVY_PASSWORD Windows service account password.
--params SERVY_PROCESS_PARAMETERS Command-line arguments for the service process.
--envVars SERVY_ENVIRONMENT_VARIABLES Environment variables for the service process.
--failureProgramParams SERVY_FAILURE_PROGRAM_PARAMETERS Arguments for the failure recovery program.
--preLaunchParams SERVY_PRE_LAUNCH_PARAMETERS Arguments for the pre-launch executable.
--preLaunchEnv SERVY_PRE_LAUNCH_ENVIRONMENT_VARIABLES Env vars for the pre-launch executable.
--postLaunchParams SERVY_POST_LAUNCH_PARAMETERS Arguments for the post-launch executable.
--preStopParams SERVY_PRE_STOP_PARAMETERS Arguments for the pre-stop executable.
--postStopParams SERVY_POST_STOP_PARAMETERS Arguments for the post-stop executable.
--heartbeatUrl SERVY_HEARTBEAT_URL Heartbeat ping URL. Monitoring endpoints such as https://hc-ping.com/<uuid> are capability URLs: anyone who learns the path can fake a "service is alive" ping. Servy masks the URL in its own logs. Available from v10.4; earlier versions ignore the variable, so pass --heartbeatUrl there.
PowerShell Example:
# Set the secrets in the process-level environment
$env:SERVY_PASSWORD = 'p@ssw0rd_123!'
$env:SERVY_ENVIRONMENT_VARIABLES = 'API_KEY=secret_key_123;DB_URL=...'

# Install the service (omit sensitive flags)
servy-cli install --name="MySecureService" --path="C:\App\app.exe" --user="DOMAIN\svc_account"

# Clear variables immediately after use
Remove-Item Env:SERVY_PASSWORD
Remove-Item Env:SERVY_ENVIRONMENT_VARIABLES

Infiltration Guard: Local Import Enforcement

Importing service configurations from Universal Naming Convention (UNC) paths or through redirected paths (such as symbolic links or junctions) poses severe security risks. These techniques are designed to bypass system trust boundaries and expose the application execution layer to malicious configuration injection.

To preserve system integrity, the import pipeline explicitly blocks non-local paths. The primary attack vectors mitigated by this enforcement include:

  • Attacker-Controlled Configuration Injection: UNC targets (e.g., \\attacker\share\evil.xml) allow a remote adversary to host a malicious configuration file on an infrastructure node under their direct control. If ingested, an attacker can inject arbitrary executable paths, unauthorized parameters, or unverified environment variables, effectively hijacking the service's runtime behavior.
  • Path Redirection Attacks: By leveraging filesystem symbolic links, directory junctions, or specialized Win32 reparse points, an attacker can manipulate the path resolution mechanics. This can trick the engine into reading from an entirely different backend target than intended, exposing sensitive files or pulling parameters from unexpected network locations.
  • Privilege Escalation and System Integrity: Because the engine performs administrative elevation validation checks to operate securely, it possesses high-privilege access to the local machine. If an import task is manipulated into processing files from protected operating system directories (such as the Windows or System32 namespaces), it can facilitate unintended system-level file access or manipulation.
  • Network-Based Mapping Bypasses: Virtual local paths - including mapped network drives (e.g., Z:\config.json) or local DOS device substitutions (subst) - frequently mask underlying remote storage volumes. These targets inherently lack the rigid security boundaries of localized storage hardware, introducing network-level interception vectors.

Mitigation: Defense-in-Depth Pipeline

To counter these vectors, the engine pipes all configuration paths through a strict, sequential validation chain before any file access occurs across the CLI or GUI interfaces:

  1. Explicit UNC Inspection: Rejects any raw path strings starting with standard network prefixes (\\) or parsing as a remote URI.
  2. Drive Interface Queries: Evaluates the target volume via DriveInfo to proactively block network-backed logical drive letters.
  3. Reparse Point Ancestor Walks: Recursively traces the full directory tree to verify that no parent or sibling paths utilize symbolic links or directory junctions.
  4. File-Level Symlink Evaluation: Directly inspects filesystem attributes to confirm the target file is a physical, non-symbolic entity.
  5. Reserved Device Blocks: Prevents spoofing attempts using legacy system device designations (e.g., CON, PRN, AUX).
  6. Protected System Directory Fencing: Prevents configuration loading out of primary administrative operating system paths.
  7. Win32 Kernel Handle Finalization: Opens a temporary restricted read handle and resolves the target's final canonical path via GetFinalPathNameByHandle, ensuring junctions, subst mappings, and virtual devices cannot conceal a UNC target.

Supply Chain and Trust

Security requires transparency. You should not have to guess if Servy is safe.

  • Digitally Signed: All executables and installers are signed by SignPath. This proves the code has not been altered since it left the build server.
  • SBOM (Software Bill of Materials): Servy releases include a full inventory of every component and dependency in the CycloneDX format.
  • Vulnerability Scanning: GitHub Dependabot raises an alert whenever a published advisory matches one of the dependencies.
  • Scanned Releases: Release binaries are scanned on VirusTotal, and false-positive reports are submitted to Microsoft Security Intelligence and affected AV vendors.

The Servy Trust Boundary

Starting from v10.2, Servy uses per-service security isolation. Before v10.2, every service account shared a single trust boundary: each one could read and change the shared configuration database, read the encryption key, and read or delete the logs and recovery state of every other service. v10.2 removes that shared tier. A service account can now reach only its own configuration, its own runtime state and the log folders of its own services (logs\services\<ServiceName>\).

Architectural Design & Runtime Permissions

v10.2 adds a background Windows service, Servy (Servy.Host.exe). It runs as LocalSystem and is the only process that opens the configuration database and the encryption key on behalf of a running service. Each Servy.Service.exe still runs under the identity of your configured service account, but it no longer opens db\Servy.db or security\aes_key.dat itself. It asks the Servy service for what it needs over a local named pipe (\\.\pipe\SERVY_HOST_IPC_PIPE):

  • Reading its configuration: the service receives its own decrypted configuration, and nothing else. The password of the service account is never sent.
  • Writing its runtime state: the process ID of the wrapped application, the active stdout/stderr log paths and the stop timeout in effect are written for it.
  • Restart attempts: the restart-attempt counter of the recovery feature is stored in Servy.db and read and written through the pipe. Before v10.2 it was kept in text files in %ProgramData%\Servy\recovery\; that folder is imported into the database once, by the database migration of the upgrade, and deleted once its counters have been imported.

The Servy service checks every request:

  • Who may connect: the pipe is created with a protected access list that grants NT AUTHORITY\SYSTEM and BUILTIN\Administrators Full Control, grants the accounts of the services Servy manages Read/Write only. Network logons are supported: a local process whose token comes from a network logon (for example servy-cli in a PowerShell remoting or WinRM session) is checked like any other. The pipe is local only: it is created with PIPE_REJECT_REMOTE_CLIENTS, so a client on another computer cannot open it, and the Servy service also refuses any request from one. The list is rebuilt whenever a service is installed, reinstalled under another account or removed, and is applied to the pipe immediately, without restarting the Servy service, so an account loses access to the pipe when its last service goes away.
  • What a caller may ask for: a service account may only read or update the service it is running. The Servy service compares the process ID of the caller (GetNamedPipeClientProcessId) with the process ID the Service Control Manager reports for the service named in the request, and refuses the request when they differ. Administrators and SYSTEM are not restricted.
  • Who the client talks to: before sending anything, Servy.Service.exe checks that the process serving the pipe is the one the Service Control Manager reports for the Servy service, so another process cannot squat the pipe name and serve a forged configuration.

Every service Servy installs depends on the Servy service, so Windows starts it first. The Desktop App, Servy Manager and servy-cli extract Servy.Host.exe into %ProgramData%\Servy, install the Servy service when it is missing (startup type Automatic), start it when it is not running, and add the dependency to services installed by an older version. When the Servy service is registered with another executable than the one they extracted (after switching between the .NET 10 and the .NET Framework 4.8 build, which names it Servy.Host.Net48.exe), they stop the running Servy services and the Servy service, point the service at their own executable, and start them again. When Servy.Service.exe or Servy.Host.exe has to be updated, they stop the running Servy services and the Servy service, replace the binaries, then start them again. The name Servy is reserved and cannot be used for one of your own services.

Servy.Service.exe writes its own log to the folder of its service, %ProgramData%\Servy\logs\services\<ServiceName>\Servy.Service.log, and Servy.Restarter.exe, which it launches, writes Servy.Restarter.log to the same folder. A character a folder name cannot hold (< > : " / \ | ? * and control characters), a trailing dot or space, a reserved device name such as CON, and the characters % and ~ are percent-encoded in the folder name (My:Service logs to logs\services\My%3AService\), so two services never share a folder; a name whose encoded form is longer than 100 characters is shortened to its first 64 encoded characters, followed by ~ and 16 hexadecimal characters of a hash of the name. Before v10.2 the log of every service was logs\Servy.Service.log. On an upgrade that file is left where it is, in logs\, for the administrators: it is not moved or deleted, no service writes to it any more, and no service account can read it, since it may hold the entries of every service. Delete it once you no longer need it.

Executable Permission Hardening

A service account gets the least privilege its services need: nothing on the vault root, nothing in db\, security\ and logs\, write access only in the log folder of each of its own services (logs\services\<ServiceName>\), Read & Execute on the binaries and Read on the configuration files. Starting from v10.2, Servy applies this hardening itself. It is built into the Desktop App, Servy Manager, servy-cli and the PowerShell module, so it works wherever they run, including a portable servy-cli.exe copied on its own. No script and no manual step are needed.

Note

AUTOMATIC SINCE v10.2

Servy hardens the vault for an account:

  • when it installs a service under an account other than Local System (including LocalService, NetworkService, virtual accounts and gMSAs), and when it reconfigures an existing service under such an account;
  • again for every account a service runs under, on every elevated start of the Desktop App, Servy Manager or servy-cli, whether or not a binary was extracted, because the vault is purged of non-administrative entries first and each account is granted its access back right after.

Local System, the BUILTIN\Administrators group and its members are left untouched: they keep Full Control, so there is nothing to restrict. The outcome is written to the Servy log. If the hardening cannot be applied, the service is still installed and the log says why; installing the service again re-applies it.

What Is Hardened

Path under %ProgramData%\Servy Service account's rights
The vault folder itself None. A grant a previous version wrote there is removed.
db\, security\ None. The account's explicit entries on the folders and on the files directly inside them (Servy.db, aes_key.dat, ...) are removed. Only the Servy service, running as LocalSystem, opens them.
logs\ and everything under it, other than the account's own service log folders None. The account cannot list, read, write or delete the logs of Servy, the shared Servy.Service.log that versions before v10.2 wrote to logs\, or the log folder of any service it does not run. The account's explicit entries on every file and folder under logs\ are removed.
logs\services\<ServiceName>\, for each service the account runs On the folder: List and Create Files (creating the log, and the new file of a log rotation), never Delete (it cannot rename or delete the folder). On the files in it: Read, Write, Delete (log rotation and pruning). The folder is created if missing.
Servy.Host.exe / Servy.Host.Net48.exe None. The service account cannot read, write or execute the host binaries.
*.exe and *.dll: Servy.Service.exe, Servy.Service.CLI.exe, Servy.Restarter.exe, handle64.exe / handle64a.exe (.NET Framework 4.8 build: the *.Net48.exe binaries, handle64.exe and every *.dll) Read & Execute
appsettings.host.json / Servy.Host.Net48.exe.config None. The service account cannot read the host configuration file.
*.json and *.config: appsettings.service.json, appsettings.restarter.json (.NET Framework 4.8 build: the *.exe.config files) Read only

The service account needs no right on logs\ or logs\services\ to reach logs\services\<ServiceName>\: Windows grants every account the Bypass traverse checking privilege by default, so a full path can be opened without listing its parents.

Accounts are compared by SID, so a service account that runs several services (written .\user for one and MACHINE\user for another, for example) is granted the folders of all of them, and installing one more service under it keeps the folders of the others. Two services that run under the same account share that account's identity, so they can read each other's logs; give each service its own account when their logs must be isolated from each other as well.

Each hardened file stops inheriting from the vault, is owned by BUILTIN\Administrators, and keeps Full Control for NT AUTHORITY\SYSTEM and BUILTIN\Administrators (well-known SIDs, so it works on any Windows language). Explicit grants to Everyone, Users and Authenticated Users are removed. Hardening one service account never removes another service account's entries, but the next elevated start removes explicit entries for any principal that is neither Administrators, Local System nor a service account in the database. The access is taken back when a service goes away: after a service is uninstalled, or reinstalled under another account, Servy always removes the former account's entries from that service's log folder, logs\services\<ServiceName>\ (the folder and its logs are kept, for the administrators). When no other service in the database still runs under that account (the same account written as .\user or MACHINE\user counts as one), Servy also removes the account's explicit entries from the vault root, db\, security\, every file and folder under logs\ and under the legacy recovery\ folder (while it is still there), and every hardened file, and revokes its access to the named pipe. A failure is logged and does not fail the uninstall. The service account can delete nothing outside the log folders of its own services, and no folder. A file that is a symbolic link or a junction, or that has more than one hard link, is left untouched and reported as a failure. A binary that is not present yet (for example Servy.Service.CLI.exe when only the Desktop App has been used) is hardened once it is extracted.

When an existing installation is upgraded to v10.2, the grants that earlier versions gave the service accounts on db\, logs\, recovery\, Servy.db and aes_key.dat are removed, and the log folder of each service is created and granted to its account, the next time the vault is hardened for each account, which happens on the first elevated start of the Desktop App, Servy Manager or servy-cli after the upgrade. The recovery\ folder itself is imported into Servy.db once, by the database migration of the upgrade, and deleted once its counters have been imported. A counter file is imported whatever its owner, so a service that ran under its own account before the upgrade keeps its counter. A counter file that is a symbolic link or junction, or has more than one hard link, is skipped with a security warning in the log, and is not deleted; the folder is then kept.

Since v10.2, Servy.Restarter.exe is extracted by the Desktop App, Servy Manager and servy-cli rather than by the service, so the service account needs no Delete right on it.

Behavior Once Hardened: The hardening breaks ACL inheritance on Servy's core binaries and restricts explicit execution rights, so a custom service account added later does not inherit rights on the hardened binaries from the %ProgramData%\Servy parent directory. Servy hardens each account when a service is installed under it; an account that is only granted permissions on the vault root by hand would be denied execution rights on Servy.Service.exe by the Windows Service Control Manager (SCM) (ERROR_ACCESS_DENIED).

The executable permission hardening protects your deployment against:

  • Unprivileged Binary Replacement & Tampering: Prevents a compromised service process or runner account from overwriting core binaries (Servy.Service.exe, Servy.Restarter.exe, Servy.Host.exe, etc.) to execute arbitrary code under elevated administrative contexts.
  • DLL Hijacking: Blocks rogue or compromised non-admin identities from planting or replacing shared .dll dependencies within the application vault directory.
  • Local Privilege Escalation (LPE): Ensures that service runner privileges cannot be leveraged to gain unauthorized write access over executable components executed by SYSTEM or Administrators.
  • Configuration Database Access: A service account cannot open db\Servy.db at all. It can neither read the configuration of other services nor change, replace or delete the database.
  • Encryption Key Access: A service account cannot open security\aes_key.dat, so it can neither read nor replace the key every Servy process trusts.
  • Cross-Service Log Access: A service account cannot read, write or delete Servy's own logs or the log folder of any service that runs under another account.

Important

Administrative Service Accounts: Use a dedicated, unprivileged service account. Because Windows evaluates all group SIDs attached to an access token and Allow ACEs do not subtract rights, any member of BUILTIN\Administrators inherently keeps FullControl over the hardened files through group membership, which is why Servy skips the hardening for such an account. Membership through a nested group (for example a domain group added to the local Administrators group) cannot always be detected; Servy then applies the hardening and logs that it could not rule it out.

Note

Starting from v9.7, Servy automatically captures and preserves existing explicit Access Control Lists (ACLs) across atomic file updates for *.exe and *.dll files located in %ProgramData%\Servy. Once configured, your Read & Execute permission boundaries will persist automatically across application updates and embedded resource re-extractions.

Servy 10.1 and Earlier

Servy 10.1 and earlier do not harden the vault automatically. Run Set-ServyExePermissions.ps1 from an Elevated (Administrator) PowerShell session for every custom account (or a group containing it) that runs a Servy service. Without it, the account has no access to the vault, or, if the vault was granted Modify by hand, it keeps inherited Modify rights on the core binaries and can replace or tamper with code executed by SYSTEM and Administrators.

  • Servy v9.7 to 10.1: the script is located in %ProgramFiles%\Servy after installation, and in the root of the portable package. That copy does not grant the vault access: with it, first grant the account Modify on %ProgramData%\Servy (inherited by subfolders and files), or use the version linked below.
  • Latest script for these versions (it keeps the Delete right on Servy.Restarter.exe that these versions need, because their service extracts the restarter itself):
# LocalService account
.\Set-ServyExePermissions.ps1 -TargetAccount "LocalService"

# Local account / relative notation
.\Set-ServyExePermissions.ps1 -TargetAccount ".\user_svc"

# Active Directory domain user or group
.\Set-ServyExePermissions.ps1 -TargetAccount "MYDOMAIN\svc-servy"

# Group Managed Service Account (gMSA)
.\Set-ServyExePermissions.ps1 -TargetAccount "CORP\app-gmsa$"

The script applies the hardening of those versions: write access to db\, logs\ and recovery\, Read & Execute on the binaries and Read on the configuration files. It does not cover aes_key.dat. Re-running it for another account preserves the permissions of the accounts it hardened before. If Servy.db is recreated (for example after purging db\ on a cloned VM), re-run it.

Tip

Managing Multiple Service Accounts: On these versions, if you manage dozens of custom runner accounts on a single server, consider creating a dedicated local or domain security group (e.g., Servy-ServiceRunners), running Set-ServyExePermissions.ps1 -TargetAccount "Servy-ServiceRunners" once, and adding each new service account to that group.

Important

Prior to v9.7, ACLs for *.exe and *.dll files inside %ProgramData%\Servy are not preserved across atomic updates. Upgrading or re-extracting binaries on older versions will cause new files to inherit default directory permissions (Modify), requiring Set-ServyExePermissions.ps1 to be re-run after each update to restore Read & Execute hardening.

What This Means for Your Deployment

  • Per-Service Isolation: Each service account reaches only its own configuration and runtime state, through the Servy service, and only the log folders of its own services. It cannot read the configuration database, the encryption key, the logs of services running under other accounts or Servy's own logs, and it cannot change the records of another service.
  • Per-Service Log Folders: Each service writes its wrapper and restarter logs to its own folder, logs\services\<ServiceName>\, which only its own account (plus SYSTEM and Administrators) can reach. The stdout/stderr logs of your application are written wherever you configure them and are not affected.
  • The Servy Service Must Run: A service cannot start without its configuration, so every Servy service depends on the Servy service. If it is stopped or disabled, the Desktop App, Servy Manager and servy-cli start it again on their next run; you can also start it from services.msc.
  • Zero Trust Isolation: Per-service isolation protects co-located services from each other's service accounts. It does not protect against an administrator or SYSTEM, and it does not isolate the applications themselves (files, network, registry). If your security architecture requires strict multi-tenant isolation, services should be deployed across distinct Virtual Machines, isolated OS installations, or Windows Containers.

Automatic Permissions Table (v7.9+)

Identity Access Level Managed By Description
SYSTEM Full Control Servy (Automatic) Required for local system service host management.
Administrators Full Control Servy (Automatic) Required for administrative configuration.
Installing User Full Control Servy (Automatic) Preserved for operational continuity when installed non-elevated.
Custom Service Accounts None on the vault root, db\, security\ and logs\; write access to the log folder of each of their own services (logs\services\<ServiceName>\) only, Read & Execute on the binaries, Read on the configuration files (see What Is Hardened) Servy (Automatic, v10.2+) Granted by Servy when a service is installed under a non-SYSTEM identity. On 10.1 and earlier, Modify on the vault, granted by Set-ServyExePermissions.ps1 run by hand.
Standard Users None Servy (Automatic) Explicitly purged on startup to prevent standard user tampering.

Note

In Servy's ACL hardening context, Standard Users refers to broad identity groups including Everyone, Users, or Authenticated Users.

Note

The installing user ACE is added by SecurityHelper.ApplySecurityRules only when the current process identity is neither SYSTEM nor an Administrator. An elevated (admin) install adds no ACE of its own: that account is already covered by the BUILTIN\Administrators grant.

Important

If you configure a service to run under a custom local or domain Service Account (or gMSA), that account needs write access to the log folder of its service (logs\services\<ServiceName>\), read access to the binaries and configuration files, and access to the Servy service's named pipe. Starting from v10.2 Servy grants exactly that automatically when it installs the service; on 10.1 and earlier, run Set-ServyExePermissions.ps1 for the account, which grants Modify on %ProgramData%\Servy. Without that access, the service runner will fail to load its configuration or write its logs. See Executable Permission Hardening section for details.

File Locations and Recovery

Your master encryption keys are stored here:

  • Database: %ProgramData%\Servy\db\Servy.db (opened only by the Servy service, Servy Manager, the Desktop App and servy-cli)
  • Key: %ProgramData%\Servy\security\aes_key.dat
  • IV: %ProgramData%\Servy\security\aes_iv.dat

The aes_iv.dat file holds the static IV used by the legacy v1 cipher format (Servy < 6.5). Servy 6.5+ uses a per-message random IV embedded in the ciphertext, so the static IV is no longer used to encrypt or decrypt anything in current builds - v1 decryption is permanently disabled (AllowLegacyV1Decryption = false) to mitigate downgrade attacks. While disabled, the file is not read on service start and is not loaded into memory; the entire load path is compiled out via the AllowLegacyV1Decryption constant, so the runtime no longer creates aes_iv.dat on fresh installs; the file is only present on machines that were first set up by an older (pre-gating) build, where it should be retained - do not delete it. To migrate records written by a pre-6.5 build, export them with a v1-compatible Servy version and import the resulting file into the current version; they will be re-encrypted as v2.

Critical Warning: Machine Migration

Because the encryption is tied to your specific Windows installation, you cannot copy the .dat files to a new server.

To move Servy to a new PC, use Servy-Dump.ps1 and Servy-Restore.ps1 to export and import your service configurations. If you attempt to copy the .dat files to a new machine, Servy will fail to decrypt any of the sensitive fields.

Best Practices

  • Backup the Whole Servy Data Folder: Before doing a Windows Reset or Refresh, back up the entire %ProgramData%\Servy\ tree. The keys (security\*.dat) and the encrypted configuration database (db\Servy.db) must be restored together - keys alone cannot decrypt anything, and the database alone cannot be decrypted without the matching keys.
  • Use Managed Accounts: When possible, run services under Group Managed Service Accounts (gMSA) for the best balance of security and ease of use.
  • Audit Access: Periodically check the Security tab of the %ProgramData%\Servy folder to ensure no unauthorized users have been added manually.

Troubleshooting

  • Access Denied on Startup: This usually means the account running the service does not have permissions to the %ProgramData%\Servy folder. Refer to the Permissions Table above.
  • Service Cannot Load Its Configuration (v10.2+): Check that the Servy service is installed and running (sc.exe query Servy), then read %ProgramData%\Servy\logs\Servy.Host.log and %ProgramData%\Servy\logs\services\<ServiceName>\Servy.Service.log. Running the Desktop App, Servy Manager or any servy-cli command reinstalls and starts the Servy service if needed; reinstalling the service re-grants its account access to the named pipe. The one exception is a service named Servy that does not run Servy.Host.exe or Servy.Host.Net48.exe, for example one installed under that name by an earlier version: Servy leaves it untouched and reports an error that names it, so the host service is not installed and the Servy services cannot load their configuration. Export that service, uninstall it, and import it under another name, then run any Servy command again. The same happens when the executable of the existing Servy service cannot be read.
  • Decryption Error: This happens if the .dat files were moved from another computer, restored from an image, corrupted, or if the Windows MachineGuid was altered. Servy then refuses to save any service whose encrypted fields (Password, Parameters, EnvironmentVariables, PreLaunchEnvironmentVariables and the hook parameters PreLaunchParameters, PostLaunchParameters, PreStopParameters, PostStopParameters, FailureProgramParameters) cannot be decrypted, so they cannot be re-entered in place, and it never replaces an existing aes_key.dat. To recover:
    1. Do not delete or replace aes_key.dat or aes_iv.dat: a new key can never decrypt the existing database.
    2. Restore %ProgramData%\Servy\security\aes_key.dat and aes_iv.dat from a backup of this machine, then restart Servy.
    3. Otherwise, copy %ProgramData%\Servy\db\Servy.db back to the original machine and export the service configurations to XML or JSON there. You can use Servy-Dump.ps1 to perform the batch export.
    4. Only then, on this machine, back up and delete %ProgramData%\Servy\security and %ProgramData%\Servy\db, and import the exported configurations (per Critical Warning: Machine Migration). Usernames and passwords are not exported and must be re-entered. You can use Servy-Restore.ps1 to perform the batch import.

Servy v7.9+ automates the routine parts of Windows security - Access Control Lists (ACLs) and machine-bound key derivation - so a locked-down setup is the default rather than a manual checklist.

Questions? Check the full Troubleshooting Guide or open a GitHub Issue to get help from the community and developers.

Clone this wiki locally