hardware/windows/ssh.md

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

ref

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.