rfd/0242-improve-windows-installation-experience.md
When we shipped VNet for Windows in early FY2026, the installer for Teleport Connect was switched from a per-user install (which does not require admin permissions) to a per-machine install (which does require admin permissions). This has created friction for users without local admin rights.
The application was moved to a per-machine installation to support the VNet system service (TeleportVNetService
implemented by tsh.exe vnet-service). This service manages the TUN device configuration and related networking tasks.
To prevent privilege escalation, the service binary must reside in a secure directory that grants write access only to
administrators. The per-machine installation path (C:\Program Files\Teleport Connect\bin\tsh.exe) was ideal for this
purpose.
Furthermore, because the per-machine installer requests elevated privileges during setup, it can register the service
with the Service Control Manager (SCM) seamlessly, without requiring additional user prompts.
To accommodate standard users who lack the permissions to write to Program Files and install system services, the app will provide a dual-mode installer:
| Per-User | Per-Machine | |
|---|---|---|
| Install directory | %LocalAppData% | %ProgramFiles% |
| Elevation required | No (Standard user) | Yes (Administrator/UAC) |
| VNet support | Disabled | Enabled |
| Update mechanism | Default user-space process | Privileged Windows service |
| System registry hive for updater settings | HKCU / HKLM (HKLM takes precedence) | HKLM |
During the runtime, the app will detect the per-machine mode by checking if it's been installed to the path specified in
HKLM\Software\22539266-67e8-54a3-83b9-dfdca7b33ee1\InstallLocation. This registry entry is created by electron-builder
when installing the app. The UUID is stable, generated from the appId and electron-builder's internal ID.
It will be also hardcoded in nsis.guid in electron-builder config.
Based on this check, the following update logic can be applied:
We will reintroduce the unprivileged installation path for users who do not need VNet functionality.
By setting oneClick: false, perMachine: false, and selectPerMachineByDefault: true in the electron-builder
configuration, users can choose between "Install for me" (unprivileged) and "Install for all users" (privileged).
The "Install for all users" option will be preselected, so users will end up with VNet support by default.
Note: IT admins generally don't like apps that install to
%LocalAppData%, since they are more difficult to manage through centralized management tools. We considered migrating from the current NSIS installer to the MSIX format that works better with enterprise deployment tools, however, MSIX doesn't allow any customization of the installation process. Adopting this format would require decoupling the VNet service from the application, leaving us without a clear mechanism on how to update the standalone service.
Since VNet is exclusive to the per-machine installation, the installer will explicitly state it:
Only the per-machine option comes with VNet, Teleport's VPN-like experience for accessing TCP applications and SSH servers.
If a user attempts to run VNet in a per-user installation, the app will verify the lack of the system service and display an error:
VNet system service not found. To use VNet, install Teleport Connect in a per-machine mode. Administrator privileges will be required.
In this mode, the installer will install both the application and the VNet service.
To install per-machine updates silently, we will introduce a special Update Service, implemented by tsh.exe
(tsh.exe connect-updater-service).
This service will run with full system permissions, and its only job will be to install updates that the main app has
already downloaded.
We will set up the service DACL so that any authenticated user can start the service and check its status. The service must be extremely careful and must not implicitly trust any input data.
Even though the update service will only allow installing binaries signed by the same organization as the service itself (enforced if the service is signed), an attacker could force it to install an older, vulnerable version of the VNet service.
This is possible because of two reasons: Teleport Client Tool Managed Updates rely on clusters specifying a required client version and Teleport Connect is multi-cluster client. An attacker could trick a user into adding a malicious cluster to the app and setting it as the one managing updates, effectively granting that remote cluster control over the local per-machine service version.
This risk is a direct consequence of our update model. Since different clusters may require specific client versions, we delegate the decision of which cluster to "follow" for updates to the end user (for details see https://github.com/gravitational/teleport/blob/75b56b1c67bd7eccc1074738ece671adccf21ea2/rfd/0144-client-tools-updates.md#multi-cluster-support). While a similar risk exists for per-user installations, the impact is isolated to the user's local space. With a per-machine service, a standard user's decision to follow a malicious cluster could compromise the security of the entire machine.
To reduce the risk of malicious version changes, client tool updates in Connect will only permit upgrades (currently downgrades are allowed). This restriction will be enforced in both the user-space application and the privileged updater. Since most Connect users are logged into one (or at most two) clusters, frequent version switching is uncommon, and this change should be largely unnoticeable to them.
Important: this measure does not fully eliminate the risk, as Teleport maintains two release branches simultaneously. As a result, a version having higher major number doesn't necessarily have to be a newer one, for example, the latest N-1.x.x release may be more recent than an N.0.0 version published months earlier. However, this change does make the auto-update system more predictable: once the application is on the latest version, it cannot downgrade to an older release.
Additionally, in case of a serious vulnerability, a patch release could modify autoupdate thresholds for the app (and for the service) to disallow updates from a version N-1.x.x to release below certain N.x.x version.
There is an open question whether the no-downgrade policy should also apply to versions specified via the
TELEPORT_TOOLS_VERSION environment variable or the ToolsVersion registry value.
The TELEPORT_TOOLS_VERSION environment variable was originally designed to pin a specific version for debugging,
testing, or manual update scenarios for CLI tools. It was implemented in Connect primarily for compatibility; however,
changing the version of a desktop application through an environment variable is neither common nor convenient. While
moving update control to the system registry improves the user experience, it does not seem to justify increasing
the complexity of the autoupdate rules by allowing downgrades in certain cases.
For most users, it is sufficient to disable updates entirely by setting the value to off.
Download an update.
Triggering the service.
tsh.exe , passing the file path and
version as arguments.app.on('quit') handler after the tsh daemon has exited.Transferring the update to the service.
ImpersonateNamedPipeClient from the service side, but this Windows API is not
available in golang.org/x/sys/windows.tsh.exe connect-updater-install-update --path= --update-version= starts the update service and waits for it.\\.\pipe\TeleportConnectUpdaterPipe and accepts only a single
connection from an authenticated user. Any other client must wait until the current service invocation exits.
"D:" + // DACL
"(A;;GA;;;SY)" + // Allow (A);; Generic All (GA);;; SYSTEM (SY)
"(A;;GA;;;BA)" + // Allow (A);; Generic All (GA);;; Built-in Admins (BA)
"(A;;GENERIC_READ|FILE_WRITE_DATA;;;AU)" // Allow (A);;GENERIC_READ | FILE_WRITE_DATA ;;;Authenticated Users (AU)
GENERIC_READ | FILE_WRITE_DATA (not GENERIC_WRITE) so authenticated users can read/write
pipe data
but cannot create pipe instances.winio.ListenPipe calls NtCreateNamedPipeFile with FILE_CREATE flag, which ensures the
call succeeds only if the pipe name does not already exist, preventing pipe-name squatting.Staging the update file.
%ProgramData%\TeleportConnectUpdater directory exists.
"O:SY" + // Owner SYSTEM
"D:P" + // 'P' blocks permissions inheritance from the parent directory
"(A;OICI;GA;;;SY)" + // Allow System Full Access
"(A;OICI;GA;;;BA)" // Allow Built-in Administrators Full Access
ERROR_ALREADY_EXISTS error is returned, open a file handle and verify it's a directory (and not a reparse
point). Reapply the security descriptor to ensure no unauthorized permissions persist.os.RemoveAll; its behavior has been checked to ensure it does
not traverse reparse points and therefore does not delete data outside the target directory.%ProgramData%\TeleportConnectUpdater\<GUID>
This provides isolation from any malicious DLL that could be loaded from the same directory, resulting in an LPE vector.
Validation.
ToolsVersion value from HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Teleport\TeleportConnect.
off, the service exits early.tsh.exe) version.Installation.
Update settings will be managed via the Windows Registry. Configuration can be applied at either the machine level (HKLM) or the user level (HKCU).
HKEY_LOCAL_MACHINE take priority over HKEY_CURRENT_USER.TELEPORT_TOOLS_VERSION and TELEPORT_CDN_BASE_URL env variables will be deprecated. This prevents standard users
from overriding versioning policies.
Registry Paths:
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Teleport\TeleportConnectHKEY_CURRENT_USER\SOFTWARE\Policies\Teleport\TeleportConnect - must be used only in the context of per-user
installations,
will not affect per-machine behavior.| Setting | Type | Description |
|---|---|---|
| ToolsVersion | REG_SZ | Pins the application to a specific X.Y.Z version or disables updates entirely (off). Replaces TELEPORT_TOOLS_VERSION. |
| CdnBaseUrl | REG_SZ | Specifies a custom build source or CDN mirror in a private network. Replaces TELEPORT_CDN_BASE_URL. |
As mentioned earlier, the VNet service could be separated from Teleport Connect. The app would install in user-space, while the VNet service would be treated as a per-machine, on-demand component.
Pros:
Cons:
Teleport VNet.exe that would consist of tsh.exe,
wintun.dll and msgfile.dll.Teleport VNet.exe were executed as part of the Teleport
Connect installation.