hardware/windows/ssh.md
Table of Contents
SSH on Windows (OpenSSH Server)
Admin authorized_keys
- ref
- location:
C:\ProgramData\ssh\administrators_authorized_keys - after editing, run in admin powershell:
restart-service sshd
Set the Default Shell for Incoming SSH Sessions
Via regedit:
- open regedit to
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\OpenSSH - create new string value named
DefaultShell - set value to the location of the shell, e.g.
I:\git-sdk-64\usr\bin\zsh.exe
Or via CMD/PowerShell:
# check current value
reg query HKLM\SOFTWARE\OpenSSH /v DefaultShell
# set to Powershell
reg add HKLM\SOFTWARE\OpenSSH /v DefaultShell /d C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe
# or set to git-cmd zsh
reg add HKLM\SOFTWARE\OpenSSH /v DefaultShell /d E:\Program%20Files\Git\usr\bin\zsh.exe
If using zsh as the default shell, make sure .zshrc includes:
export PATH=$PATH:/e/Program\ Files/Git/usr/bin
Current default shell (2026-09-20): C:\Program Files\PowerShell\7\pwsh.exe (PowerShell 7). It was previously
the old Windows PowerShell 5.1 (C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe), which had a
quoting bug: an Alias (e.g. Set-Alias vi nvim) invoked with zero arguments appended a spurious empty
argument, producing a malformed quoted command line that broke scoop's shim (Shim: Could not create process with command '"...\nvim.exe" '). Switching to pwsh 7 avoided that bug. Belt-and-suspenders fix: also prefer
function vi { & nvim @args } over Set-Alias vi nvim in profiles — it always builds a clean argument array
regardless of PowerShell version.
SSH Sessions Run Elevated for Administrators-Group Users (and why that breaks scoop)
sshd_config (C:\ProgramData\ssh\sshd_config) has:
Match Group administrators
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
Any local-Administrators-group user (this includes domin) authenticating over SSH is matched by this block
and therefore keyed against administrators_authorized_keys instead of the normal ~/.ssh/authorized_keys.
Per Win32-OpenSSH's documented behavior, a successful auth via administrators_authorized_keys grants a
full elevated Administrator token for the whole session — not the normal filtered/standard token you get
logging in locally. Console title shows Administrator: ... and
([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
returns True in these sessions, confirming it.
Symptom this caused: running vi/nvim (installed via scoop) over SSH failed with:
Shim: Could not determine if target is a GUI app. Assuming console.
Shim: Could not create process with command '"C:\Users\domin\scoop\apps\neovim\current\bin\nvim.exe" '.
Root cause: scoop apps use a current junction (reparse point/mount point) pointing at the versioned install
dir, created by a non-elevated scoop process. Windows blocks an elevated process from traversing a
junction created by a lower-trust context — this is a security feature preventing junction-based privilege
escalation. Confirmed directly:
Get-ChildItem 'C:\Users\domin\scoop\apps\neovim\current'
# Get-ChildItem: The path cannot be traversed because it contains an untrusted mount point.
Fix: recreate the junction from inside an elevated session (e.g. run this over the elevated SSH session itself) so it's trusted for elevated traversal too:
scoop reset neovim
Caveat: this fix is per-app and doesn't stick. The next scoop update <app> run from a normal (non-elevated)
desktop session recreates the junction non-elevated again, and it'll break over SSH the same way. For any scoop
app used over SSH, re-run scoop reset <app> from an elevated SSH session after updating it. The more durable
fix would be to stop granting full-Administrator SSH sessions by default (drop the Match Group administrators
block, or remove the key from administrators_authorized_keys and keep it only in the regular
authorized_keys) if elevated-by-default SSH isn't actually needed.