fix(ssh): repair managed Windows SSH config permissions - #1110
Merged
Merged
Conversation
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
2 times, most recently
from
September 14, 2026 16:50
b895e47 to
3635e17
Compare
EhabY
marked this pull request as ready for review
September 14, 2026 16:55
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
6 times, most recently
from
September 14, 2026 17:54
72e1d72 to
91612cb
Compare
EhabY
marked this pull request as draft
September 14, 2026 18:24
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
4 times, most recently
from
September 16, 2026 10:46
6ebe186 to
ad4b2dc
Compare
EhabY
marked this pull request as ready for review
September 16, 2026 11:01
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
4 times, most recently
from
September 16, 2026 13:22
4a896a4 to
16adc12
Compare
untra
reviewed
Sep 16, 2026
untra
reviewed
Sep 16, 2026
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
from
September 16, 2026 16:00
16adc12 to
dddc9e9
Compare
Member
|
is there any reason you didn't use the Scripts ran by the Windows Script Host are super sketchy patterns that AVs love to flag for no reason. Spawning PowerShell is similar but less so. |
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
2 times, most recently
from
September 17, 2026 16:16
8cf3d1c to
dd84e8c
Compare
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
from
September 17, 2026 16:27
dd84e8c to
98bbb22
Compare
Collaborator
Author
|
Simplified this a lot more by removing the pre/post ACL checks that ran to make sure everything was applied correctly. We now apply this on all runs on Windows (which takes the same amount of time as the check anyway). The only windows specific logic is in |
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
4 times, most recently
from
September 18, 2026 23:39
b7a0f0c to
07c23ef
Compare
This was referenced Sep 18, 2026
fioan89
reviewed
Sep 21, 2026
fioan89
approved these changes
Sep 21, 2026
OpenSSH rejects a config file whose ACL grants access beyond the owner, SYSTEM, and Administrators, so a stale explicit grant on a generated fragment fails every connection with "Bad owner or permissions". Lock the Coder-managed SSH directory with checked icacls commands and let its inheritable grant repair the files beneath it. Inject the repair as a collaborator so the config writer stays platform-independent, and reject links, non-files, and paths that icacls could expand before granting anything. Repair existing deployment fragments on upgrade, and drive the real Windows tools from the native tests. Move the runtime architecture check into a global setup so vitest.config.mts stays declarative. Speed up the Windows ARM64 unit test job, where installing dependencies took eight minutes against one on x64. Neither bufferutil nor utf-8-validate ships a win32-arm64 prebuild, and both fall back to JavaScript, so skip their node-gyp builds. Hand the pnpm store cache to pnpm/action-setup, which keys on the same lockfile hash but falls back to the last store for the platform, so a dependency bump no longer refetches the whole store. A cold setup step now takes 2m31s. Fixes #1108
Two connecting editor windows share the Coder-managed SSH directory, and each one writes its config through a temporary sibling file. A scan can read a directory entry that the other window renames away before the following lstat, and that ENOENT aborted directory preparation and the connection. Skip an entry that no longer exists, and rethrow every other error.
EhabY
force-pushed
the
fix/windows-ssh-config-acls
branch
from
September 21, 2026 15:45
07c23ef to
7593d1b
Compare
matifali
approved these changes
Sep 21, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1108. Windows files inherit their permissions from the directory they live in, so a config the extension generates under
%APPDATA%\coder.coder-remote\sshcan end up readable by other accounts. OpenSSH rejects such a file withBad owner or permissionsand skips the wholeInclude, so one bad file blocks every Coder host, not just the one it came from.Before each managed write, the extension locks that directory down with built-in commands and lets its files inherit from it. No Windows Script Host, ADSI, native dependency, or elevation:
whoami.exe /user /fo csv /nhicacls.exe <dir> /reseticacls.exe <dir> /inheritance:r /grant:r <trustee>*.conffile so it inherits the directoryicacls.exe <file> /reset*.confmatch, not only the file being written, also repairs other deployments and editors on the first connection after an upgrade. The temporary file is reset before its rename.readdirandlstat.SshConfigtakes an optionalManagedPermissions. Its absence is the marker for "not Coder-managed", which is why it has no no-op default: a default would make the extension enumerate~/.sshon every save on every platform.createManagedPermissions()returnsundefinedoff Windows, so the whole repair path stays Windows-only.What each file changes
src/remote/windowsAcl.ts: new. Theicaclsrepair itself, exportingWINDOWS_ACL,createManagedPermissions, andsystem32.src/remote/sshConfig.ts: addsManagedPermissions, splitssave()into named steps, and makes the temp write exclusive (wx).src/remote/remote.ts: passescreateManagedPermissions()to the per-deploymentSshConfig.CONTRIBUTING.md, andCHANGELOG.md.windows-11-armunit job, because the repair shells out toicacls.exeandwhoami.exeand Windows on ARM64 is a platform users connect from.test/env-check.tsfails that job if it ever runs under x64 emulation, which would silently make it a duplicate..github/actions/setup/action.yml: moves pnpm store caching fromsetup-nodetopnpm/action-setup.setup-noderestores on an exact lockfile hash, so a dependency bump refetched the whole store on every runner;pnpm/action-setupkeys on the same hash but falls back to the last store for the platform.pnpm-workspace.yaml: turns off thebufferutilandutf-8-validatesource builds. Neither ships awin32-arm64prebuild, so node-gyp compiled both on every cold ARM64 install. Both are dev-only and fall back to JavaScript, and platforms with a prebuild keep theirs..gitattributesand.vscodeignore: a generated-file marker and a trailing newline, unrelated to the fix.Validation
pnpm test(2,751 passed, 6 skipped),pnpm typecheck,pnpm lint, andpnpm format:checkpass. Windows CI covers x64 and ARM64 on a cold pnpm store. Against OpenSSH 9.6p1, a*.confdirectory aborts the wholeIncludeon Windows, where glibc instead reads zero lines and ignores it, so the extension reports that entry rather than trying to fix it with an ACL change.Limits
A directory-preparation failure stops the write. A single file-reset failure logs a warning and still attempts the connection, so OpenSSH may reject a fragment the repair missed. The two directory commands are not atomic: a failure after
/resetcan leave the directory with its parent's grants. The path and link checks stop mistakes, not an attacker racing the check.Generated by Coder Agents on behalf of @EhabY.