mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Copy an existing target's DACL onto the empty staging file before any content is written, then publish with ReplaceFileW so Windows replacement keeps the target security descriptor instead of inheriting the broader parent policy. Keep new-file inheritance and POSIX mode behavior unchanged, retain the already-protected temp when a concurrently removed target requires rename fallback, and translate native errors into Node-style codes for the filesystem error boundary. Add host-independent Win32 binding coverage, native Windows descriptor assertions, package documentation, and a bilingual implemented RFC that supersedes the earlier inheritance-only replacement claim.
135 lines
4.7 KiB
TypeScript
135 lines
4.7 KiB
TypeScript
/**
|
|
* Windows security-descriptor helpers for atomic local-file replacement. Koffi loads lazily so
|
|
* non-Windows processes never open Win32 libraries.
|
|
* @module @deepseek-ai/dsh-fs-local/win32
|
|
*/
|
|
|
|
import { toNamespacedPath } from 'node:path'
|
|
|
|
type GetFileSecurityW = (
|
|
path: string,
|
|
requestedInformation: number,
|
|
descriptor: Buffer | null,
|
|
length: number,
|
|
needed: [number],
|
|
) => number
|
|
type SetFileSecurityW = (path: string, securityInformation: number, descriptor: Buffer) => number
|
|
type ReplaceFileW = (
|
|
replaced: string,
|
|
replacement: string,
|
|
backup: null,
|
|
flags: number,
|
|
exclude: null,
|
|
reserved: null,
|
|
) => number
|
|
type GetLastError = () => number
|
|
|
|
interface Win32Bindings {
|
|
getFileSecurityW: GetFileSecurityW
|
|
setFileSecurityW: SetFileSecurityW
|
|
replaceFileW: ReplaceFileW
|
|
getLastError: GetLastError
|
|
}
|
|
|
|
interface Win32ErrnoException extends NodeJS.ErrnoException {
|
|
win32Code: number
|
|
}
|
|
|
|
const DACL_SECURITY_INFORMATION = 0x00000004
|
|
const PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000
|
|
const ERROR_FILE_NOT_FOUND = 2
|
|
const ERROR_PATH_NOT_FOUND = 3
|
|
const ERROR_ACCESS_DENIED = 5
|
|
|
|
let bindings: Win32Bindings | undefined
|
|
|
|
async function win32(): Promise<Win32Bindings> {
|
|
if (bindings !== undefined) return bindings
|
|
const koffi = (await import('koffi')).default
|
|
const advapi32 = koffi.load('advapi32.dll')
|
|
const kernel32 = koffi.load('kernel32.dll')
|
|
bindings = {
|
|
getFileSecurityW: advapi32.func('int __stdcall GetFileSecurityW(const char16_t *path, uint32_t requested, void *descriptor, uint32_t length, _Out_ uint32_t *needed)') as GetFileSecurityW,
|
|
setFileSecurityW: advapi32.func('int __stdcall SetFileSecurityW(const char16_t *path, uint32_t information, const void *descriptor)') as SetFileSecurityW,
|
|
replaceFileW: kernel32.func('int __stdcall ReplaceFileW(const char16_t *replaced, const char16_t *replacement, const char16_t *backup, uint32_t flags, void *exclude, void *reserved)') as ReplaceFileW,
|
|
getLastError: kernel32.func('uint32_t __stdcall GetLastError()') as GetLastError,
|
|
}
|
|
return bindings
|
|
}
|
|
|
|
function errnoCode(win32Code: number): string {
|
|
switch (win32Code) {
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_PATH_NOT_FOUND:
|
|
return 'ENOENT'
|
|
case ERROR_ACCESS_DENIED:
|
|
return 'EACCES'
|
|
default:
|
|
return 'EIO'
|
|
}
|
|
}
|
|
|
|
function win32Error(syscall: string, win32Code: number, path: string): Win32ErrnoException {
|
|
const code = errnoCode(win32Code)
|
|
const error = new Error(`${syscall} ${code} (Win32 ${win32Code}): ${path}`) as Win32ErrnoException
|
|
error.code = code
|
|
error.errno = win32Code
|
|
error.syscall = syscall
|
|
error.path = path
|
|
error.win32Code = win32Code
|
|
return error
|
|
}
|
|
|
|
/**
|
|
* Read a file's self-relative DACL security descriptor.
|
|
* @param path - existing file whose DACL is read.
|
|
* @returns a descriptor buffer accepted by `SetFileSecurityW`.
|
|
*/
|
|
export async function readFileDaclWin32(path: string): Promise<Buffer> {
|
|
const api = await win32()
|
|
const nativePath = toNamespacedPath(path)
|
|
const needed: [number] = [0]
|
|
api.getFileSecurityW(nativePath, DACL_SECURITY_INFORMATION, null, 0, needed)
|
|
if (needed[0] === 0) throw win32Error('GetFileSecurityW', api.getLastError(), path)
|
|
|
|
const descriptor = Buffer.alloc(needed[0])
|
|
if (api.getFileSecurityW(nativePath, DACL_SECURITY_INFORMATION, descriptor, descriptor.length, needed) === 0) {
|
|
throw win32Error('GetFileSecurityW', api.getLastError(), path)
|
|
}
|
|
return descriptor.subarray(0, needed[0])
|
|
}
|
|
|
|
/**
|
|
* Copy an existing file's DACL onto another file and protect it from staging-parent inheritance.
|
|
* The destination must still be empty when confidentiality depends on this call.
|
|
* @param source - existing file whose DACL is copied.
|
|
* @param destination - existing file that receives the protected DACL.
|
|
*/
|
|
export async function copyFileDaclWin32(source: string, destination: string): Promise<void> {
|
|
const descriptor = await readFileDaclWin32(source)
|
|
const api = await win32()
|
|
const information = (DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION) >>> 0
|
|
if (api.setFileSecurityW(toNamespacedPath(destination), information, descriptor) === 0) {
|
|
throw win32Error('SetFileSecurityW', api.getLastError(), destination)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace a Windows file while preserving the replaced file's ACL and other replace metadata.
|
|
* @param replaced - existing destination file.
|
|
* @param replacement - closed staging file on the same volume.
|
|
*/
|
|
export async function replaceFileWin32(replaced: string, replacement: string): Promise<void> {
|
|
const api = await win32()
|
|
if (api.replaceFileW(
|
|
toNamespacedPath(replaced),
|
|
toNamespacedPath(replacement),
|
|
null,
|
|
0,
|
|
null,
|
|
null,
|
|
) === 0) {
|
|
throw win32Error('ReplaceFileW', api.getLastError(), replaced)
|
|
}
|
|
}
|