mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-13 00:19:03 +02:00
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:
co-authored by
Sina Matian
Claude Fable 5
parent
270f1a038a
commit
fa4e4c3f2c
@@ -71,7 +71,7 @@ export function restrictFilePermissions(filePath: string): void {
|
||||
execFileSync(
|
||||
'icacls',
|
||||
[filePath, '/inheritance:r', '/grant:r', `${user}:(F)`],
|
||||
{ stdio: 'ignore' },
|
||||
{ stdio: 'ignore', windowsHide: true },
|
||||
);
|
||||
} catch (err) {
|
||||
warnIcaclsFailure(filePath, err);
|
||||
@@ -101,7 +101,7 @@ export function restrictDirectoryPermissions(dirPath: string): void {
|
||||
execFileSync(
|
||||
'icacls',
|
||||
[dirPath, '/inheritance:r', '/grant:r', `${user}:(OI)(CI)(F)`],
|
||||
{ stdio: 'ignore' },
|
||||
{ stdio: 'ignore', windowsHide: true },
|
||||
);
|
||||
} catch (err) {
|
||||
warnIcaclsFailure(dirPath, err);
|
||||
@@ -139,14 +139,54 @@ export function appendSecureFile(
|
||||
if (!existed) restrictFilePermissions(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows only: probe whether the current process can actually list the
|
||||
* directory. `fs.accessSync` doesn't consult NTFS ACLs on Windows, so a
|
||||
* real readdir is the only honest check.
|
||||
*/
|
||||
function canListDir(dirPath: string): boolean {
|
||||
try { fs.readdirSync(dirPath); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows only: repair a broken DACL on a state directory (#1605).
|
||||
*
|
||||
* `icacls /inheritance:r /grant:r <user>:(F)` is a single command, but the
|
||||
* two halves can partially fail: inheritance gets stripped while the user
|
||||
* grant doesn't resolve (localized account names, domain accounts, roaming
|
||||
* profiles). The result is a DACL with no usable ACE — often just a machine
|
||||
* SID — and the client can't read its own state files. `/reset` restores
|
||||
* inherited ACLs from the parent, making the directory functional again.
|
||||
* Functional-but-unhardened beats hardened-but-unusable.
|
||||
*/
|
||||
export function repairBrokenDacl(dirPath: string): void {
|
||||
if (process.platform !== 'win32') return;
|
||||
try {
|
||||
execFileSync('icacls', [dirPath, '/reset', '/T', '/C', '/Q'], { stdio: 'ignore', windowsHide: true });
|
||||
} catch (err) {
|
||||
warnIcaclsFailure(dirPath, err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `mkdir -p` with owner-only directory permissions, cross-platform.
|
||||
* Replaces `fs.mkdirSync(path, { recursive: true, mode: 0o700 })` + Windows ACL.
|
||||
* Safe to call on an existing directory — re-applies the ACL idempotently.
|
||||
*
|
||||
* Windows: after applying the restricted ACL, verifies the directory is
|
||||
* still listable by this process and repairs a broken DACL (#1605) if not.
|
||||
*/
|
||||
export function mkdirSecure(dirPath: string): void {
|
||||
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
||||
restrictDirectoryPermissions(dirPath);
|
||||
if (process.platform === 'win32' && !canListDir(dirPath)) {
|
||||
repairBrokenDacl(dirPath);
|
||||
restrictDirectoryPermissions(dirPath);
|
||||
// If re-hardening broke access again, reset once more and leave the
|
||||
// directory with inherited ACLs — the client must be able to read
|
||||
// its own state.
|
||||
if (!canListDir(dirPath)) repairBrokenDacl(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user