Skip to content

fix(browse): grant Windows ACLs by SID to avoid owner lockout when username matches machine name#2169

Open
voltapix26 wants to merge 1 commit into
garrytan:mainfrom
voltapix26:fix/windows-acl-sid-grant
Open

fix(browse): grant Windows ACLs by SID to avoid owner lockout when username matches machine name#2169
voltapix26 wants to merge 1 commit into
garrytan:mainfrom
voltapix26:fix/windows-acl-sid-grant

Conversation

@voltapix26

Copy link
Copy Markdown

Problem

restrictFilePermissions / restrictDirectoryPermissions in browse/src/file-permissions.ts harden Windows ACLs with:

icacls <path> /inheritance:r /grant:r <os.userInfo().username>:(F)

Granting by bare username is ambiguous. On machines where the username collides with another account object — most commonly when the machine name equals the username (e.g. user Eshan on machine ESHAN) — LookupAccountName resolves the bare name to the machine's domain SID (S-1-5-21-… with no RID) instead of the user account. The resulting ACL has inheritance stripped and a single grant to a principal that isn't the user, so it denies even the owner.

Symptoms: ~/.gstack becomes inaccessible and browse fails with EPERM on mkdir — a self-lockout caused by the hardening itself. This is easy to hit in the wild: OOBE suggests the user's first name as the machine name, so Alice on ALICE is a common single-user-laptop configuration.

Fix

Add a getWindowsGrantee() helper (cached per process) that:

  1. Resolves the current user's SID via %SystemRoot%\System32\whoami.exe /user /fo csv /nh and grants *<SID>:(F) — SID grants are unambiguous by construction.
    • The absolute path matters: a bare whoami can resolve to GNU coreutils' whoami (Git for Windows / MSYS2 put usr/bin on PATH), which rejects /user.
  2. Falls back to USERDOMAIN\USERNAME (domain-qualified, still unambiguous) if SID resolution fails, before ever falling back to the bare username.

Both restrictFilePermissions and restrictDirectoryPermissions now use the helper.

Tests

  • Added a Windows-only test asserting the resolved grantee is a SID (*S-1-…) or a domain-qualified name — never a bare username — via a __getWindowsGranteeForTests export (same pattern as the existing __resetWarnedForTests).
  • Full browse/test/file-permissions.test.ts suite passes on a Windows 11 machine that exhibits the original bug (username == machine name): 14 pass, 0 fail. The existing "file remains readable by the caller" test is the behavioral regression guard on such machines.

Note for release

dist/browse.exe and dist/server-node.mjs embed this module, so a dist rebuild is required for the fix to reach releases — the source change alone doesn't affect already-built binaries.

🤖 Generated with Claude Code

restrictFilePermissions/restrictDirectoryPermissions granted icacls
permissions by bare username (os.userInfo().username). On machines where
the username collides with another account object — most commonly when
the machine name equals the username — LookupAccountName resolves the
bare name to the machine's domain SID (S-1-5-21-... with no RID),
producing an ACL that denies even the owner. ~/.gstack becomes
inaccessible and browse fails with EPERM on mkdir.

Resolve the current user's SID via %SystemRoot%\System32\whoami.exe
(absolute path — a bare 'whoami' can hit GNU coreutils' whoami from the
Git for Windows PATH, which rejects /user) and grant *<SID>:(F), which
is unambiguous. Fall back to USERDOMAIN\USERNAME if SID resolution
fails.

Note: dist/browse.exe and dist/server-node.mjs embed this module, so a
dist rebuild is required for the fix to take effect in releases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jul 4, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant