Skip to content

Environment Variables

Christophe-Rogiers edited this page Oct 12, 2026 · 57 revisions

Table of Contents

  1. Introduction
  2. Environment Variable Expansion
  3. GUI
  4. CLI Option: --envVars
  5. CLI Example
  6. PowerShell Option: -EnvVars
  7. PowerShell Example
  8. Tips

Introduction

Servy allows you to define environment variables for the service process, enabling fine-grained control over the runtime context.

Environment variable expansion is supported in multiple areas of Servy configuration:

  • Service binary paths (--path / -Path)
  • Startup directories (--startupDir / -StartupDir)
  • Process parameters (--params / -Params)
  • Environment variables (--envVars / -EnvVars)

This makes it possible to reference existing system variables, user-defined variables, or paths dynamically in your configuration.

Note

All hooks inherit the main service's EnvironmentVariables except Pre-Launch hook. There is no separate Post-Launch / Pre-Stop / Post-Stop / Failure-Program EnvironmentVariables setting - only Pre-Launch supports a per-hook override.

Environment Variable Expansion

Servy uses a sophisticated expansion engine that allows variables to reference each other. The resolution follows this specific order:

  1. System Environment: All current system and process environment variables are loaded first. Since v10.2, the service first brings them up to date with the System variables in the registry (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment), so a System variable added or changed in Windows is seen without restarting the machine (a removed one is dropped by the apps only, not by the service) - see the note below.
  2. Custom Overrides: Variables defined in the Environment Variables field (or --envVars) are added next. If a custom variable has the same name as a system variable, the custom value wins.
  3. Cross-Reference Expansion: Finally, all variables are scanned for %VAR% placeholders. These placeholders are resolved using the final merged set of variables.

Note

New and changed System variables (v10.2+). Windows gives a process its environment when it starts, and a Windows service inherits the environment of the Service Control Manager, which is only built when the machine starts. Since v10.2, whenever Servy expands a %VAR% placeholder - in the paths, startup directories, parameters and environment variables above - it first reads the System variables from the registry and applies what changed. The read is read-only and needs no administrator rights, so it works for the service under any account and for the Desktop App, Servy Manager and the CLI alike.

  • The service (Servy.Service.exe): every System variable takes its current registry value, so an added or changed variable reaches the service and its process the next time the service (or a hook) starts its process - restarting the service is enough, the machine does not need a restart. A variable removed from the registry is not removed from the service: it keeps the value it inherited from the Service Control Manager, which is only rebuilt when the machine starts. A variable also defined as a User variable of the service account keeps the User value, as in Windows, and Path is the System Path followed by the user's.
  • The Desktop App, Servy Manager and the CLI: the System variables are recorded when the app starts. A variable added later is picked up; a variable changed or removed later is picked up as long as the app still holds the value it started with. A value the app got from a User variable, or from the program or console that started it, is kept.
  • Variables Windows sets per account (such as USERNAME, USERPROFILE, APPDATA) are never taken from the System key. User variables changed after start are not picked up; restart the app for those.

Protected Variables

For security reasons, Servy restricts --envVars / -EnvVars and --preLaunchEnv / -PreLaunchEnv from overriding a core set of variables that would otherwise enable privilege escalation, runtime injection (DLL/JIT profiler hijacking, debugger probes, search-path attacks, etc.), or process hijacking.

While a user with sufficient privileges to install services already has full system access, blocking overrides of protected variables provides an additional layer of safety, improves auditability, and helps prevent unintended side effects. If an attacker has enough privileges to compromise the system, they are forced to make changes through standard Windows administration tools that trigger standard system logs and EDR alerts. They cannot use Servy as a silent, stealthy mechanism for local process injection or persistence.

Variables are divided into two distinct security tiers:

  1. Immutable Core System Variables (Tier 1): Essential operating system integrity boundaries that can never be overridden under any circumstances.
  2. Overridable Runtime Variables (Tier 2): Application, framework, and diagnostic variables blocked by default to prevent injection, but which can be overridden on a per-service basis if explicitly permitted by configuration policy (AllowOverriddenRuntimeVars).

The setting applies to the main process and to every hook (pre-launch, post-launch, pre-stop, post-stop and the failure program), including the pre-launch environment variables.

Any attempt to override a blocked variable is ignored each time the service starts (during environment-variable expansion), and a warning is recorded in %ProgramData%\Servy\logs\services\<ServiceName>\Servy.Service.log:

Security: Blocked an attempt to override protected runtime variable 'JAVA_OPTS'. Custom values for this variable are ignored to prevent runtime injection. Enable 'AllowOverriddenRuntimeVars' in service settings to allow this override.

A Tier 1 variable is always ignored, with this warning:

Security: Blocked an attempt to override immutable system variable 'PATH'. Custom values for this variable are ignored to prevent core OS instability or privilege escalation.

Tier 1: Immutable Core System Variables (Never Overridable)

These variables protect core Windows operating system paths, shell binaries, profile identity scopes, and security policy boundaries. They remain strictly untouchable regardless of configuration settings:

Category Environment Variables Security / Integrity Purpose
System Integrity PATH, COMSPEC, SYSTEMROOT, WINDIR, SYSTEMDRIVE, TEMP, TMP, PATHEXT, PROGRAMFILES, PROGRAMFILES(X86), PROGRAMW6432, COMMONPROGRAMFILES, COMMONPROGRAMFILES(X86), COMMONPROGRAMW6432 Protects core operating system paths, shell execution binaries, executable extension resolution rules, and system-wide temporary layout storage areas from redirection or manipulation.
Profile & Identity Redirection APPDATA, LOCALAPPDATA, PUBLIC, HOMEDRIVE, HOMEPATH, HOME, USERDOMAIN, USERDOMAIN_ROAMINGPROFILE, LOGONSERVER Prevents the malicious redirection of active user profile locations, local cache repositories, home path scopes, or domain network logons which could allow credential harvesting or state poisoning.
User & Profile Integrity USERNAME, USERPROFILE, ALLUSERSPROFILE, PROGRAMDATA, PSMODULEPATH Safeguards system configuration and initialization boundaries, tracking contexts, and default PowerShell utility module path discovery scopes from arbitrary interference.
Windows AppCompat & Symbol Debugging __COMPAT_LAYER, SHIM_FILE_LOG, SHIM_DEBUG_LEVEL, _NT_SYMBOL_PATH, _NT_ALT_SYMBOL_PATH, _NT_SOURCE_PATH, MICROSOFT_TELEMETRY_ENV_OVERRIDE Hardens execution trees against debugging diagnostics vectors, system symbol repository redirections, and application compatibility shim injection layer techniques.
Global / Unix Compatibility LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT Controls dynamic tracking or link-editor behaviors within Unix compatibility frameworks, MinGW subsystems, WSL boundaries, or Cygwin sandboxes to stop arbitrary binary instrumentation.
PowerShell Hardening Bypass __PSLockDownPolicy, PSExecutionPolicyPreference Mitigates unauthorized administrative policy changes at process initialization by blocking programmatic overrides targeting Windows execution restrictions or the system LanguageMode.

Tier 2: Overridable Runtime Variables (Blocked by Default)

These variables control framework execution options, classpaths, diagnostic attaches, and runtime environments. They are blocked by default to prevent runtime injection, but can be unlocked on a per-service basis:

Category Environment Variables Security / Integrity Purpose
.NET Runtime Injection & Diagnostics COR_ENABLE_PROFILING, COR_PROFILER, COR_PROFILER_PATH, CORECLR_ENABLE_PROFILING, CORECLR_PROFILER, CORECLR_PROFILER_PATH, DOTNET_STARTUP_HOOKS, DOTNET_ROOT, DOTNET_ROOT(x86), DOTNET_HOST_PATH, DOTNET_BUNDLE_EXTRACT_BASE_DIR, DOTNET_ADDITIONAL_DEPS, DOTNET_SHARED_STORE, DOTNET_DiagnosticPorts, COMPlus_DiagnosticPorts, DOTNET_EnableDiagnostics, COMPlus_EnableDiagnostics, DOTNET_EnableDiagnostics_IPC, COMPlus_EnableDiagnostics_IPC, DOTNET_EnableDiagnostics_Profiler, COMPlus_EnableDiagnostics_Profiler, DOTNET_EnableEventPipe, COMPlus_EnableEventPipe, DOTNET_GCName, COMPlus_GCName, DOTNET_GCPath, COMPlus_GCPath, DOTNET_LegacyHostPolicy, COMPlus_LegacyHostPolicy, DOTNET_LegacyTransform, COMPlus_LegacyTransform, DOTNET_PerfMapEnabled, COMPlus_PerfMapEnabled, DOTNET_ZapDisable, COMPlus_ZapDisable, DOTNET_DbgEnableMiniDump, COMPlus_DbgEnableMiniDump, DOTNET_DbgMiniDumpName, COMPlus_DbgMiniDumpName, DOTNET_DbgMiniDumpType, COMPlus_DbgMiniDumpType Shuts down execution hijacking and process attachment pathways targeting legacy, modern, and cross-compiled .NET/CoreCLR runtimes. This blocks unauthorized remote diagnostic socket endpoint exposure (DiagnosticPorts), runtime profiler/assembly DLL injection setups, custom Garbage Collector (GCPath) hijacking vectors, and the redirection of sensitive process memory dumps (DbgMiniDumpName) to insecure disk areas.
Java Injection JAVA_TOOL_OPTIONS, _JAVA_OPTIONS, JDK_JAVA_OPTIONS, JAVA_OPTS, JAVA_OPTIONS, CATALINA_OPTS, CATALINA_JAVA_OPTS, MAVEN_OPTS, M2_OPTS, GRADLE_OPTS, ANT_OPTS, JBOSS_JAVA_OPTS, WILDFLY_OPTS, CLASSPATH, JAVA_HOME, JRE_HOME, JDK_HOME Blocks execution exploits (such as malicious -javaagent arguments) loaded through native JVM diagnostics (java.exe launcher flags) or shell-wrapper scripts commonly deployed in enterprise application frameworks like Tomcat, JBoss, Maven, and Gradle.
Node.js & NPM Injection NODE_OPTIONS, NODE_PATH, NODE_EXTRA_CA_CERTS, NPM_CONFIG_PREFIX, NPM_CONFIG_USERCONFIG, NPM_CONFIG_GLOBALCONFIG Defends Node.js runtime instances against preload option injections, local dependency resolution adjustments, rogue Certificate Authority additions (preventing Man-In-The-Middle traffic decryption), and npm registry config tampering.
TLS Trust Store & OpenSSL Configuration OPENSSL_CONF, OPENSSL_MODULES, SSL_CERT_FILE, SSL_CERT_DIR, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY Prevents arbitrary code execution via OpenSSL dynamic engine/provider module loading (OPENSSL_CONF, OPENSSL_MODULES), blocks silent Man-In-The-Middle (MITM) traffic decryption by preventing malicious overriding or swapping of trusted CA certificate bundles, and stops outbound HTTP/HTTPS network traffic redirection through attacker-controlled proxies.
Python Injection PYTHONSTARTUP, PYTHONPATH, PYTHONHOME, PYTHONUSERBASE, PYTHONEXECUTABLE Prevents arbitrary script parsing or site-package source location hijacking through specialized system initialization hooks, site library layout directories, or custom interpreter path redirections.
Ruby & Perl Injection RUBYOPT, RUBYLIB, PERL5OPT, PERL5LIB, PERLLIB Disallows option mapping switches and internal code inclusion path manipulations within local Ruby and Perl execution setups.
PHP Injection PHPRC, PHP_INI_SCAN_DIR Blocks attackers from supplying custom configuration directives or dynamically scanning arbitrary directories for malicious extension modules via modified initialization file search paths.

Overriding Tier 2 Runtime Variables

If your application requires custom runtime options (such as custom heap memory limits in JAVA_OPTS or CATALINA_OPTS for Apache Tomcat), you can explicitly permit Tier 2 overrides for the service:

  • Desktop Configurator UI: Navigate to the Advanced tab -> check Allow Overridden Runtime Environment Variables (e.g., JAVA_HOME, JAVA_OPTS). The See list of runtime variables. link below it opens this section.
  • CLI: Pass the --allowOverriddenRuntimeVars flag when installing the service (to change the setting on an existing service, run install again):
servy-cli install --name="TomcatService" --path="cmd.exe" --params="/c C:\Tomcat\bin\catalina.bat run" --envVars="JAVA_HOME=C:\JDK21;CATALINA_OPTS=-Xmx2048m" --allowOverriddenRuntimeVars
  • PowerShell: Pass the -AllowOverriddenRuntimeVars switch to Install-ServyService.
  • Import/Export: Set AllowOverriddenRuntimeVars to true in the XML or JSON configuration (see Export/Import Services).

When enabled, Servy logs an audit record (Info level) for each overridden variable every time the service's process or one of its hooks is started, confirming that the runtime variable override was explicitly authorized:

Security Audit: Override for protected runtime variable 'CATALINA_OPTS' permitted by service policy configuration.

Note

Tier 1 Core System Variables (such as PATH, SYSTEMROOT, or COMSPEC) remain strictly protected and cannot be overridden even when AllowOverriddenRuntimeVars is enabled. If you need to extend PATH for a service, modify the System environment variables or reference custom paths directly in process parameters.

Example of Expansion Logic

If your system has TEMP=C:\Windows\Temp and you define:

  • MY_ROOT=C:\ServyApp
  • MY_LOGS=%MY_ROOT%\logs
  • APP_TEMP=%TEMP%

The final environment seen by your process will be:

  • MY_ROOT: C:\ServyApp
  • MY_LOGS: C:\ServyApp\logs
  • APP_TEMP: C:\Windows\Temp

Note

Variable Ordering and Circular References: Variables are resolved using a multi-pass fixed-point algorithm, so the order in which you define them does not matter - MY_LOGS=%MY_ROOT%\logs resolves correctly even if MY_ROOT is defined later in the list. The expansion engine runs up to 5 passes (AppConfig.MaxEnvVarExpansionPasses); chains deeper than that will leave unresolved %VAR% placeholders with no warning logged - the loop exits silently once the pass cap is reached while values are still changing.

Avoid circular references:

  • Self-reference (A=%A%): caught as soon as the expansion engine encounters a value that contains its own %NAME% token. Logged as Direct cycle detected for variable 'A'; leaving literal placeholder. and the placeholder is left literal (unless the OS already exports a value for A, in which case the inherited value is substituted to mimic Windows PATH-append semantics).
  • Multi-variable cycles (A=%B%, B=%A%, or longer chains like A=%B%, B=%C%, C=%A%): not detected by a dedicated check. Two-variable or indirect cycles typically degenerate into a direct self-reference (%A% inside A) during intermediate resolution and surface as Direct cycle detected for variable 'A' on a subsequent pass. If a cycle does not degenerate within 5 passes, resolution halts silently when the pass limit is reached, leaving the remaining %VAR% placeholders intact in the expanded environment.

GUI

The advanced tab in Servy allows setting environment variables for the service process:

servy-config-advanced

CLI Option: --envVars

The --envVars command-line option lets you specify environment variables for the service process.

  • Syntax: --envVars="VAR1=value1; VAR2=value2"
  • Separate multiple variables with semicolons (;) - Special characters can be escaped:
    • \= to escape =
    • \" to escape "
    • \; to escape ;
    • \\ to escape \
    • %% to escape % (collapses to a single %, matching cmd.exe behaviors) (starting from v8.5+)
  • Supports environment variable expansion. Example: --envVars="VAR1=%ProgramData%\MyApp; VAR2=%VAR1%\bin; CHANCE=100%%"
  • Useful for setting runtime context without changing system-wide environment variables.

CLI Example

servy-cli install `
  --name="MyNodeService" `
  --description="My NodeJS Server" `
  --path="%ProgramFiles%\nodejs\node.exe" `
  --startupDir="C:\Apps\App" `
  --params="C:\Apps\App\index.js" `
  --startupType="Automatic" `
  --envVars="NODE_ENV=production; APP_CONFIG=C:\Apps\App\config.json"

PowerShell Option: -EnvVars

The -EnvVars parameter lets you define environment variables when installing a service via PowerShell.

  • Type: string (semicolon-separated list, optional)
  • Apply the same escaping rules as the CLI.
  • Multiple variables are separated with semicolons (;)
  • Variables are applied only to the service process, not system-wide.

PowerShell Example

Import-Module "C:\Program Files\Servy\Servy.psm1" -Force

$installParams = @{
    Name         = "MyNodeService"
    Description  = "My NodeJS Server"
    Path         = "C:\Program Files\nodejs\node.exe"
    StartupDir   = "C:\Apps\App"
    Params       = "C:\Apps\App\index.js"
    StartupType  = "Automatic"

    EnvVars      = "NODE_ENV=production; APP_CONFIG=C:\Apps\App\config.json"
}

Install-ServyService @installParams

Tips

  • Case Insensitivity: Environment variable names are case-insensitive. Defining node_env will correctly override an existing NODE_ENV.
  • Expansion Order: You can reference both existing system variables (like %ProgramData%) and other custom variables defined in the same list.
  • Safe Percent Escaping: To pass a literal percent character into your environment safely and prevent it from being processed as an expansion block, use a double percent sign (%%). For example, defining ALERT_MSG=Battery at 100%% will expand correctly to Battery at 100% inside the service process.
  • Verification: To troubleshoot, if you aren't sure if your variables are applying correctly, run this to dump the environment to a file (PowerShell Admin):
servy-cli install --name="EnvTest" --startupType="Manual" --path="C:\Windows\System32\cmd.exe" --params="/c set > C:\servy_env.txt && timeout /t 3600 /nobreak > nul" --envVars="MY_ROOT=C:\ServyApp; MY_LOGS=%MY_ROOT%\logs"
servy-cli restart --name="EnvTest"
Start-Sleep -Seconds 3
Get-Content C:\servy_env.txt | Select-String "MY_LOGS"

# Clean up when you are done
servy-cli uninstall --name="EnvTest"

Clone this wiki locally