fix(fs): preserve Windows DACLs across atomic replacement

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.
This commit is contained in:
Tianyi Cui
2026-07-19 12:38:18 +08:00
parent 46580e4083
commit 2b673bd68d
12 changed files with 472 additions and 16 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-19-windows-atomic-write-dacl-preservation.md: 393ce8a992b8c0b7b580f2c794e098d66e14258e
2026-07-19-windows-atomic-write-dacl-preservation.zh.md: c7a0b6278cf739cc5ef4432d679e48b88b61d198

View File

@@ -0,0 +1,27 @@
# RFC: Preserve Windows DACLs during atomic file replacement
Status: implemented
English | [中文](2026-07-19-windows-atomic-write-dacl-preservation.zh.md)
## Problem
On Windows, creating the staging directory and temp file under the target's parent and relying only on inherited DACLs is sufficient for a new file, but not for replacing an existing file whose explicit or protected DACL is narrower than its parent: content is written under the broader parent DACL, and rename carries that staging descriptor onto the replacement.
## Decision
`dsh-fs-local` reads an existing target's DACL with `GetFileSecurityW`, applies it to the empty temp file with inheritance protected before writing content, and publishes the closed temp with `ReplaceFileW`. The protected staging descriptor prevents the temp directory's inherited entries from broadening access; `ReplaceFileW` preserves the original target security descriptor and other replacement metadata. New files have no prior descriptor to preserve and continue to inherit the destination directory's DACL.
Native Windows coverage protects a target DACL, inspects the written staging file, and compares the final replacement descriptor. Host-independent binding tests cover Win32 error translation and every native call boundary.
## Alternatives considered
**Rely on directory inheritance for replacements.** Rejected because a target may carry a narrower explicit or protected DACL than its parent, so inheritance neither protects staged content nor preserves the target access policy.
**Use `ReplaceFileW` without protecting the temp.** Rejected because it repairs the final descriptor only after the content has already been written under the staging file's inherited DACL.
**Install an owner-only DACL for every write.** Rejected because it would discard deliberate project sharing. Copying the target DACL preserves the deployment's existing access policy instead of inventing one.
## Consequences
Replacing a Windows file now requires permission to read the target DACL and set the temp DACL; failure is loud before content is written. The package carries Koffi for the narrow Win32 calls, loaded only on Windows replacement paths. New-file behavior remains directory-inherited, and POSIX mode behavior is unchanged.

View File

@@ -0,0 +1,27 @@
# RFC: Windows 原子文件替换期间保留 DACL
Status: implemented
[English](2026-07-19-windows-atomic-write-dacl-preservation.md) | 中文
## 问题
在 Windows 上,在目标文件的父目录下创建暂存目录和临时文件,并且只依赖继承的 DACL足以满足新建文件的需要但无法安全替换显式或受保护 DACL 比父目录更严格的现有文件:内容会在权限更宽松的父目录 DACL 下写入,而重命名又会把这个暂存安全描述符带到替换后的文件上。
## 决策
`dsh-fs-local` 通过 `GetFileSecurityW` 读取现有目标文件的 DACL在写入内容前将其以禁止继承的形式应用到空临时文件并通过 `ReplaceFileW` 发布已关闭的临时文件。受保护的暂存安全描述符可防止暂存目录中的继承条目扩大访问权限;`ReplaceFileW` 会保留原目标文件的安全描述符及其他替换元数据。新建文件没有既有描述符需要保留,因此仍继承目标目录的 DACL。
Windows 原生覆盖率测试会保护目标文件的 DACL、检查写入完成的暂存文件并对比最终替换文件的描述符。与宿主平台无关的绑定测试覆盖 Win32 错误转换以及每个原生调用边界。
## 备选方案
**替换文件时依赖目录继承。** 不予采用,因为目标文件可能带有比父目录更严格的显式或受保护 DACL目录继承既无法保护暂存内容也无法保留目标文件的访问策略。
**使用 `ReplaceFileW`,但不保护临时文件。** 不予采用,因为这只能在内容已经按暂存文件继承的 DACL 写入之后修复最终描述符。
**每次写入都设置仅所有者可访问的 DACL。** 不予采用,因为这会破坏项目有意设置的共享权限。复制目标文件的 DACL 可以保留部署中已有的访问策略,无需另行创设策略。
## 影响
替换 Windows 文件现在要求调用方有权读取目标 DACL 并设置临时文件 DACL如果权限不足系统会在写入内容前明确失败。该包package引入 Koffi 以执行少量 Win32 调用,并且只在 Windows 替换路径上加载。新建文件仍按目录继承POSIX mode 行为保持不变。