fix(browse): self-repair broken Windows DACLs on state dirs (#1605)

icacls '/inheritance:r /grant:r' can partially fail on localized or domain
accounts: inheritance strips but the user grant doesn't resolve, leaving a
machine-SID-only DACL the owner can't even list — the sidebar/PTY failure
chain in #1605, caused by the very hardening call meant to protect the dir.
mkdirSecure now verifies listability after hardening (a real readdir —
fs.accessSync doesn't consult NTFS ACLs) and repairs via icacls /reset,
re-hardens, and if hardening breaks access again leaves inherited ACLs:
functional-but-unhardened beats hardened-but-unusable. The icacls calls
carry windowsHide (#1835's last two sites) and the fork's static spawn-hide
tripwire lands here, pinning every covered site. file-permissions.test.ts
is already in the windows-free-tests curated shard, so the DACL contract
executes on windows-latest.

Ported from time-attack/gstack (GStack 2).

Co-authored-by: Sina Matian <sina@time-attack.dev>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 12:53:33 -07:00
co-authored by Sina Matian Claude Fable 5
parent 270f1a038a
commit fa4e4c3f2c
3 changed files with 129 additions and 2 deletions
+32
View File
@@ -22,6 +22,7 @@ import {
writeSecureFile,
appendSecureFile,
mkdirSecure,
repairBrokenDacl,
__resetWarnedForTests,
} from '../src/file-permissions';
@@ -145,4 +146,35 @@ describe('mkdirSecure', () => {
expect(fs.existsSync(path.join(tmpDir, 'a', 'b'))).toBe(true);
expect(fs.existsSync(d)).toBe(true);
});
test('created directory is listable by the creating process', () => {
// #1605 contract: whatever ACL hardening happens, the client must be
// able to read its own state dir immediately after creation.
const d = path.join(tmpDir, 'state');
mkdirSecure(d);
fs.writeFileSync(path.join(d, 'browse.json'), '{}');
expect(fs.readdirSync(d)).toContain('browse.json');
});
});
describe('repairBrokenDacl', () => {
test('is a no-op on non-Windows platforms', () => {
if (process.platform === 'win32') return;
const d = path.join(tmpDir, 'dir');
fs.mkdirSync(d);
expect(() => repairBrokenDacl(d)).not.toThrow();
});
test('on Windows, does not throw and directory stays listable', () => {
if (process.platform !== 'win32') return;
const d = path.join(tmpDir, 'dir');
fs.mkdirSync(d);
expect(() => repairBrokenDacl(d)).not.toThrow();
expect(() => fs.readdirSync(d)).not.toThrow();
});
test('on Windows, swallows icacls failure on a nonexistent path', () => {
if (process.platform !== 'win32') return;
expect(() => repairBrokenDacl(path.join(tmpDir, 'nonexistent'))).not.toThrow();
});
});