# Agent Note: Windows 原子文件替换期间保留 DACL

Status: implemented

[English](2026-07-19-windows-atomic-write-dacl-preservation.md) | 中文

## 问题

原子写入在 POSIX 上以 `0o700` 保护暂存目录、以 `0o600` 保护临时文件，但 Windows mode 位只呈现实际 DACL 的合成只读视图。在目标文件的父目录下创建暂存目录和临时文件，并且只依赖继承的 DACL，足以满足新建文件的需要，但无法安全替换显式或受保护 DACL 比父目录更严格的现有文件：内容会在权限更宽松的父目录 DACL 下写入，而重命名又会把这个暂存安全描述符带到替换后的文件上。

## 决策

`dsh-fs-local` 通过 `GetFileSecurityW` 读取现有目标文件的 DACL，在写入内容前将其以禁止继承的形式应用到空临时文件，并通过 `ReplaceFileW` 发布已关闭的临时文件。受保护的暂存安全描述符可防止暂存目录中的继承条目扩大访问权限；`ReplaceFileW` 会保留原目标文件的访问策略及其他替换元数据。其 ACL 合并过程可能重新序列化自动继承状态或复制等价 ACE，因此不能把自相对安全描述符缓冲区的逐字节相等作为稳定约定。新的 Windows 文件没有既有描述符需要保留，因此仍继承目标目录的 DACL；其暂存目录也因此位于目标文件旁。POSIX 继续使用仅所有者可访问的暂存 mode，并保留现有目标文件的 mode。

Windows 原生覆盖率测试会保护目标文件的 DACL、检查写入完成的暂存文件，并对比最终替换文件中保持顺序且去重后的 ACE 策略。与宿主平台无关的绑定测试覆盖 Win32 错误转换以及每个原生调用边界。mode 位断言仍仅适用于 POSIX；新文件的 DACL 继承由操作系统约定规定，不应通过特定机器的账户允许列表来断言。

## 备选方案

**替换文件时依赖目录继承。** 不予采用，因为目标文件可能带有比父目录更严格的显式或受保护 DACL；目录继承既无法保护暂存内容，也无法保留目标文件的访问策略。

**使用 `ReplaceFileW`，但不保护临时文件。** 不予采用，因为这只能在内容已经按暂存文件继承的 DACL 写入之后修复最终描述符。

**每次写入都设置仅所有者可访问的 DACL。** 不予采用，因为这会破坏项目有意设置的共享权限。复制目标文件的 DACL 可以保留部署中已有的访问策略，无需另行创设策略。

**使用 `Get-Acl` 或 `icacls` 断言继承账户。** 不予采用，因为这类测试验证的是机器策略，而不是包行为；系统内置账户名会本地化，使输出在不同宿主上不稳定。

**在 Windows 上跳过现有 `chmod` 调用。** 不予采用，因为 Node 会把这些可写 mode 映射为无害的空操作；平台条件判断只会增加分支，不会改变 DACL 行为。

## 影响

替换 Windows 文件现在要求调用方有权读取目标 DACL 并设置临时文件 DACL；如果权限不足，系统会在写入内容前明确失败。该包引入 Koffi 以执行少量 Win32 调用，并且只在 Windows 替换路径上加载。新的 Windows 文件会在目录按设计开放较宽访问权限时继承该权限，而 POSIX 临时内容仍仅允许所有者访问；只读 Windows 目标文件仍会在发布时失败，早于重放合成 mode 可能产生影响的时点。
