test(fs): compare Windows DACL access policy

ReplaceFileW preserves ACLs by merging security information, which may reserialize auto-inheritance state and duplicate equivalent ACEs. Compare the final ordered, de-duplicated ACE policy instead of requiring byte-identical self-relative descriptor buffers.

Update the host-independent binding assertion to expect the namespaced absolute paths that the Win32 boundary actually receives, and align the package and bilingual RFC contracts with the semantic DACL guarantee.
This commit is contained in:
Tianyi Cui
2026-07-19 13:25:15 +08:00
parent 0f8d0082e4
commit a6b8ce456a
5 changed files with 30 additions and 7 deletions

View File

@@ -372,6 +372,28 @@ describe('streamWholeText', () => {
// bits, so mode assertions are POSIX-only; native DACL preservation is asserted separately.
const posixModes = process.platform !== 'win32'
function daclAcePolicy(descriptor: Buffer): string[] {
const daclOffset = descriptor.readUInt32LE(16)
if (daclOffset === 0) return []
const aceCount = descriptor.readUInt16LE(daclOffset + 4)
const policy: string[] = []
const seen = new Set<string>()
let offset = daclOffset + 8
for (let index = 0; index < aceCount; index++) {
const size = descriptor.readUInt16LE(offset + 2)
const ace = Buffer.from(descriptor.subarray(offset, offset + size))
// INHERITED_ACE records provenance, not the entry's access policy.
ace.writeUInt8(ace.readUInt8(1) & ~0x10, 1)
const key = ace.toString('hex')
if (!seen.has(key)) {
seen.add(key)
policy.push(key)
}
offset += size
}
return policy
}
describe('writeFileAtomic — temp-file safety', () => {
it('writes through a private staging dir and owner-only temp file', async () => {
const file = join(dir, 'a.txt')
@@ -409,7 +431,7 @@ describe('writeFileAtomic — temp-file safety', () => {
})
expect(await readFile(file, 'utf8')).toBe('new')
expect(await readFileDaclWin32(file)).toEqual(expectedDacl)
expect(daclAcePolicy(await readFileDaclWin32(file))).toEqual(daclAcePolicy(expectedDacl))
})
it('copies a Windows target DACL before content and publishes through secure replacement', async () => {

View File

@@ -1,5 +1,6 @@
/** Host-independent binding tests for the Win32 DACL and replacement helpers. */
import { toNamespacedPath } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
type GetFileSecurityW = (
@@ -92,7 +93,7 @@ describe('Windows file-security helpers', () => {
await copyFileDaclWin32('source', 'temp')
expect(native.installed).toEqual([descriptor])
await replaceFileWin32('target', 'temp')
expect(native.replacements).toEqual([['target', 'temp']])
expect(native.replacements).toEqual([[toNamespacedPath('target'), toNamespacedPath('temp')]])
})
it('maps descriptor-size probe failures to Node-style codes', async () => {