Windows Group Policy¶
On Windows, dotvault can be fully configured via Group Policy using the Windows Registry. This allows centralised management of dotvault settings across a domain without deploying YAML config files.
How it works¶
When HKLM registry keys exist at SOFTWARE\Policies\goodtune\dotvault, dotvault loads all configuration from the registry and ignores the YAML config file entirely. Only machine-level policy (HKLM) is read — HKCU is intentionally skipped because it is user-writable and therefore cannot be trusted for enforced configuration.
The --config flag is blocked under Group Policy by default
When a policy is present, dotvault refuses a --config path/to/config.yaml override and exits with an error — a managed machine cannot be pointed at an arbitrary config from the command line. To allow the override (for development or troubleshooting on a specific machine), set a BypassSystemConfig REG_DWORD of 1 directly under SOFTWARE\Policies\goodtune\dotvault (the bypass_system_config: true YAML equivalent). With the flag set, an invocation with --config loads the named file and ignores the registry; without it, --config is rejected. The flag is part of the policy, so it stays under the administrator's control rather than the user's.
Authoring the registry values¶
dotvault does not ship an ADMX administrative template. Instead, admins author the registry values directly under SOFTWARE\Policies\goodtune\dotvault and deploy them via Group Policy Preferences > Registry (or any registry-deployment tool: SCCM, Intune, a .reg import, etc.).
The supported authoring workflow is to write the configuration as YAML and convert it to a .reg file with dotvault reg-import:
This emits a canonical Windows Registry Editor Version 5.00 file (UTF-16LE with BOM, matching regedit.exe) targeting HKLM\SOFTWARE\Policies\goodtune\dotvault. Import it into the policy hive, or load it into a GPO's registry preferences. The reverse direction — pulling an existing policy hive back into YAML for review — is dotvault reg-export:
Both commands round-trip the entire configuration without loss — including observability header values (see the credential note below) — so the YAML and .reg forms are interchangeable. The web UI's Effective Configuration screen exposes the same conversion via download buttons.
Registry schema¶
Every YAML field has a registry equivalent. The tables below give the value names; reg-import writes exactly these, and the live loader reads exactly these. The one exception is Vault\TokenSocket, marked as legacy below: the loader still reads it for compatibility, but reg-import only ever writes the Vault\TokenSockets list that supersedes it.
Top-level settings (policy root key)¶
| Registry value | Type | Description |
|---|---|---|
BypassSystemConfig |
REG_DWORD | Allow the --config command-line override on this machine (0/1; default 0). Lives directly under the policy root key, not in a subkey. |
Vault settings (Vault\ subkey)¶
| Registry value | Type | Description |
|---|---|---|
Vault\Address |
REG_SZ | Vault server URL (required) |
Vault\CACert |
REG_SZ | Path to CA certificate |
Vault\TLSSkipVerify |
REG_DWORD | Skip TLS verification (0/1) |
Vault\KVMount |
REG_SZ | KVv2 mount path |
Vault\UserPrefix |
REG_SZ | Per-user path prefix |
Vault\AuthMethod |
REG_SZ | oidc, ldap, token, mtls, or mtls+tpm |
Vault\AuthRole |
REG_SZ | Vault auth role |
Vault\AuthMount |
REG_SZ | Vault auth mount path |
Vault\OIDCCallbackPort |
REG_DWORD | Fixed local TCP port for the OIDC CLI redirect_uri (0 = built-in default of 8250) |
Vault\Policies |
REG_MULTI_SZ | Least-privilege policy set the working token is downscoped to (empty = carry every granted policy) |
Vault\NoDefaultPolicy |
REG_DWORD | Strip the implicit default policy from the working token (0/1) |
Vault\DisableTokenRenewal |
REG_DWORD | Disable RenewSelf (0/1) |
Vault\TokenSockets |
REG_MULTI_SZ | Peer dotvault socket patterns to borrow a token from and fan peer actions out to (literal paths or final-segment globs); an explicitly empty value disables peer sockets, absent applies the defaults |
Vault\TokenSocket |
REG_SZ | Legacy single-path form, read only when TokenSockets is absent; the value ~/.ssh/dotvault.sock expands to the default pair. Removed before 1.0 (#172). |
Vault\BorrowOnly |
REG_DWORD | Forbid a fresh-auth flow entirely; only ever borrow via TokenSockets (0/1) |
Sync settings (Sync\ subkey)¶
| Registry value | Type | Description |
|---|---|---|
Sync\Interval |
REG_SZ | Go duration string (e.g. 15m) |
Web UI settings (Web\ subkey)¶
| Registry value | Type | Description |
|---|---|---|
Web\Enabled |
REG_DWORD | Enable web UI (0/1) |
Web\Listen |
REG_SZ | Listen address (loopback only) |
Web\LoginText |
REG_SZ | Login-page markdown (multi-line via hex(1)) |
Web\SecretViewText |
REG_SZ | Secret-view markdown (multi-line via hex(1)) |
Observability settings (Observability\ subkey)¶
| Registry value | Type | Description |
|---|---|---|
Observability\Enabled |
REG_DWORD | Master switch for the OTLP exporters, both signals (0/1) |
Observability\Endpoint |
REG_SZ | Deprecated shared default (see below). OTLP collector endpoint |
Observability\Protocol |
REG_SZ | Deprecated shared default. grpc or http/protobuf |
Observability\Insecure |
REG_DWORD | Deprecated shared default. Disable transport TLS (0/1) |
Observability\ExportInterval |
REG_SZ | Export interval (e.g. 30s, 1m) |
Observability\Headers\<name> |
REG_SZ | Deprecated shared default. OTLP header value (see note) |
Observability\Metrics\Enabled |
REG_DWORD | Tri-state: absent = inherit, 0 = signal off, 1 = on |
Observability\Metrics\Endpoint |
REG_SZ | Metrics-only endpoint override (separate backend) |
Observability\Metrics\Protocol |
REG_SZ | Metrics-only protocol override |
Observability\Metrics\Insecure |
REG_DWORD | Tri-state: absent = inherit shared |
Observability\Metrics\Headers\<name> |
REG_SZ | Metrics-only headers; the subkey's presence replaces the shared map wholesale (present-but-empty = no headers) |
Observability\Metrics\Temporality |
REG_SZ | Metric temporality preference: cumulative, delta, or lowmemory (metrics subkey only — rejected under Logs) |
Observability\Logs\… |
— | Same values for the log signal; a Temporality value under Logs is rejected at config load (reg-import emits it empty there, which is inert) |
The top-level values are shared defaults driving both signals (metrics and logs) against one collector; the Metrics\ / Logs\ subkeys override them per signal so the two can go to separate backends or one can be switched off — same layering as the YAML metrics: / logs: blocks. The shared exporter values (Endpoint, Protocol, Insecure, Headers directly under Observability) are deprecated in favour of the per-signal subkeys, mirroring the YAML deprecation (#140): the daemon warns at startup and meters each use on dotvault.config.deprecated, and a future release removes them — author new policy under the Metrics\ / Logs\ subkeys. Detection is presence-based, so an explicit Insecure=0 DWORD (which loads as the default false) does not count as a use. On a GPO deployment running dotvaultw.exe (GUI subsystem, no console) the stderr warning is not visible — the dotvault.config.deprecated metric is the observable channel there, which is exactly what it exists for. Endpoint values follow the same contract as YAML: a full URL's scheme carries the TLS intent and an explicit path is used verbatim; a path-less URL gets the OTLP standard /v1/metrics / /v1/logs appended (http/protobuf); bare host:port is the canonical gRPC form with TLS governed by Insecure.
Observability headers carry credentials
OTLP headers typically hold bearer tokens (Datadog / Grafana Cloud / Honeycomb, etc.). Config conversion is lossless in every direction, so reg-export and reg-import do round-trip header values verbatim (each as a REG_SZ value under Observability\Headers) — which means a generated .reg artefact contains the live tokens. Treat it as a secret: store it at restricted permissions and don't check it in. If you would rather keep tokens out of the policy hive and out of any exported artefact, leave headers empty and set them via the per-user EnvironmentFile (OTEL_EXPORTER_OTLP_HEADERS) instead — the SDK falls through to those env vars.
Remote configuration (RemoteConfig\ subkey)¶
| Registry value | Type | Description |
|---|---|---|
RemoteConfig\URL |
REG_SZ | Remote configuration endpoint (https required except loopback hosts) |
RemoteConfig\RefreshInterval |
REG_SZ | Re-fetch cadence (e.g. 15m; default: the sync interval; floor 1m) |
RemoteConfig\CACert |
REG_SZ | Path to a CA bundle pinning the service's TLS certificate |
RemoteConfig\Headers\<name> |
REG_SZ | Extra dimension header sent with every fetch (e.g. X-Dotvault-Env) |
When URL is set, the registry-delivered policy becomes the base configuration and the daemon merges the remotely fetched dynamic sections (rules, enrolments, sync) on top — see Remote Configuration. A GPO base may deliberately carry zero rules when the remote service supplies them all. Unlike observability headers, these values are non-secret dimension labels; like every section, they round-trip losslessly through reg-export / reg-import.
Rules (Rules\{RuleName} subkeys)¶
Each rule is a subkey under Rules\{RuleName}:
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\VaultKey (REG_SZ) "gh"
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\TargetPath (REG_SZ) "~/.config/gh/hosts.yml"
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\TargetFormat (REG_SZ) "yaml"
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\TargetTemplate (REG_SZ) "github.com:\n oauth_token: \"{{.oauth_token}}\""
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\Description (REG_SZ) "GitHub CLI credentials"
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\TargetMerge (REG_SZ) ""
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\TargetDeleteNulls (REG_DWORD) 0x00000001
TargetDeleteNulls is the one non-REG_SZ value in a rule subkey. It enables field nullification: a null in the template removes that key from the target file instead of writing it. It is valid only for json and yaml rules — a 1 on any other format is rejected at startup like any other invalid policy. reg-export always emits the value, including 0x00000000, so re-importing an export clears a flag a previous policy set.
Write it as a genuine REG_DWORD. dotvault refuses to start on a TargetDeleteNulls of the wrong type rather than reading it as disabled — silently treating a mistyped policy as "off" would leave retired credentials on disk while the policy said they were being removed.
Optional OAuth subkey for rules with service onboarding:
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\OAuth\EnginePath (REG_SZ)
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\OAuth\Provider (REG_SZ)
SOFTWARE\Policies\goodtune\dotvault\Rules\gh\OAuth\Scopes (REG_MULTI_SZ)
Enrolments (Enrolments\{Name} subkeys)¶
Each enrolment is a subkey under Enrolments\{Name}:
SOFTWARE\Policies\goodtune\dotvault\Enrolments\gh\Engine (REG_SZ) "github"
SOFTWARE\Policies\goodtune\dotvault\Enrolments\gh\Settings\client_id (REG_SZ) "178c6fc778ccc68e1d6a"
SOFTWARE\Policies\goodtune\dotvault\Enrolments\gh\Settings\scopes (REG_MULTI_SZ) "repo\0read:org\0gist"
SOFTWARE\Policies\goodtune\dotvault\Enrolments\gh\Settings\https_proxy (REG_SZ) "http://squid.example.com:3128"
The https_proxy value (or its http_proxy alias) is optional. When unset, the engine consults the machine's IE / WinHTTP proxy configuration — including any deployed PAC script — once per outbound request. Set it explicitly here only when you want this enrolment pinned to a specific proxy regardless of the system-level policy.
SSH agent (Agent\ subkey)¶
The scalar transport settings live directly under Agent\; the relay has its own Agent\Relay\ subkey; the ordered key sources are subkeys under Agent\Keys\{N} where {N} is the zero-based list index:
SOFTWARE\Policies\goodtune\dotvault\Agent\Enabled (REG_DWORD)
SOFTWARE\Policies\goodtune\dotvault\Agent\UnixPath (REG_SZ)
SOFTWARE\Policies\goodtune\dotvault\Agent\WindowsPipe (REG_SZ)
SOFTWARE\Policies\goodtune\dotvault\Agent\WindowsPutty (REG_DWORD)
SOFTWARE\Policies\goodtune\dotvault\Agent\Relay\Enabled (REG_DWORD)
SOFTWARE\Policies\goodtune\dotvault\Agent\Relay\Socket (REG_SZ)
SOFTWARE\Policies\goodtune\dotvault\Agent\Relay\Pipe (REG_SZ)
SOFTWARE\Policies\goodtune\dotvault\Agent\Keys\0\Source (REG_SZ) "vault-ca"
SOFTWARE\Policies\goodtune\dotvault\Agent\Keys\0\Mount (REG_SZ) "ssh-client-signer"
SOFTWARE\Policies\goodtune\dotvault\Agent\Keys\0\Role (REG_SZ) "dotvault-user"
SOFTWARE\Policies\goodtune\dotvault\Agent\Keys\0\Principals (REG_MULTI_SZ)
Agent\Relay\Enabled and Agent\WindowsPutty are both tri-state: absent means "the policy expresses no preference", which is not the same as 0, because both default to on. Leave the value out to accept the default; write 0 only to turn the feature off.
That distinction matters most for Agent\Relay\Enabled, because it is the only control that stops dotvault proxying to the SSH agents the user already runs — and on Windows that is the one place the guarantee is weakest, since a named pipe carries no owner a caller can check. See the SSH agent guide for what that does and does not mean; pinning Agent\Relay\Pipe narrows which pipe is dialled but establishes nothing about who is listening on it, so it is not a substitute. Because it is an off-switch, its DWORD is read strictly: a value of the wrong type fails the configuration load and names itself, rather than being read as absent and quietly leaving the relay on.
Authoring these by hand is fiddly; prefer reg-import from a YAML config.
Example: deploying via GPO¶
A typical deployment workflow:
- Author the configuration as YAML and convert it with
dotvault reg-import config.yaml --output dotvault-policy.reg. - Create a new GPO linked to the target OU (e.g. "Developer Workstations").
- Deploy the registry values under
SOFTWARE\Policies\goodtune\dotvaultvia Group Policy Preferences > Registry (import the.reg, or recreate the values from it). - Deploy the binary via SCCM, Intune, or a similar tool.
- Create a scheduled task (via GPO Preferences or a script) to run
dotvaultw.exeat user logon.
Verifying the configuration¶
On a managed machine, verify that dotvault is reading from the registry:
To dump the effective policy back to YAML for review:
To test with a YAML config file instead (bypassing the registry), the policy must first opt in by setting BypassSystemConfig to 1 under the policy root key. Once it is set:
Without BypassSystemConfig, that command exits with an error explaining the override is not permitted.