@openclaw/fs-safe 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/LICENSE +1 -0
  3. package/README.md +52 -4
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +5 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +5 -0
  9. package/dist/archive-crc32.d.ts.map +1 -1
  10. package/dist/archive-crc32.js +6 -1
  11. package/dist/archive-deadline.d.ts.map +1 -1
  12. package/dist/archive-deadline.js +3 -4
  13. package/dist/archive-durability.d.ts +6 -6
  14. package/dist/archive-durability.d.ts.map +1 -1
  15. package/dist/archive-durability.js +1 -1
  16. package/dist/archive-entry.d.ts.map +1 -1
  17. package/dist/archive-entry.js +4 -5
  18. package/dist/archive-gzip-tail.d.ts +3 -0
  19. package/dist/archive-gzip-tail.d.ts.map +1 -1
  20. package/dist/archive-gzip-tail.js +23 -3
  21. package/dist/archive-input.d.ts.map +1 -1
  22. package/dist/archive-input.js +4 -2
  23. package/dist/archive-merge.d.ts +5 -1
  24. package/dist/archive-merge.d.ts.map +1 -1
  25. package/dist/archive-merge.js +16 -13
  26. package/dist/archive-native.d.ts.map +1 -1
  27. package/dist/archive-native.js +7 -6
  28. package/dist/archive-parser.wasm +0 -0
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +83 -74
  31. package/dist/archive-staging.d.ts +6 -3
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +42 -22
  34. package/dist/archive-tar-stream.d.ts +11 -4
  35. package/dist/archive-tar-stream.d.ts.map +1 -1
  36. package/dist/archive-tar-stream.js +23 -10
  37. package/dist/archive-tar-wasm.d.ts.map +1 -1
  38. package/dist/archive-tar-wasm.js +18 -14
  39. package/dist/archive-zip-admission.d.ts +1 -1
  40. package/dist/archive-zip-admission.d.ts.map +1 -1
  41. package/dist/archive-zip-admission.js +48 -12
  42. package/dist/archive-zip-loader.d.ts +6 -0
  43. package/dist/archive-zip-loader.d.ts.map +1 -0
  44. package/dist/archive-zip-loader.js +38 -0
  45. package/dist/archive-zip-names.d.ts.map +1 -1
  46. package/dist/archive-zip-names.js +13 -8
  47. package/dist/archive-zip-preflight.d.ts +2 -3
  48. package/dist/archive-zip-preflight.d.ts.map +1 -1
  49. package/dist/archive-zip-preflight.js +2 -34
  50. package/dist/archive.d.ts.map +1 -1
  51. package/dist/archive.js +12 -10
  52. package/dist/bounded-read.d.ts +12 -0
  53. package/dist/bounded-read.d.ts.map +1 -1
  54. package/dist/bounded-read.js +82 -45
  55. package/dist/clone-metadata.d.ts +19 -0
  56. package/dist/clone-metadata.d.ts.map +1 -0
  57. package/dist/clone-metadata.js +32 -0
  58. package/dist/copy-file-input.d.ts +22 -0
  59. package/dist/copy-file-input.d.ts.map +1 -0
  60. package/dist/copy-file-input.js +69 -0
  61. package/dist/copy-policy.d.ts +3 -0
  62. package/dist/copy-policy.d.ts.map +1 -0
  63. package/dist/copy-policy.js +8 -0
  64. package/dist/copy-publication.d.ts +10 -1
  65. package/dist/copy-publication.d.ts.map +1 -1
  66. package/dist/copy-publication.js +27 -0
  67. package/dist/copy-tree-portable.d.ts +9 -0
  68. package/dist/copy-tree-portable.d.ts.map +1 -0
  69. package/dist/copy-tree-portable.js +222 -0
  70. package/dist/copy.d.ts +16 -0
  71. package/dist/copy.d.ts.map +1 -0
  72. package/dist/copy.js +125 -0
  73. package/dist/directory-durability.d.ts.map +1 -1
  74. package/dist/directory-durability.js +5 -4
  75. package/dist/directory-guard.d.ts +11 -1
  76. package/dist/directory-guard.d.ts.map +1 -1
  77. package/dist/directory-guard.js +53 -11
  78. package/dist/durability.d.ts +1 -1
  79. package/dist/durability.d.ts.map +1 -1
  80. package/dist/durability.js +1 -1
  81. package/dist/error-detail.d.ts.map +1 -1
  82. package/dist/error-detail.js +4 -1
  83. package/dist/file-handle-transfer.d.ts +14 -0
  84. package/dist/file-handle-transfer.d.ts.map +1 -0
  85. package/dist/file-handle-transfer.js +64 -0
  86. package/dist/file-hash.d.ts +9 -2
  87. package/dist/file-hash.d.ts.map +1 -1
  88. package/dist/file-hash.js +135 -39
  89. package/dist/file-lock-sync.d.ts.map +1 -1
  90. package/dist/file-lock-sync.js +8 -4
  91. package/dist/file-store-boundary.d.ts.map +1 -1
  92. package/dist/file-store-boundary.js +7 -5
  93. package/dist/file-store-path.d.ts +3 -0
  94. package/dist/file-store-path.d.ts.map +1 -0
  95. package/dist/file-store-path.js +27 -0
  96. package/dist/file-store-prune.d.ts.map +1 -1
  97. package/dist/file-store-prune.js +6 -4
  98. package/dist/file-store-sync-write.d.ts.map +1 -1
  99. package/dist/file-store-sync-write.js +56 -44
  100. package/dist/file-store.d.ts.map +1 -1
  101. package/dist/file-store.js +2 -18
  102. package/dist/filename.d.ts.map +1 -1
  103. package/dist/filename.js +4 -13
  104. package/dist/guarded-mkdir.d.ts +1 -0
  105. package/dist/guarded-mkdir.d.ts.map +1 -1
  106. package/dist/guarded-mkdir.js +4 -2
  107. package/dist/guarded-mutation.d.ts +2 -0
  108. package/dist/guarded-mutation.d.ts.map +1 -1
  109. package/dist/guarded-mutation.js +8 -4
  110. package/dist/guest-dispatch-python.d.ts +2 -0
  111. package/dist/guest-dispatch-python.d.ts.map +1 -0
  112. package/dist/guest-dispatch-python.js +117 -0
  113. package/dist/guest-native-python.d.ts +4 -0
  114. package/dist/guest-native-python.d.ts.map +1 -0
  115. package/dist/guest-native-python.js +135 -0
  116. package/dist/guest.d.ts +9 -0
  117. package/dist/guest.d.ts.map +1 -0
  118. package/dist/guest.js +413 -0
  119. package/dist/home-dir.d.ts.map +1 -1
  120. package/dist/home-dir.js +9 -7
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/install-path.d.ts.map +1 -1
  124. package/dist/install-path.js +5 -8
  125. package/dist/json-durable-queue-directory.js +3 -3
  126. package/dist/json-durable-queue.d.ts.map +1 -1
  127. package/dist/json-durable-queue.js +19 -18
  128. package/dist/json.d.ts.map +1 -1
  129. package/dist/json.js +2 -1
  130. package/dist/local-roots.d.ts.map +1 -1
  131. package/dist/local-roots.js +24 -25
  132. package/dist/move-path-stage.d.ts +8 -0
  133. package/dist/move-path-stage.d.ts.map +1 -0
  134. package/dist/move-path-stage.js +56 -0
  135. package/dist/move-path.d.ts.map +1 -1
  136. package/dist/move-path.js +30 -34
  137. package/dist/mutation-authority.d.ts +9 -0
  138. package/dist/mutation-authority.d.ts.map +1 -0
  139. package/dist/mutation-authority.js +36 -0
  140. package/dist/native-binding.d.ts +29 -1
  141. package/dist/native-binding.d.ts.map +1 -1
  142. package/dist/native-operations.d.ts +1 -1
  143. package/dist/native-operations.d.ts.map +1 -1
  144. package/dist/native-operations.js +11 -5
  145. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  146. package/dist/native-pinned-write-windows.js +19 -7
  147. package/dist/native-pinned-write.d.ts.map +1 -1
  148. package/dist/native-pinned-write.js +3 -1
  149. package/dist/native-staged-file.d.ts +3 -3
  150. package/dist/native-staged-file.d.ts.map +1 -1
  151. package/dist/native-staged-file.js +35 -8
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +5 -4
  154. package/dist/output.d.ts +2 -0
  155. package/dist/output.d.ts.map +1 -1
  156. package/dist/output.js +2 -0
  157. package/dist/overwrite-file-handle.d.ts +8 -0
  158. package/dist/overwrite-file-handle.d.ts.map +1 -0
  159. package/dist/overwrite-file-handle.js +42 -0
  160. package/dist/path-case.d.ts +7 -0
  161. package/dist/path-case.d.ts.map +1 -0
  162. package/dist/path-case.js +136 -0
  163. package/dist/path.d.ts.map +1 -1
  164. package/dist/path.js +5 -2
  165. package/dist/permissions-windows.d.ts +1 -1
  166. package/dist/permissions-windows.d.ts.map +1 -1
  167. package/dist/permissions-windows.js +85 -53
  168. package/dist/pinned-open.d.ts.map +1 -1
  169. package/dist/pinned-open.js +3 -1
  170. package/dist/pinned-operation.js +1 -1
  171. package/dist/pinned-write.d.ts +7 -3
  172. package/dist/pinned-write.d.ts.map +1 -1
  173. package/dist/pinned-write.js +51 -30
  174. package/dist/positional-read.d.ts +9 -0
  175. package/dist/positional-read.d.ts.map +1 -0
  176. package/dist/positional-read.js +36 -0
  177. package/dist/private-temp-workspace.d.ts.map +1 -1
  178. package/dist/private-temp-workspace.js +6 -4
  179. package/dist/publish-copy-stage.d.ts +13 -0
  180. package/dist/publish-copy-stage.d.ts.map +1 -0
  181. package/dist/publish-copy-stage.js +47 -0
  182. package/dist/publish-file.d.ts.map +1 -1
  183. package/dist/publish-file.js +6 -5
  184. package/dist/realpath.d.ts +4 -0
  185. package/dist/realpath.d.ts.map +1 -0
  186. package/dist/realpath.js +43 -0
  187. package/dist/recursive-mkdir-path.d.ts +3 -0
  188. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  189. package/dist/recursive-mkdir-path.js +8 -0
  190. package/dist/replace-directory.d.ts.map +1 -1
  191. package/dist/replace-directory.js +2 -1
  192. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  193. package/dist/replace-file-copy-fallback.js +23 -31
  194. package/dist/replace-file-copy-source.d.ts.map +1 -1
  195. package/dist/replace-file-copy-source.js +7 -12
  196. package/dist/replace-file-mode.d.ts +3 -0
  197. package/dist/replace-file-mode.d.ts.map +1 -0
  198. package/dist/replace-file-mode.js +10 -0
  199. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  200. package/dist/replace-file-temp-owner.js +6 -2
  201. package/dist/replace-file.d.ts +1 -0
  202. package/dist/replace-file.d.ts.map +1 -1
  203. package/dist/replace-file.js +14 -8
  204. package/dist/root-context.d.ts +3 -0
  205. package/dist/root-context.d.ts.map +1 -1
  206. package/dist/root-context.js +53 -28
  207. package/dist/root-create-input.d.ts +10 -0
  208. package/dist/root-create-input.d.ts.map +1 -0
  209. package/dist/root-create-input.js +80 -0
  210. package/dist/root-directory-list.d.ts +26 -0
  211. package/dist/root-directory-list.d.ts.map +1 -0
  212. package/dist/root-directory-list.js +219 -0
  213. package/dist/root-entries.d.ts +11 -0
  214. package/dist/root-entries.d.ts.map +1 -0
  215. package/dist/root-entries.js +61 -0
  216. package/dist/root-errors.d.ts +6 -5
  217. package/dist/root-errors.d.ts.map +1 -1
  218. package/dist/root-errors.js +16 -12
  219. package/dist/root-file.d.ts +2 -0
  220. package/dist/root-file.d.ts.map +1 -1
  221. package/dist/root-file.js +12 -4
  222. package/dist/root-impl.d.ts +17 -51
  223. package/dist/root-impl.d.ts.map +1 -1
  224. package/dist/root-impl.js +358 -234
  225. package/dist/root-options.d.ts +77 -0
  226. package/dist/root-options.d.ts.map +1 -0
  227. package/dist/root-options.js +18 -0
  228. package/dist/root-path-existing.d.ts +5 -0
  229. package/dist/root-path-existing.d.ts.map +1 -1
  230. package/dist/root-path-existing.js +59 -3
  231. package/dist/root-path-symlink.d.ts.map +1 -1
  232. package/dist/root-path-symlink.js +3 -2
  233. package/dist/root-path.d.ts +1 -0
  234. package/dist/root-path.d.ts.map +1 -1
  235. package/dist/root-path.js +74 -162
  236. package/dist/root-paths.d.ts.map +1 -1
  237. package/dist/root-paths.js +13 -9
  238. package/dist/root-remove.d.ts +5 -0
  239. package/dist/root-remove.d.ts.map +1 -0
  240. package/dist/root-remove.js +286 -0
  241. package/dist/root-symlink-policy.d.ts +14 -0
  242. package/dist/root-symlink-policy.d.ts.map +1 -0
  243. package/dist/root-symlink-policy.js +34 -0
  244. package/dist/root-walk.d.ts +5 -4
  245. package/dist/root-walk.d.ts.map +1 -1
  246. package/dist/root-walk.js +99 -50
  247. package/dist/root-write-mode.d.ts.map +1 -1
  248. package/dist/root-write-mode.js +3 -5
  249. package/dist/root.d.ts +4 -1
  250. package/dist/root.d.ts.map +1 -1
  251. package/dist/secret-file.d.ts.map +1 -1
  252. package/dist/secret-file.js +2 -1
  253. package/dist/secret-read-async.d.ts.map +1 -1
  254. package/dist/secret-read-async.js +2 -1
  255. package/dist/secure-file.d.ts.map +1 -1
  256. package/dist/secure-file.js +25 -8
  257. package/dist/secure-temp-dir.d.ts.map +1 -1
  258. package/dist/secure-temp-dir.js +2 -1
  259. package/dist/sibling-staged-file.d.ts +1 -0
  260. package/dist/sibling-staged-file.d.ts.map +1 -1
  261. package/dist/sibling-staged-file.js +42 -8
  262. package/dist/sibling-temp.d.ts +2 -0
  263. package/dist/sibling-temp.d.ts.map +1 -1
  264. package/dist/sibling-temp.js +6 -4
  265. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  266. package/dist/sidecar-lock-acquire.js +17 -5
  267. package/dist/sidecar-lock-policy.d.ts +2 -0
  268. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  269. package/dist/sidecar-lock-policy.js +20 -1
  270. package/dist/staged-directory.d.ts.map +1 -1
  271. package/dist/staged-directory.js +4 -3
  272. package/dist/temp-cleanup.d.ts.map +1 -1
  273. package/dist/temp-cleanup.js +3 -2
  274. package/dist/temp-target.d.ts +14 -12
  275. package/dist/temp-target.d.ts.map +1 -1
  276. package/dist/temp-target.js +12 -6
  277. package/dist/timing.d.ts +1 -0
  278. package/dist/timing.d.ts.map +1 -1
  279. package/dist/timing.js +25 -6
  280. package/dist/trash.d.ts.map +1 -1
  281. package/dist/trash.js +9 -7
  282. package/dist/unicode-path.d.ts +3 -0
  283. package/dist/unicode-path.d.ts.map +1 -0
  284. package/dist/unicode-path.js +13 -0
  285. package/dist/walk.d.ts.map +1 -1
  286. package/dist/walk.js +9 -28
  287. package/dist/windows-owner.d.ts +9 -12
  288. package/dist/windows-owner.d.ts.map +1 -1
  289. package/dist/windows-owner.js +24 -58
  290. package/dist/write-file-handle.d.ts +7 -0
  291. package/dist/write-file-handle.d.ts.map +1 -0
  292. package/dist/write-file-handle.js +26 -0
  293. package/docs/advanced.md +22 -4
  294. package/docs/archive.md +44 -9
  295. package/docs/atomic.md +24 -1
  296. package/docs/config.md +1 -0
  297. package/docs/contributing.md +36 -1
  298. package/docs/copy.md +155 -0
  299. package/docs/directory-identity.md +85 -0
  300. package/docs/durability.md +53 -3
  301. package/docs/entries.md +109 -0
  302. package/docs/errors.md +4 -4
  303. package/docs/file-store.md +15 -0
  304. package/docs/guest.md +141 -0
  305. package/docs/in-place-write.md +81 -0
  306. package/docs/index.md +2 -0
  307. package/docs/install.md +31 -0
  308. package/docs/local-roots.md +8 -1
  309. package/docs/native-helper.md +10 -3
  310. package/docs/native.md +18 -1
  311. package/docs/output.md +32 -6
  312. package/docs/path-case.md +64 -0
  313. package/docs/path-scope.md +1 -1
  314. package/docs/permissions.md +29 -10
  315. package/docs/positional-read.md +63 -0
  316. package/docs/public-api.md +31 -2
  317. package/docs/root.md +196 -6
  318. package/docs/secure-file.md +2 -0
  319. package/docs/security-model.md +14 -0
  320. package/docs/sidecar-lock.md +12 -3
  321. package/docs/temp.md +35 -6
  322. package/docs/timing.md +2 -0
  323. package/docs/types.md +19 -12
  324. package/docs/walk.md +54 -5
  325. package/docs/writing.md +173 -5
  326. package/package.json +19 -8
@@ -1,9 +1,10 @@
1
1
  import os from "node:os";
2
2
  import { getNativeBinding } from "./native.js";
3
- import { executePermissionCommand, formatPermissionErrorDetail, getPermissionCommandFailure, } from "./permission-exec.js";
3
+ import { executePermissionCommand } from "./permission-exec.js";
4
+ import { safeStat } from "./permissions.js";
4
5
  import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
5
6
  import { resolveWindowsSystemCommand } from "./windows-command.js";
6
- import { inspectWindowsOwner, resolveWindowsCurrentUserSid, resolveWindowsPrincipalSids, } from "./windows-owner.js";
7
+ import { inspectWindowsOwner } from "./windows-owner.js";
7
8
  const INHERIT_FLAGS = new Set(["I", "OI", "CI", "IO", "NP"]);
8
9
  const WORLD_PRINCIPALS = new Set(["everyone", "users", "builtin\\users", "authenticated users", "nt authority\\authenticated users", "anonymous logon", "nt authority\\anonymous logon", "guests", "builtin\\guests", "interactive", "nt authority\\interactive", "network", "nt authority\\network", "local"]);
9
10
  const TRUSTED_BASE = new Set([
@@ -92,13 +93,7 @@ export async function inspectWindowsPermissions(params) {
92
93
  const error = `Windows owner inspection failed: ${owner.error}`;
93
94
  return { ...unverified, ownerError: owner.error, error, errorDetail: owner.errorDetail, errorCause: owner.errorCause };
94
95
  }
95
- const acl = await inspectWindowsAcl(params.targetPath, {
96
- env: params.opts?.env,
97
- exec: params.opts?.exec,
98
- currentUserSid: owner.currentUserSid,
99
- principalSids: owner.principalSids,
100
- principalTranslationFailed: owner.principalTranslationFailed,
101
- });
96
+ const acl = summarizeWindowsOwnerAcl(owner);
102
97
  const ownerFields = {
103
98
  ...(owner.sid ? { ownerSid: owner.sid } : {}),
104
99
  ...(owner.trusted !== undefined ? { ownerTrusted: owner.trusted } : {}),
@@ -132,8 +127,18 @@ export function resolveWindowsUserPrincipal(env, userInfo = defaultWindowsUserIn
132
127
  const domain = env?.USERDOMAIN?.trim();
133
128
  return domain ? `${domain}\\${username}` : username;
134
129
  }
130
+ function buildTrustedSidPrincipals(env) {
131
+ const trusted = new Set();
132
+ const userSid = normalizeSid(env?.USERSID ?? "");
133
+ if (userSid && SID_RE.test(userSid) && !WORLD_SIDS.has(userSid))
134
+ trusted.add(userSid);
135
+ return trusted;
136
+ }
135
137
  function buildTrustedPrincipals(env) {
136
- const trusted = new Set(TRUSTED_BASE);
138
+ const trusted = new Set([
139
+ ...TRUSTED_BASE,
140
+ ...buildTrustedSidPrincipals(env),
141
+ ]);
137
142
  const principal = resolveWindowsUserPrincipal(env);
138
143
  if (principal) {
139
144
  trusted.add(normalize(principal));
@@ -141,9 +146,6 @@ function buildTrustedPrincipals(env) {
141
146
  if (userOnly)
142
147
  trusted.add(normalize(userOnly));
143
148
  }
144
- const userSid = normalizeSid(env?.USERSID ?? "");
145
- if (userSid && SID_RE.test(userSid) && !WORLD_SIDS.has(userSid))
146
- trusted.add(userSid);
147
149
  return trusted;
148
150
  }
149
151
  function classifyPrincipal(principal, trustedPrincipals) {
@@ -215,7 +217,11 @@ export function parseIcaclsOutput(output, targetPath) {
215
217
  return entries;
216
218
  }
217
219
  export function summarizeWindowsAcl(entries, env) {
218
- const trustedPrincipals = buildTrustedPrincipals(env);
220
+ // Canonical SID entries cannot match account names. Classify them directly
221
+ // so structured descriptor facts do not depend on a separate user lookup.
222
+ const trustedPrincipals = entries.every((entry) => SID_RE.test(normalize(entry.sid ?? entry.principal)))
223
+ ? buildTrustedSidPrincipals(env)
224
+ : buildTrustedPrincipals(env);
219
225
  const trusted = [];
220
226
  const untrustedWorld = [];
221
227
  const untrustedGroup = [];
@@ -230,49 +236,75 @@ export function summarizeWindowsAcl(entries, env) {
230
236
  }
231
237
  return { trusted, untrustedWorld, untrustedGroup };
232
238
  }
233
- export async function inspectWindowsAcl(targetPath, opts) {
234
- let command = "";
235
- let startedAt = performance.now();
236
- const exec = async (file, args) => {
237
- command = file;
238
- startedAt = performance.now();
239
- return await (opts?.exec ?? defaultPermissionExec)(file, args);
240
- };
241
- try {
242
- if (opts?.principalTranslationFailed)
243
- throw new Error("Windows ACL principal SID translation failed");
244
- const { stdout, stderr } = await exec(resolveWindowsSystemCommand("icacls.exe", opts?.env), [targetPath]);
245
- let entries = parseIcaclsOutput(`${stdout}\n${stderr}`.trim(), targetPath);
246
- if (!entries.length)
247
- throw new Error("Windows ACL output could not be verified");
248
- const unresolvedPrincipals = entries.filter((entry) => !entry.sid).map((entry) => entry.principal);
249
- const principalSids = await resolveWindowsPrincipalSids({ principals: unresolvedPrincipals, known: opts?.principalSids, env: opts?.env, exec });
250
- entries = entries.map((entry) => {
251
- const sid = entry.sid ?? principalSids[entry.principal.toLowerCase()];
252
- if (!sid)
253
- throw new Error(`Windows ACL principal SID could not be verified: ${entry.principal}`);
254
- return { ...entry, sid };
255
- });
256
- let currentUserSid = normalizeSid(opts?.currentUserSid ?? "");
257
- let effectiveEnv = currentUserSid ? { USERSID: currentUserSid } : undefined;
258
- let { trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv);
259
- if (!currentUserSid && untrustedGroup.some((entry) => entry.sid && !TRUSTED_SIDS.has(entry.sid))) {
260
- currentUserSid = (await resolveWindowsCurrentUserSid({ exec, env: opts?.env })) ?? "";
261
- if (currentUserSid) {
262
- effectiveEnv = { USERSID: currentUserSid };
263
- ({ trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv));
264
- }
265
- }
266
- return { ok: true, entries, trusted, untrustedWorld, untrustedGroup };
267
- }
268
- catch (err) {
239
+ const MASK_RIGHTS = [
240
+ [0x1000_0000, "GA"], [0x8000_0000, "GR"], [0x4000_0000, "GW"], [0x2000_0000, "GE"],
241
+ [0x0001_0000, "D"], [0x0002_0000, "RC"], [0x0004_0000, "WDAC"], [0x0008_0000, "WO"],
242
+ [0x0010_0000, "S"], [0x0001, "RD"], [0x0002, "WD"], [0x0004, "AD"],
243
+ [0x0008, "REA"], [0x0010, "WEA"], [0x0020, "X"], [0x0040, "DC"], [0x0080, "RA"], [0x0100, "WA"],
244
+ ];
245
+ function summarizeWindowsOwnerAcl(owner) {
246
+ const error = owner.error ?? owner.aclError;
247
+ if (error || owner.daclPresent === undefined || !owner.aces || !owner.currentUserSid) {
269
248
  return {
270
249
  ok: false, entries: [], trusted: [], untrustedWorld: [], untrustedGroup: [],
271
- error: formatPermissionErrorDetail(String(err)),
272
- errorDetail: getPermissionCommandFailure(err, command, performance.now() - startedAt),
273
- errorCause: err,
250
+ error: error ?? "Windows ACL query returned incomplete descriptor data",
251
+ errorDetail: owner.errorDetail, errorCause: owner.errorCause,
274
252
  };
275
253
  }
254
+ // A null DACL grants everyone full access; an empty DACL grants nothing.
255
+ const grants = owner.daclPresent ? owner.aces : [{ sid: "s-1-1-0", mask: 0x001f_01ff, deny: false, inheritOnly: false }];
256
+ const entries = grants.filter(ace => !ace.deny && !ace.inheritOnly).map(ace => {
257
+ const rights = ace.mask === 0x001f_01ff ? ["F"] : MASK_RIGHTS.filter(([mask]) => (ace.mask & mask) !== 0).map(([, name]) => name);
258
+ return {
259
+ principal: ace.sid, sid: ace.sid, rights, rawRights: "(" + rights.join(",") + ")",
260
+ // Retain the coarse policy's conservative treatment of read-control and
261
+ // mutation rights. Denies do not subtract grants or claim effective access.
262
+ canRead: (ace.mask & 0x9002_0089) !== 0,
263
+ canWrite: (ace.mask & 0x500d_0156) !== 0,
264
+ };
265
+ });
266
+ return { ok: true, entries, ...summarizeWindowsAcl(entries, { USERSID: owner.currentUserSid }) };
267
+ }
268
+ export async function inspectWindowsAcl(targetPath, opts) {
269
+ if (opts?.principalTranslationFailed) {
270
+ const error = new Error("Windows ACL principal SID translation failed");
271
+ return summarizeWindowsOwnerAcl({ error: String(error), errorCause: error });
272
+ }
273
+ let owner;
274
+ if (process.platform === "win32" && opts?.env === undefined && opts?.exec === undefined) {
275
+ try {
276
+ const native = getNativeBinding();
277
+ const stat = native && await safeStat(targetPath);
278
+ // Keep the named-query behavior for leaf links; the native reader follows them.
279
+ const facts = stat?.ok && !stat.isSymlink ? native?.readOwnerAndDacl(targetPath) : undefined;
280
+ if (facts && !facts.fallbackRequired && facts.isLocal && facts.aceListComplete && facts.unsupportedAceTypes.length === 0 &&
281
+ facts.aces.every(ace => ace.flags.inherited && ace.mask !== 0)) {
282
+ // .NET normalizes explicit ACEs, but retains nonzero inherited ACEs in
283
+ // their original order without merging them. Other forms keep its query.
284
+ // Adapt descriptor facts only: the public summary owns its SID and rights
285
+ // classification, which differs from the native secure-read policy.
286
+ owner = {
287
+ daclPresent: facts.daclPresent,
288
+ currentUserSid: facts.currentUserSid,
289
+ aces: facts.aces.map(ace => ({
290
+ sid: ace.sid,
291
+ mask: ace.mask,
292
+ deny: ace.aceType === "deny",
293
+ inheritOnly: ace.flags.inheritOnly,
294
+ })),
295
+ };
296
+ }
297
+ }
298
+ catch {
299
+ // Preserve this inspector's structured fallback and command diagnostics
300
+ // when the optional helper or native descriptor query is unavailable.
301
+ }
302
+ }
303
+ owner ??= await inspectWindowsOwner({ targetPath, env: opts?.env, exec: opts?.exec ?? defaultPermissionExec });
304
+ return summarizeWindowsOwnerAcl({
305
+ ...owner,
306
+ currentUserSid: normalizeSid(opts?.currentUserSid ?? "") || owner.currentUserSid,
307
+ });
276
308
  }
277
309
  export function formatWindowsAclSummary(summary) {
278
310
  if (!summary.ok)
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-open.d.ts","sourceRoot":"","sources":["../src/pinned-open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAMzB,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,2BAA2B,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAExE,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,OAAO,EAAE,EACT,WAAW,GAAG,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CACpF,CAAC;AAQF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,yBAAyB,CAAC;IACxC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,GAAG,oBAAoB,CAoDvB"}
1
+ {"version":3,"file":"pinned-open.d.ts","sourceRoot":"","sources":["../src/pinned-open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAOzB,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,2BAA2B,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAExE,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,OAAO,EAAE,EACT,WAAW,GAAG,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CACpF,CAAC;AAQF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,yBAAyB,CAAC;IACxC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,GAAG,oBAAoB,CAqDvB"}
@@ -3,6 +3,7 @@ import { isUnsafeDeviceReadPath } from "./device-path.js";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
5
5
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
+ import { realpathSync } from "./realpath.js";
6
7
  function isExpectedPathError(error) {
7
8
  const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
8
9
  return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
@@ -22,7 +23,8 @@ export function openPinnedFileSync(params) {
22
23
  return { ok: false, reason: "validation" };
23
24
  }
24
25
  }
25
- const realPath = params.resolvedPath ?? ioFs.realpathSync(params.filePath);
26
+ const realPath = params.resolvedPath ??
27
+ (ioFs === fs ? realpathSync(params.filePath) : ioFs.realpathSync(params.filePath));
26
28
  if (isUnsafeDeviceReadPath(realPath)) {
27
29
  return { ok: false, reason: "validation" };
28
30
  }
@@ -27,7 +27,7 @@ function validatePinnedRelativePath(relativePath) {
27
27
  relativePath.startsWith("..\\")) {
28
28
  throw new FsSafeError("invalid-path", "relative path must not escape root");
29
29
  }
30
- for (const segment of relativePath.split("/")) {
30
+ for (const segment of relativePath.split(process.platform === "win32" ? /[\\/]/ : "/")) {
31
31
  if (segment === "..") {
32
32
  throw new FsSafeError("invalid-path", "relative path must not contain '..'");
33
33
  }
@@ -1,14 +1,15 @@
1
- import type { Readable } from "node:stream";
2
1
  import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
2
  import type { FileIdentityStat } from "./file-identity.js";
3
+ import { type CopyFileInput } from "./copy-file-input.js";
4
4
  export type PinnedWriteInput = {
5
5
  kind: "buffer";
6
6
  data: string | Buffer;
7
7
  encoding?: BufferEncoding;
8
8
  } | {
9
9
  kind: "stream";
10
- stream: Readable;
11
- };
10
+ stream: AsyncIterable<Uint8Array | string>;
11
+ stageBeforePublish?: boolean;
12
+ } | CopyFileInput;
12
13
  export type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
13
14
  export type PublishedWriteIdentity = Readonly<{
14
15
  dev: bigint;
@@ -22,10 +23,13 @@ export type PinnedWriteParams = {
22
23
  mode: number;
23
24
  sync?: boolean;
24
25
  overwrite?: boolean;
26
+ assertBeforeMutation?: () => void;
27
+ rejectFinalSymlink?: boolean;
25
28
  maxBytes?: number;
26
29
  input: PinnedWriteInput;
27
30
  rootIdentity?: FileIdentityStat;
28
31
  onRenameIdentityMismatch?: "verify-content";
32
+ onPublished?: (identity: PublishedWriteIdentity) => void;
29
33
  verifyPublished?: (fd: number, identity: PublishedWriteIdentity, parentGuard: AnyAsyncDirectoryGuard) => Promise<void>;
30
34
  };
31
35
  export declare function runPinnedWriteHelper(params: PinnedWriteParams): Promise<FileIdentityStat>;
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAA4F,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG7J,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAY3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAsDzC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAE5C,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
1
+ {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAOA,OAAO,EAA4F,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG7J,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAa3D,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,sBAAsB,CAAC;AAG7E,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5F,aAAa,CAAC;AA4ClB,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAEzD,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,sBAAsB,KAChC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
@@ -17,6 +17,10 @@ import { resolveReadOpenFlags } from "./read-open-flags.js";
17
17
  import { cleanupPinnedFilePath } from "./replace-file-temp-owner.js";
18
18
  import { withSidecarLock } from "./sidecar-lock.js";
19
19
  import { getFsSafeTestHooks } from "./test-hooks.js";
20
+ import { writeAllToFile } from "./write-file-handle.js";
21
+ import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
22
+ import { writeCopyFileToFd } from "./copy-file-input.js";
23
+ import { publishCopyStage } from "./publish-copy-stage.js";
20
24
  function byteLength(input, encoding) {
21
25
  return typeof input === "string"
22
26
  ? Buffer.byteLength(input, encoding ?? "utf8")
@@ -36,20 +40,13 @@ function assertWithinMaxBytes(bytes, maxBytes) {
36
40
  throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
37
41
  }
38
42
  }
39
- async function writeStreamToHandle(stream, handle, maxBytes) {
43
+ async function writeStreamToHandle(stream, handle, maxBytes, assertBeforeMutation) {
40
44
  let bytes = 0;
41
45
  for await (const chunk of stream) {
42
46
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
43
47
  bytes += buffer.byteLength;
44
48
  assertWithinMaxBytes(bytes, maxBytes);
45
- let offset = 0;
46
- while (offset < buffer.byteLength) {
47
- const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
48
- if (bytesWritten <= 0) {
49
- throw new FsSafeError("helper-failed", "fallback stream write made no progress");
50
- }
51
- offset += bytesWritten;
52
- }
49
+ await writeAllToFile(handle, buffer, { assertBeforeMutation });
53
50
  }
54
51
  }
55
52
  export async function runPinnedWriteHelper(params) {
@@ -95,7 +92,6 @@ async function runPinnedWriteFallback(params) {
95
92
  ? { dev: params.rootIdentity.dev, ino: params.rootIdentity.ino } : undefined;
96
93
  if (exactRoot)
97
94
  await inspectDirectoryIdentity(params.rootPath, exactRoot);
98
- const guardOptions = { bigint: exactRoot !== undefined };
99
95
  let parentPath = params.relativeParentPath
100
96
  ? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
101
97
  : params.rootPath;
@@ -107,15 +103,24 @@ async function runPinnedWriteFallback(params) {
107
103
  parentPath = await mkdirPathComponentsWithGuards({
108
104
  rootReal: params.rootPath,
109
105
  targetPath: parentPath,
106
+ assertBeforeMutation: params.assertBeforeMutation,
110
107
  beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
111
108
  });
112
109
  }
113
110
  const parentGuard = params.mkdir
114
- ? await createAsyncDirectoryGuard(parentPath, guardOptions)
115
- : await createNearestExistingDirectoryGuard(params.rootPath, parentPath, guardOptions);
111
+ ? await createAsyncDirectoryGuard(parentPath, { bigint: true })
112
+ : await createNearestExistingDirectoryGuard(params.rootPath, parentPath, { bigint: true });
116
113
  const targetPath = path.join(parentPath, params.basename);
117
- if (params.overwrite === false) {
118
- const handle = await withAsyncDirectoryGuards([parentGuard], async () => await fs.open(targetPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL, params.mode), {
114
+ if (params.overwrite === false && (params.input.kind === "buffer" ||
115
+ (params.input.kind === "stream" && !params.input.stageBeforePublish))) {
116
+ const assertBeforeMutation = () => {
117
+ assertFinalSymlinkRejected(targetPath, params.rejectFinalSymlink);
118
+ params.assertBeforeMutation?.();
119
+ };
120
+ const handle = await withAsyncDirectoryGuards([parentGuard], async () => {
121
+ assertBeforeMutation();
122
+ return await fs.open(targetPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL, params.mode);
123
+ }, {
119
124
  onPostGuardFailure: async (openedHandle) => {
120
125
  // The parent failed verification, so targetPath may now resolve
121
126
  // somewhere else. Close the fd, but do not clean up by path.
@@ -129,15 +134,12 @@ async function runPinnedWriteFallback(params) {
129
134
  createdIdentity = verificationIdentity;
130
135
  if (params.input.kind === "buffer") {
131
136
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
132
- if (typeof params.input.data === "string") {
133
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
134
- }
135
- else {
136
- await handle.writeFile(params.input.data);
137
- }
137
+ await writeAllToFile(handle, params.input.data, {
138
+ encoding: params.input.encoding, assertBeforeMutation,
139
+ });
138
140
  }
139
141
  else {
140
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
142
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes, assertBeforeMutation);
141
143
  }
142
144
  // Content writes may clear set-ID bits; finalize them through the owned fd.
143
145
  await handle.chmod(params.mode);
@@ -176,20 +178,21 @@ async function runPinnedWriteFallback(params) {
176
178
  let readHandle;
177
179
  let renamed = false;
178
180
  try {
181
+ params.assertBeforeMutation?.();
179
182
  handle = await fs.open(tempPath, tempFlags, params.mode);
180
183
  let verificationIdentity = fsSync.fstatSync(handle.fd, { bigint: true });
181
184
  tempIdentity = verificationIdentity;
182
185
  if (params.input.kind === "buffer") {
183
186
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
184
- if (typeof params.input.data === "string") {
185
- await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
186
- }
187
- else {
188
- await handle.writeFile(params.input.data);
189
- }
187
+ await writeAllToFile(handle, params.input.data, {
188
+ encoding: params.input.encoding, assertBeforeMutation: params.assertBeforeMutation,
189
+ });
190
+ }
191
+ else if (params.input.kind === "file") {
192
+ await writeCopyFileToFd(handle.fd, params.input, params.maxBytes, params.assertBeforeMutation);
190
193
  }
191
194
  else {
192
- await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
195
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes, params.assertBeforeMutation);
193
196
  }
194
197
  tempStat = fsSync.fstatSync(handle.fd);
195
198
  const tempPathStat = fsSync.lstatSync(tempPath);
@@ -200,10 +203,28 @@ async function runPinnedWriteFallback(params) {
200
203
  await handle.chmod(params.mode);
201
204
  if (params.sync !== false)
202
205
  await syncFileBestEffort(handle);
206
+ if (params.input.kind === "file")
207
+ await params.input.verifySource();
203
208
  let verifiedIdentity = expectedTempStat;
204
209
  await withAsyncDirectoryGuards([parentGuard], async () => {
205
- await fs.rename(tempPath, targetPath);
206
- renamed = true;
210
+ assertFinalSymlinkRejected(targetPath, params.rejectFinalSymlink);
211
+ if (params.overwrite === false) {
212
+ publishCopyStage({
213
+ temporaryPath: tempPath, targetPath, fd: handle.fd,
214
+ identity: tempIdentity, parentGuard,
215
+ assertBeforeMutation: params.assertBeforeMutation,
216
+ onPublished: (identity) => {
217
+ renamed = true;
218
+ params.onPublished?.(identity);
219
+ },
220
+ });
221
+ }
222
+ else {
223
+ params.assertBeforeMutation?.();
224
+ await fs.rename(tempPath, targetPath);
225
+ renamed = true;
226
+ params.onPublished?.(verificationIdentity);
227
+ }
207
228
  await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
208
229
  if (params.sync !== false)
209
230
  await syncDirectoryBestEffort(parentPath);
@@ -0,0 +1,9 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ export type ReadFileWindowOptions = {
3
+ signal?: AbortSignal;
4
+ };
5
+ /** Fills a caller-owned buffer from an explicit position, stopping at EOF. */
6
+ export declare function readFileWindowFully(handle: FileHandle, buffer: Buffer, position: number, { signal }?: ReadFileWindowOptions): Promise<number>;
7
+ /** Synchronous positional read; neither variant advances or closes its descriptor. */
8
+ export declare function readFileWindowFullySync(fd: number, buffer: Buffer, position: number): number;
9
+ //# sourceMappingURL=positional-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"positional-read.d.ts","sourceRoot":"","sources":["../src/positional-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AASF,8EAA8E;AAC9E,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,EAAE,MAAM,EAAE,GAAE,qBAA0B,GACrC,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW5F"}
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
2
+ // Node's synchronous read length is a signed 32-bit value on supported runtimes.
3
+ const MAX_READ_BYTES = 0x7fffffff;
4
+ function validateWindow(buffer, position) {
5
+ if (!Number.isSafeInteger(position) || position < 0 ||
6
+ !Number.isSafeInteger(position + buffer.length)) {
7
+ throw new RangeError("read position and window end must be non-negative safe integers");
8
+ }
9
+ }
10
+ /** Fills a caller-owned buffer from an explicit position, stopping at EOF. */
11
+ export async function readFileWindowFully(handle, buffer, position, { signal } = {}) {
12
+ validateWindow(buffer, position);
13
+ signal?.throwIfAborted();
14
+ let total = 0;
15
+ while (total < buffer.length) {
16
+ const { bytesRead } = await handle.read(buffer, total, Math.min(buffer.length - total, MAX_READ_BYTES), position + total);
17
+ // Settle the pending read before cancellation lets the caller reuse its buffer or handle.
18
+ signal?.throwIfAborted();
19
+ if (bytesRead === 0)
20
+ break;
21
+ total += bytesRead;
22
+ }
23
+ return total;
24
+ }
25
+ /** Synchronous positional read; neither variant advances or closes its descriptor. */
26
+ export function readFileWindowFullySync(fd, buffer, position) {
27
+ validateWindow(buffer, position);
28
+ let total = 0;
29
+ while (total < buffer.length) {
30
+ const bytesRead = fs.readSync(fd, buffer, total, Math.min(buffer.length - total, MAX_READ_BYTES), position + total);
31
+ if (bytesRead === 0)
32
+ break;
33
+ total += bytesRead;
34
+ }
35
+ return total;
36
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ import path from "node:path";
5
5
  import { fileStore, fileStoreSync, } from "./file-store.js";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { isNotFoundPathError } from "./path.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
8
10
  import { throwFsSafeReadError } from "./root-errors.js";
9
11
  import { matchRootFileOpenFailure, openRootFileSync, } from "./root-file.js";
10
12
  import { registerTempPathForExit, } from "./temp-cleanup.js";
@@ -66,7 +68,7 @@ function throwTempWorkspaceOpenFailure(failure) {
66
68
  });
67
69
  }
68
70
  async function ensurePrivateDirectory(dir, mode) {
69
- await fs.mkdir(dir, { recursive: true, mode });
71
+ await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode });
70
72
  const stat = fsSync.statSync(dir);
71
73
  if (!stat.isDirectory()) {
72
74
  throw new Error(`Temp root must be a directory: ${dir}`);
@@ -74,7 +76,7 @@ async function ensurePrivateDirectory(dir, mode) {
74
76
  await fs.chmod(dir, mode).catch(() => undefined);
75
77
  }
76
78
  function ensurePrivateDirectorySync(dir, mode) {
77
- fsSync.mkdirSync(dir, { recursive: true, mode });
79
+ fsSync.mkdirSync(recursiveMkdirPath(dir), { recursive: true, mode });
78
80
  const stat = fsSync.statSync(dir);
79
81
  if (!stat.isDirectory()) {
80
82
  throw new Error(`Temp root must be a directory: ${dir}`);
@@ -93,7 +95,7 @@ async function createTempWorkspace(options) {
93
95
  const requestedRoot = path.resolve(options.rootDir);
94
96
  let root = requestedRoot;
95
97
  try {
96
- root = fsSync.realpathSync.native(requestedRoot);
98
+ root = realpathSync.native(requestedRoot);
97
99
  }
98
100
  catch {
99
101
  root = requestedRoot;
@@ -194,7 +196,7 @@ export function tempWorkspaceSync(options) {
194
196
  const requestedRoot = path.resolve(options.rootDir);
195
197
  let root = requestedRoot;
196
198
  try {
197
- root = fsSync.realpathSync.native(requestedRoot);
199
+ root = realpathSync.native(requestedRoot);
198
200
  }
199
201
  catch {
200
202
  root = requestedRoot;
@@ -0,0 +1,13 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
+ import type { PublishedWriteIdentity } from "./pinned-write.js";
4
+ export declare function publishCopyStage(params: {
5
+ temporaryPath: string;
6
+ targetPath: string;
7
+ fd: number;
8
+ identity: BigIntStats;
9
+ parentGuard: AnyAsyncDirectoryGuard;
10
+ assertBeforeMutation?: () => void;
11
+ onPublished?: (identity: PublishedWriteIdentity) => void;
12
+ }): void;
13
+ //# sourceMappingURL=publish-copy-stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-copy-stage.d.ts","sourceRoot":"","sources":["../src/publish-copy-stage.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAIhE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,sBAAsB,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC1D,GAAG,IAAI,CAqCP"}
@@ -0,0 +1,47 @@
1
+ import fs, {} from "node:fs";
2
+ import path from "node:path";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
5
+ // Node has no portable no-replace rename. Link only our completed private stage,
6
+ // then unlink it in the same JS turn so local readers never observe two names.
7
+ export function publishCopyStage(params) {
8
+ const { identity, temporaryPath, targetPath } = params;
9
+ const staged = fs.lstatSync(temporaryPath, { bigint: true });
10
+ const opened = fs.fstatSync(params.fd, { bigint: true });
11
+ if (!staged.isFile() || staged.isSymbolicLink() || staged.nlink !== 1n ||
12
+ !sameFileIdentityForCleanup(staged, identity) ||
13
+ !sameFileIdentityForCleanup(opened, identity)) {
14
+ throw new FsSafeError("path-mismatch", "exclusive copy stage changed before publication");
15
+ }
16
+ params.assertBeforeMutation?.();
17
+ fs.linkSync(temporaryPath, targetPath);
18
+ let observerRejected = false;
19
+ let observerError;
20
+ try {
21
+ params.onPublished?.(identity);
22
+ }
23
+ catch (error) {
24
+ observerRejected = true;
25
+ observerError = error;
26
+ }
27
+ try {
28
+ const parent = fs.lstatSync(path.dirname(temporaryPath), { bigint: true });
29
+ const current = fs.lstatSync(temporaryPath, { bigint: true });
30
+ if (parent.isSymbolicLink() || !parent.isDirectory() ||
31
+ !sameFileIdentityForCleanup(parent, params.parentGuard.stat) ||
32
+ current.isSymbolicLink() || !current.isFile() ||
33
+ !sameFileIdentityForCleanup(current, identity)) {
34
+ throw new FsSafeError("path-mismatch", "published copy temporary name changed before cleanup");
35
+ }
36
+ // This removes the stage's name, never the published destination or source.
37
+ fs.unlinkSync(temporaryPath);
38
+ }
39
+ catch (error) {
40
+ throw new FsSafeError("helper-failed", "published copy temporary cleanup failed", {
41
+ cause: observerRejected ? new AggregateError([observerError, error], "observer and cleanup failed") : error,
42
+ details: { publication: "published", path: targetPath, dev: identity.dev, ino: identity.ino, cleanup: "failed" },
43
+ });
44
+ }
45
+ if (observerRejected)
46
+ throw observerError;
47
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GACpC,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AA0ND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4MtC"}
1
+ {"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GACpC,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AA2ND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4MtC"}
@@ -90,9 +90,9 @@ async function copyPinnedSource(params) {
90
90
  throw error;
91
91
  }
92
92
  let target;
93
- let createdIdentity = fsSync.fstatSync(nativeFd, { bigint: true });
94
- rememberCreatedTarget(params.failure, createdIdentity, "copy-verify");
95
93
  try {
94
+ let createdIdentity = fsSync.fstatSync(nativeFd, { bigint: true });
95
+ rememberCreatedTarget(params.failure, createdIdentity, "copy-verify");
96
96
  fsSync.fchmodSync(nativeFd, 0o600);
97
97
  createdIdentity = fsSync.fstatSync(nativeFd, { bigint: true });
98
98
  await getFsSafeTestHooks()?.afterPublishTargetCreated?.("exclusive-copy", params.targetPath, createdIdentity);
@@ -106,8 +106,9 @@ async function copyPinnedSource(params) {
106
106
  throw new FsSafeError("path-mismatch", "native publication target changed after copy");
107
107
  }
108
108
  const hashed = await hashFileHandle(target, params.native);
109
- fsSync.closeSync(nativeFd);
109
+ const completedFd = nativeFd;
110
110
  nativeFd = undefined;
111
+ fsSync.closeSync(completedFd);
111
112
  return {
112
113
  handle: target,
113
114
  exactIdentity: opened,
@@ -126,9 +127,9 @@ async function copyPinnedSource(params) {
126
127
  }
127
128
  }
128
129
  const target = await fs.open(params.targetPath, "wx+", 0o600);
129
- const createdIdentity = fsSync.fstatSync(target.fd, { bigint: true });
130
- rememberCreatedTarget(params.failure, createdIdentity, "copy-verify");
131
130
  try {
131
+ const createdIdentity = fsSync.fstatSync(target.fd, { bigint: true });
132
+ rememberCreatedTarget(params.failure, createdIdentity, "copy-verify");
132
133
  await target.chmod(0o600);
133
134
  const exactIdentity = fsSync.fstatSync(target.fd, { bigint: true });
134
135
  await getFsSafeTestHooks()?.afterPublishTargetCreated?.("exclusive-copy", params.targetPath, exactIdentity);
@@ -0,0 +1,4 @@
1
+ export declare const realpathSync: ((input: string) => string) & {
2
+ native: (input: string) => string;
3
+ };
4
+ //# sourceMappingURL=realpath.d.ts.map