@openclaw/fs-safe 0.12.0 → 0.13.1

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 (395) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +12 -7
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +32 -11
  5. package/dist/advanced.d.ts +2 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +2 -0
  8. package/dist/archive-input.d.ts.map +1 -1
  9. package/dist/archive-input.js +6 -2
  10. package/dist/archive-merge.d.ts.map +1 -1
  11. package/dist/archive-merge.js +15 -2
  12. package/dist/archive-native.d.ts.map +1 -1
  13. package/dist/archive-native.js +7 -19
  14. package/dist/archive-plan.js +1 -1
  15. package/dist/archive-read.d.ts.map +1 -1
  16. package/dist/archive-read.js +18 -13
  17. package/dist/archive-staging.d.ts.map +1 -1
  18. package/dist/archive-staging.js +43 -10
  19. package/dist/archive-tar-inspect.d.ts.map +1 -1
  20. package/dist/archive-tar-inspect.js +4 -1
  21. package/dist/archive-zip-admission.d.ts +1 -1
  22. package/dist/archive-zip-admission.d.ts.map +1 -1
  23. package/dist/archive-zip-admission.js +2 -2
  24. package/dist/archive-zip-directory.d.ts +3 -0
  25. package/dist/archive-zip-directory.d.ts.map +1 -1
  26. package/dist/archive-zip-directory.js +13 -2
  27. package/dist/archive-zip-loader.d.ts +3 -2
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +30 -4
  30. package/dist/archive-zip-manifest.d.ts +5 -0
  31. package/dist/archive-zip-manifest.d.ts.map +1 -0
  32. package/dist/archive-zip-manifest.js +22 -0
  33. package/dist/archive-zip-names.d.ts +6 -1
  34. package/dist/archive-zip-names.d.ts.map +1 -1
  35. package/dist/archive-zip-names.js +35 -14
  36. package/dist/archive-zip-preflight.d.ts.map +1 -1
  37. package/dist/archive-zip-preflight.js +3 -2
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -4
  40. package/dist/copy-file-input.d.ts +1 -1
  41. package/dist/copy-file-input.d.ts.map +1 -1
  42. package/dist/copy-file-input.js +2 -0
  43. package/dist/darwin-acl.d.ts +4 -0
  44. package/dist/darwin-acl.d.ts.map +1 -0
  45. package/dist/darwin-acl.js +24 -0
  46. package/dist/deny-mutations.d.ts.map +1 -1
  47. package/dist/deny-mutations.js +8 -2
  48. package/dist/directory-durability.d.ts.map +1 -1
  49. package/dist/directory-durability.js +67 -23
  50. package/dist/directory-entry-path.d.ts +3 -0
  51. package/dist/directory-entry-path.d.ts.map +1 -0
  52. package/dist/directory-entry-path.js +21 -0
  53. package/dist/directory-guard.d.ts +17 -1
  54. package/dist/directory-guard.d.ts.map +1 -1
  55. package/dist/directory-guard.js +134 -48
  56. package/dist/directory-mode-node.d.ts +12 -0
  57. package/dist/directory-mode-node.d.ts.map +1 -1
  58. package/dist/directory-mode-node.js +102 -4
  59. package/dist/effective-uid.d.ts +2 -0
  60. package/dist/effective-uid.d.ts.map +1 -0
  61. package/dist/effective-uid.js +25 -0
  62. package/dist/file-handle-transfer.d.ts.map +1 -1
  63. package/dist/file-handle-transfer.js +98 -27
  64. package/dist/file-hash.d.ts.map +1 -1
  65. package/dist/file-hash.js +3 -0
  66. package/dist/file-lock-sync.d.ts.map +1 -1
  67. package/dist/file-lock-sync.js +36 -6
  68. package/dist/file-lock.d.ts.map +1 -1
  69. package/dist/file-lock.js +29 -9
  70. package/dist/file-observation.d.ts +1 -1
  71. package/dist/file-observation.d.ts.map +1 -1
  72. package/dist/file-store-boundary.d.ts +6 -2
  73. package/dist/file-store-boundary.d.ts.map +1 -1
  74. package/dist/file-store-boundary.js +20 -65
  75. package/dist/file-store-copy-source.d.ts +5 -0
  76. package/dist/file-store-copy-source.d.ts.map +1 -0
  77. package/dist/file-store-copy-source.js +31 -0
  78. package/dist/file-store-path.d.ts.map +1 -1
  79. package/dist/file-store-path.js +4 -1
  80. package/dist/file-store-sync-directory.d.ts +16 -0
  81. package/dist/file-store-sync-directory.d.ts.map +1 -0
  82. package/dist/file-store-sync-directory.js +349 -0
  83. package/dist/file-store.d.ts.map +1 -1
  84. package/dist/file-store.js +11 -28
  85. package/dist/filename.d.ts.map +1 -1
  86. package/dist/filename.js +65 -20
  87. package/dist/fs.d.ts.map +1 -1
  88. package/dist/fs.js +3 -2
  89. package/dist/guarded-mkdir.d.ts +8 -0
  90. package/dist/guarded-mkdir.d.ts.map +1 -1
  91. package/dist/guarded-mkdir.js +176 -27
  92. package/dist/guest.d.ts.map +1 -1
  93. package/dist/guest.js +4 -1
  94. package/dist/home-dir.d.ts.map +1 -1
  95. package/dist/home-dir.js +73 -10
  96. package/dist/install-path.d.ts.map +1 -1
  97. package/dist/install-path.js +54 -19
  98. package/dist/json-durable-queue-ownership.d.ts +6 -0
  99. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  100. package/dist/json-durable-queue-ownership.js +89 -42
  101. package/dist/json-durable-queue-paths.d.ts +11 -0
  102. package/dist/json-durable-queue-paths.d.ts.map +1 -0
  103. package/dist/json-durable-queue-paths.js +42 -0
  104. package/dist/json-durable-queue-read.d.ts.map +1 -1
  105. package/dist/json-durable-queue-read.js +2 -0
  106. package/dist/json-durable-queue.d.ts.map +1 -1
  107. package/dist/json-durable-queue.js +50 -58
  108. package/dist/json-store.d.ts.map +1 -1
  109. package/dist/json-store.js +5 -1
  110. package/dist/json.d.ts.map +1 -1
  111. package/dist/json.js +54 -22
  112. package/dist/local-file-access.d.ts.map +1 -1
  113. package/dist/local-file-access.js +4 -0
  114. package/dist/local-file-descriptor.d.ts +17 -0
  115. package/dist/local-file-descriptor.d.ts.map +1 -0
  116. package/dist/local-file-descriptor.js +84 -0
  117. package/dist/local-roots.d.ts.map +1 -1
  118. package/dist/local-roots.js +35 -7
  119. package/dist/move-path-cleanup.d.ts +2 -0
  120. package/dist/move-path-cleanup.d.ts.map +1 -1
  121. package/dist/move-path-cleanup.js +44 -18
  122. package/dist/move-path.d.ts.map +1 -1
  123. package/dist/move-path.js +31 -6
  124. package/dist/native-binding.d.ts +17 -0
  125. package/dist/native-binding.d.ts.map +1 -1
  126. package/dist/native-binding.js +8 -1
  127. package/dist/native-directory-observation.d.ts +17 -0
  128. package/dist/native-directory-observation.d.ts.map +1 -0
  129. package/dist/native-directory-observation.js +37 -0
  130. package/dist/native-operations.d.ts.map +1 -1
  131. package/dist/native-operations.js +6 -4
  132. package/dist/native-parent-admission.d.ts +32 -0
  133. package/dist/native-parent-admission.d.ts.map +1 -0
  134. package/dist/native-parent-admission.js +127 -0
  135. package/dist/native-pinned-write-windows.d.ts +0 -1
  136. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  137. package/dist/native-pinned-write-windows.js +8 -9
  138. package/dist/native-pinned-write.d.ts.map +1 -1
  139. package/dist/native-pinned-write.js +314 -42
  140. package/dist/native-staged-file.d.ts +4 -4
  141. package/dist/native-staged-file.d.ts.map +1 -1
  142. package/dist/native-staged-file.js +20 -10
  143. package/dist/native.d.ts +1 -1
  144. package/dist/native.d.ts.map +1 -1
  145. package/dist/native.js +7 -2
  146. package/dist/output.d.ts.map +1 -1
  147. package/dist/output.js +15 -5
  148. package/dist/overwrite-file-handle.d.ts.map +1 -1
  149. package/dist/overwrite-file-handle.js +5 -1
  150. package/dist/owner-dacl.d.ts.map +1 -1
  151. package/dist/owner-dacl.js +2 -0
  152. package/dist/path-policy.d.ts.map +1 -1
  153. package/dist/path-policy.js +7 -2
  154. package/dist/path-prefix.d.ts +7 -0
  155. package/dist/path-prefix.d.ts.map +1 -0
  156. package/dist/path-prefix.js +82 -0
  157. package/dist/path-scope-lexical.d.ts.map +1 -1
  158. package/dist/path-scope-lexical.js +18 -8
  159. package/dist/path-segment-route.d.ts +7 -0
  160. package/dist/path-segment-route.d.ts.map +1 -0
  161. package/dist/path-segment-route.js +24 -0
  162. package/dist/path-suffix-aliases.d.ts +10 -0
  163. package/dist/path-suffix-aliases.d.ts.map +1 -0
  164. package/dist/path-suffix-aliases.js +386 -0
  165. package/dist/path.d.ts.map +1 -1
  166. package/dist/path.js +16 -5
  167. package/dist/permissions-windows.d.ts.map +1 -1
  168. package/dist/permissions-windows.js +14 -3
  169. package/dist/permissions.d.ts.map +1 -1
  170. package/dist/permissions.js +37 -8
  171. package/dist/pinned-mutation-admission.d.ts +25 -0
  172. package/dist/pinned-mutation-admission.d.ts.map +1 -0
  173. package/dist/pinned-mutation-admission.js +425 -0
  174. package/dist/pinned-mutation-observation.d.ts +34 -0
  175. package/dist/pinned-mutation-observation.d.ts.map +1 -0
  176. package/dist/pinned-mutation-observation.js +142 -0
  177. package/dist/pinned-mutation-shared-route.d.ts +24 -0
  178. package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
  179. package/dist/pinned-mutation-shared-route.js +70 -0
  180. package/dist/pinned-open.d.ts +6 -0
  181. package/dist/pinned-open.d.ts.map +1 -1
  182. package/dist/pinned-open.js +28 -10
  183. package/dist/pinned-write-types.d.ts +75 -0
  184. package/dist/pinned-write-types.d.ts.map +1 -0
  185. package/dist/pinned-write-types.js +1 -0
  186. package/dist/pinned-write.d.ts +7 -33
  187. package/dist/pinned-write.d.ts.map +1 -1
  188. package/dist/pinned-write.js +151 -17
  189. package/dist/private-directory.d.ts.map +1 -1
  190. package/dist/private-directory.js +2 -0
  191. package/dist/private-producer-handoff.d.ts +16 -0
  192. package/dist/private-producer-handoff.d.ts.map +1 -0
  193. package/dist/private-producer-handoff.js +272 -0
  194. package/dist/private-temp-workspace.d.ts +2 -39
  195. package/dist/private-temp-workspace.d.ts.map +1 -1
  196. package/dist/private-temp-workspace.js +183 -77
  197. package/dist/publish-file.d.ts.map +1 -1
  198. package/dist/publish-file.js +38 -32
  199. package/dist/regular-file.d.ts.map +1 -1
  200. package/dist/regular-file.js +56 -45
  201. package/dist/replace-directory.d.ts.map +1 -1
  202. package/dist/replace-directory.js +12 -5
  203. package/dist/replace-file-temp-owner.d.ts +1 -0
  204. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  205. package/dist/replace-file-temp-owner.js +12 -1
  206. package/dist/replace-file.d.ts.map +1 -1
  207. package/dist/replace-file.js +24 -24
  208. package/dist/root-boundary.d.ts +4 -0
  209. package/dist/root-boundary.d.ts.map +1 -1
  210. package/dist/root-boundary.js +6 -1
  211. package/dist/root-context.d.ts +12 -1
  212. package/dist/root-context.d.ts.map +1 -1
  213. package/dist/root-context.js +57 -11
  214. package/dist/root-directory-creation.d.ts +13 -0
  215. package/dist/root-directory-creation.d.ts.map +1 -0
  216. package/dist/root-directory-creation.js +212 -0
  217. package/dist/root-directory-list.d.ts +16 -4
  218. package/dist/root-directory-list.d.ts.map +1 -1
  219. package/dist/root-directory-list.js +180 -39
  220. package/dist/root-directory.d.ts +23 -0
  221. package/dist/root-directory.d.ts.map +1 -0
  222. package/dist/root-directory.js +141 -0
  223. package/dist/root-errors.d.ts.map +1 -1
  224. package/dist/root-errors.js +12 -1
  225. package/dist/root-file-final-admission.d.ts +21 -0
  226. package/dist/root-file-final-admission.d.ts.map +1 -0
  227. package/dist/root-file-final-admission.js +83 -0
  228. package/dist/root-file.d.ts.map +1 -1
  229. package/dist/root-file.js +103 -24
  230. package/dist/root-impl.d.ts +1 -0
  231. package/dist/root-impl.d.ts.map +1 -1
  232. package/dist/root-impl.js +289 -317
  233. package/dist/root-move-noreplace.d.ts +14 -0
  234. package/dist/root-move-noreplace.d.ts.map +1 -0
  235. package/dist/root-move-noreplace.js +202 -0
  236. package/dist/root-observed-path.d.ts +9 -0
  237. package/dist/root-observed-path.d.ts.map +1 -0
  238. package/dist/root-observed-path.js +95 -0
  239. package/dist/root-path-errors.d.ts +12 -0
  240. package/dist/root-path-errors.d.ts.map +1 -0
  241. package/dist/root-path-errors.js +13 -0
  242. package/dist/root-path-existing.d.ts.map +1 -1
  243. package/dist/root-path-existing.js +53 -20
  244. package/dist/root-path-observation.d.ts +63 -0
  245. package/dist/root-path-observation.d.ts.map +1 -0
  246. package/dist/root-path-observation.js +180 -0
  247. package/dist/root-path-stat.d.ts +5 -0
  248. package/dist/root-path-stat.d.ts.map +1 -0
  249. package/dist/root-path-stat.js +101 -0
  250. package/dist/root-path-symlink.d.ts.map +1 -1
  251. package/dist/root-path-symlink.js +23 -4
  252. package/dist/root-path.d.ts +11 -0
  253. package/dist/root-path.d.ts.map +1 -1
  254. package/dist/root-path.js +215 -53
  255. package/dist/root-paths-lexical.d.ts +9 -0
  256. package/dist/root-paths-lexical.d.ts.map +1 -0
  257. package/dist/root-paths-lexical.js +22 -0
  258. package/dist/root-paths.d.ts +3 -25
  259. package/dist/root-paths.d.ts.map +1 -1
  260. package/dist/root-paths.js +77 -154
  261. package/dist/root-read-admission.d.ts +26 -0
  262. package/dist/root-read-admission.d.ts.map +1 -0
  263. package/dist/root-read-admission.js +94 -0
  264. package/dist/root-remove-identity.d.ts +15 -0
  265. package/dist/root-remove-identity.d.ts.map +1 -0
  266. package/dist/root-remove-identity.js +89 -0
  267. package/dist/root-remove-receipt.d.ts +17 -0
  268. package/dist/root-remove-receipt.d.ts.map +1 -0
  269. package/dist/root-remove-receipt.js +37 -0
  270. package/dist/root-remove.d.ts +2 -1
  271. package/dist/root-remove.d.ts.map +1 -1
  272. package/dist/root-remove.js +172 -10
  273. package/dist/root-write-admission.d.ts +68 -0
  274. package/dist/root-write-admission.d.ts.map +1 -0
  275. package/dist/root-write-admission.js +322 -0
  276. package/dist/root-write-compatibility.d.ts +13 -0
  277. package/dist/root-write-compatibility.d.ts.map +1 -0
  278. package/dist/root-write-compatibility.js +74 -0
  279. package/dist/root-write-complete-parent.d.ts +42 -0
  280. package/dist/root-write-complete-parent.d.ts.map +1 -0
  281. package/dist/root-write-complete-parent.js +195 -0
  282. package/dist/root-write-publication.d.ts +24 -0
  283. package/dist/root-write-publication.d.ts.map +1 -0
  284. package/dist/root-write-publication.js +85 -0
  285. package/dist/root-write-verification.d.ts.map +1 -1
  286. package/dist/root-write-verification.js +6 -4
  287. package/dist/secret-file.d.ts.map +1 -1
  288. package/dist/secret-file.js +62 -24
  289. package/dist/secret-read-async.d.ts.map +1 -1
  290. package/dist/secret-read-async.js +18 -13
  291. package/dist/secret-read-policy.d.ts.map +1 -1
  292. package/dist/secret-read-policy.js +5 -1
  293. package/dist/secure-file.d.ts.map +1 -1
  294. package/dist/secure-file.js +92 -7
  295. package/dist/secure-temp-dir.d.ts +6 -3
  296. package/dist/secure-temp-dir.d.ts.map +1 -1
  297. package/dist/secure-temp-dir.js +121 -101
  298. package/dist/secure-temp-repair.d.ts +35 -0
  299. package/dist/secure-temp-repair.d.ts.map +1 -0
  300. package/dist/secure-temp-repair.js +106 -0
  301. package/dist/sibling-staged-file.d.ts +3 -2
  302. package/dist/sibling-staged-file.d.ts.map +1 -1
  303. package/dist/sibling-staged-file.js +179 -55
  304. package/dist/sibling-temp.d.ts.map +1 -1
  305. package/dist/sibling-temp.js +27 -13
  306. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  307. package/dist/sidecar-lock-acquire.js +44 -14
  308. package/dist/sidecar-lock-root.d.ts.map +1 -1
  309. package/dist/sidecar-lock-root.js +4 -2
  310. package/dist/staged-directory.d.ts +14 -5
  311. package/dist/staged-directory.d.ts.map +1 -1
  312. package/dist/staged-directory.js +54 -3
  313. package/dist/standalone-publication-path.d.ts +2 -0
  314. package/dist/standalone-publication-path.d.ts.map +1 -0
  315. package/dist/standalone-publication-path.js +6 -0
  316. package/dist/stat-observation.d.ts +11 -0
  317. package/dist/stat-observation.d.ts.map +1 -0
  318. package/dist/stat-observation.js +64 -0
  319. package/dist/strict-file-identity.d.ts.map +1 -1
  320. package/dist/strict-file-identity.js +39 -2
  321. package/dist/symlink-parents.d.ts.map +1 -1
  322. package/dist/symlink-parents.js +7 -2
  323. package/dist/temp-target.d.ts +2 -0
  324. package/dist/temp-target.d.ts.map +1 -1
  325. package/dist/temp-target.js +130 -3
  326. package/dist/temp-workspace-admission.d.ts +22 -0
  327. package/dist/temp-workspace-admission.d.ts.map +1 -0
  328. package/dist/temp-workspace-admission.js +374 -0
  329. package/dist/temp-workspace-child-admission.d.ts +13 -0
  330. package/dist/temp-workspace-child-admission.d.ts.map +1 -0
  331. package/dist/temp-workspace-child-admission.js +95 -0
  332. package/dist/temp-workspace-descriptor.d.ts +48 -0
  333. package/dist/temp-workspace-descriptor.d.ts.map +1 -0
  334. package/dist/temp-workspace-descriptor.js +363 -0
  335. package/dist/temp-workspace-identity.d.ts +15 -0
  336. package/dist/temp-workspace-identity.d.ts.map +1 -0
  337. package/dist/temp-workspace-identity.js +41 -0
  338. package/dist/temp-workspace-owner.d.ts +7 -6
  339. package/dist/temp-workspace-owner.d.ts.map +1 -1
  340. package/dist/temp-workspace-owner.js +121 -61
  341. package/dist/temp-workspace-permissions.d.ts +4 -0
  342. package/dist/temp-workspace-permissions.d.ts.map +1 -0
  343. package/dist/temp-workspace-permissions.js +32 -0
  344. package/dist/temp-workspace-types.d.ts +40 -0
  345. package/dist/temp-workspace-types.d.ts.map +1 -0
  346. package/dist/temp-workspace-types.js +1 -0
  347. package/dist/temp.d.ts +1 -1
  348. package/dist/temp.d.ts.map +1 -1
  349. package/dist/temp.js +1 -1
  350. package/dist/test-hooks.d.ts +7 -0
  351. package/dist/test-hooks.d.ts.map +1 -1
  352. package/dist/text-atomic.d.ts.map +1 -1
  353. package/dist/text-atomic.js +3 -1
  354. package/dist/trash.d.ts.map +1 -1
  355. package/dist/trash.js +54 -8
  356. package/dist/walk.d.ts.map +1 -1
  357. package/dist/walk.js +9 -6
  358. package/dist/windows-owner.d.ts.map +1 -1
  359. package/dist/windows-owner.js +5 -0
  360. package/dist/windows-path-alias.d.ts +39 -0
  361. package/dist/windows-path-alias.d.ts.map +1 -0
  362. package/dist/windows-path-alias.js +153 -0
  363. package/docs/advanced.md +25 -0
  364. package/docs/archive.md +23 -2
  365. package/docs/atomic.md +27 -3
  366. package/docs/copy.md +3 -0
  367. package/docs/durability.md +14 -0
  368. package/docs/errors.md +14 -6
  369. package/docs/file-store.md +24 -3
  370. package/docs/filename.md +14 -7
  371. package/docs/install-path.md +2 -2
  372. package/docs/install.md +8 -7
  373. package/docs/json-store.md +5 -1
  374. package/docs/json.md +11 -0
  375. package/docs/mutation-policy-proof.md +65 -0
  376. package/docs/native-helper.md +41 -11
  377. package/docs/native.md +32 -8
  378. package/docs/output.md +33 -11
  379. package/docs/path-prefix.md +64 -0
  380. package/docs/path-suffix-aliases.md +159 -0
  381. package/docs/path.md +11 -0
  382. package/docs/private-file-store.md +14 -0
  383. package/docs/public-api.md +9 -0
  384. package/docs/quickstart.md +1 -1
  385. package/docs/reading.md +8 -1
  386. package/docs/root.md +18 -3
  387. package/docs/secret-file.md +3 -0
  388. package/docs/secure-file.md +6 -2
  389. package/docs/security-model.md +75 -8
  390. package/docs/sidecar-lock.md +28 -1
  391. package/docs/store.md +5 -1
  392. package/docs/temp.md +260 -36
  393. package/docs/test-hooks.md +14 -0
  394. package/docs/writing.md +38 -8
  395. package/package.json +10 -10
@@ -13,8 +13,16 @@ import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransient
13
13
  import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
14
14
  import { createSuppressedError } from "./suppressed-error.js";
15
15
  import { sleep } from "./timing.js";
16
- async function resolveNormalizedTargetPath(targetPath, lockRoot) {
17
- const resolved = path.resolve(targetPath);
16
+ import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
17
+ function isCwdIndependentAbsolutePath(filePath) {
18
+ if (!path.isAbsolute(filePath))
19
+ return false;
20
+ // A leading separator on Windows is rooted on the process's current drive.
21
+ // Drive-qualified, UNC, and namespace roots are longer and need no cwd state.
22
+ return process.platform !== "win32" || path.parse(filePath).root.length > 1;
23
+ }
24
+ async function resolveNormalizedTargetPath(resolved, lockRoot) {
25
+ assertNoWindowsPathAlias(resolved);
18
26
  const dir = path.dirname(resolved);
19
27
  if (lockRoot) {
20
28
  // The target is an arbitration key, not necessarily inside the lock Root.
@@ -22,15 +30,23 @@ async function resolveNormalizedTargetPath(targetPath, lockRoot) {
22
30
  await lockRoot.resolve(".");
23
31
  const parent = await canonicalPathFromExistingAncestor(dir);
24
32
  await lockRoot.resolve(".");
25
- return path.join(parent, path.basename(resolved));
33
+ assertNoWindowsPathAlias(parent);
34
+ const normalized = path.join(parent, path.basename(resolved));
35
+ assertNoWindowsPathAlias(normalized);
36
+ return normalized;
26
37
  }
27
38
  await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
39
+ let parent;
28
40
  try {
29
- return path.join(realpathSync.native(dir), path.basename(resolved));
41
+ parent = realpathSync.native(dir);
30
42
  }
31
43
  catch {
32
44
  return resolved;
33
45
  }
46
+ assertNoWindowsPathAlias(parent);
47
+ const normalized = path.join(parent, path.basename(resolved));
48
+ assertNoWindowsPathAlias(normalized);
49
+ return normalized;
34
50
  }
35
51
  export async function acquireSidecarLock(options, context) {
36
52
  const retry = options.retry ?? {};
@@ -38,9 +54,24 @@ export async function acquireSidecarLock(options, context) {
38
54
  validateSidecarLockTimeoutMs(options.timeoutMs);
39
55
  validateSidecarLockStaleMs(options.staleMs);
40
56
  validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
57
+ const targetPath = options.targetPath;
58
+ const explicitLockPath = options.lockPath;
59
+ const anchoredTargetPath = anchorWindowsDriveRelativePath(targetPath);
60
+ assertNoWindowsPathAlias(anchoredTargetPath);
61
+ const resolvedTargetPath = path.resolve(anchoredTargetPath);
62
+ let resolvedLockPath;
63
+ if (explicitLockPath !== undefined) {
64
+ const anchoredLockPath = anchorWindowsDriveRelativePath(explicitLockPath);
65
+ assertNoWindowsPathAlias(anchoredLockPath);
66
+ resolvedLockPath = isCwdIndependentAbsolutePath(explicitLockPath)
67
+ ? explicitLockPath
68
+ : path.resolve(anchoredLockPath);
69
+ }
41
70
  context.ensureExitCleanupRegistered();
42
- const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath, options.lockRoot);
43
- const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
71
+ const lockRoot = options.lockRoot;
72
+ const normalizedTargetPath = await resolveNormalizedTargetPath(resolvedTargetPath, lockRoot);
73
+ const lockPath = resolvedLockPath ?? `${normalizedTargetPath}.lock`;
74
+ assertNoWindowsPathAlias(lockPath);
44
75
  let held = context.held.get(normalizedTargetPath);
45
76
  if (held &&
46
77
  options.reentrantOwner !== undefined &&
@@ -118,8 +149,7 @@ export async function acquireSidecarLock(options, context) {
118
149
  const payload = await options.payload();
119
150
  const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
120
151
  try {
121
- if (options.lockRoot) {
122
- const lockRoot = options.lockRoot;
152
+ if (lockRoot) {
123
153
  const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
124
154
  const observation = fileObservation();
125
155
  try {
@@ -180,7 +210,7 @@ export async function acquireSidecarLock(options, context) {
180
210
  snapshot,
181
211
  acquiredAt: Date.now(),
182
212
  metadata: options.metadata ?? {},
183
- lockRoot: options.lockRoot,
213
+ lockRoot,
184
214
  retainOnExit: options.retainOnExit,
185
215
  parsePayload: options.parsePayload,
186
216
  };
@@ -239,13 +269,13 @@ export async function acquireSidecarLock(options, context) {
239
269
  // Root-created records retain the creator's byte/token receipt;
240
270
  // partial direct writes use the exclusive descriptor's identity.
241
271
  await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
242
- lockRoot: options.lockRoot,
272
+ lockRoot,
243
273
  parsePayload: options.parsePayload,
244
274
  });
245
275
  }
246
276
  else if (createdSnapshot) {
247
277
  await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
248
- lockRoot: options.lockRoot,
278
+ lockRoot,
249
279
  parsePayload: options.parsePayload,
250
280
  });
251
281
  }
@@ -271,7 +301,7 @@ export async function acquireSidecarLock(options, context) {
271
301
  let lockFileOpenDenied = false;
272
302
  try {
273
303
  rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
274
- lockRoot: options.lockRoot,
304
+ lockRoot,
275
305
  rejectNonFile: true,
276
306
  discardObservation: "changed",
277
307
  onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
@@ -302,7 +332,7 @@ export async function acquireSidecarLock(options, context) {
302
332
  heldByThisProcess: context.held.has(normalizedTargetPath),
303
333
  })) {
304
334
  if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
305
- lockRoot: options.lockRoot,
335
+ lockRoot,
306
336
  parsePayload: options.parsePayload,
307
337
  }))) {
308
338
  await waitForRetry();
@@ -320,7 +350,7 @@ export async function acquireSidecarLock(options, context) {
320
350
  normalizedTargetPath,
321
351
  snapshot,
322
352
  shouldRemoveStaleLock: options.shouldRemoveStaleLock,
323
- lockRoot: options.lockRoot,
353
+ lockRoot,
324
354
  parsePayload: options.parsePayload,
325
355
  });
326
356
  if (removal === "removed" || removal === "changed") {
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAuD5B"}
1
+ {"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAyD5B"}
@@ -53,13 +53,15 @@ export async function openSidecarRoot(lockRoot, relative, discardObservation, on
53
53
  if (!discardObservation || (!missing && (!completeParents || !discardable)))
54
54
  throw error;
55
55
  try {
56
+ const probe = fileObservation();
56
57
  try {
57
- const current = await lockRoot.stat(relative);
58
+ const current = await probe.run(() => lockRoot.stat(relative));
58
59
  if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
59
60
  throw error;
60
61
  }
61
62
  catch (probeError) {
62
- if (!(probeError instanceof FsSafeError && probeError.code === "not-found"))
63
+ if (!(probeError instanceof FsSafeError && probeError.code === "not-found") &&
64
+ !probe.has(probeError, `stat-leaf-missing:${expectedRealPath}`))
63
65
  throw probeError;
64
66
  }
65
67
  for (const { dir, stat } of parents)
@@ -1,16 +1,25 @@
1
+ import { type BigIntStats } from "node:fs";
1
2
  import type { DirectoryReceipt } from "./directory-durability.js";
2
3
  import type { FileIdentityStat } from "./file-identity.js";
4
+ import { type MutationDirectoryObservation } from "./pinned-mutation-observation.js";
3
5
  import type { StagedFileReceipt } from "./staged-file-types.js";
4
- type DirectorySnapshot = StagedFileReceipt["directory"];
6
+ export type StagedDirectorySnapshot = StagedFileReceipt["directory"];
7
+ export type PolicyStagedDirectory = Readonly<{
8
+ directory: StagedDirectorySnapshot;
9
+ observation: MutationDirectoryObservation;
10
+ stat: BigIntStats;
11
+ }>;
5
12
  export declare function exactIdentityMatches(expected: FileIdentityStat, actual: Readonly<{
6
13
  dev: bigint;
7
14
  ino: bigint;
8
15
  }>): boolean;
9
- export declare function describeStagedDirectory(fd: number, pathname: string): DirectorySnapshot;
10
- export declare function assertStagedDirectoryCurrent(receipt: DirectorySnapshot): void;
16
+ export declare function describeStagedDirectory(fd: number, pathname: string): StagedDirectorySnapshot;
17
+ export declare function assertStagedDirectoryCurrent(receipt: StagedDirectorySnapshot): BigIntStats;
18
+ export declare function describePolicyStagedDirectory(fd: number, pathname: string): PolicyStagedDirectory;
19
+ export declare function assertPolicyStagedDirectoryCurrent(captured: PolicyStagedDirectory): BigIntStats;
20
+ export declare function refreshPolicyStagedDirectoryObservation(captured: PolicyStagedDirectory): MutationDirectoryObservation;
11
21
  export declare function openStagedDirectory(directory: string | DirectoryReceipt): {
12
22
  fd: number;
13
- receipt: DirectorySnapshot;
23
+ receipt: StagedDirectorySnapshot;
14
24
  };
15
- export {};
16
25
  //# sourceMappingURL=staged-directory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,KAAK,iBAAiB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAYvF;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAQ7E;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;CAC5B,CA+BA"}
1
+ {"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,uBAAuB,CAAC;IACnC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC,CAAC;AAEH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,uBAAuB,CAY7F;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CAS1F;AASD,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,qBAAqB,CAwBvB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,qBAAqB,GAC9B,WAAW,CAOb;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,qBAAqB,GAC9B,4BAA4B,CAU9B;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,uBAAuB,CAAC;CAClC,CAiCA"}
@@ -1,7 +1,10 @@
1
- import fs from "node:fs";
1
+ import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
+ import { inspectDirectoryIdentitySync } from "./directory-guard.js";
5
+ import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
4
6
  import { realpathSync } from "./realpath.js";
7
+ import { resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
5
8
  export function exactIdentityMatches(expected, actual) {
6
9
  return ["dev", "ino"].every((key) => {
7
10
  const value = expected[key];
@@ -14,7 +17,7 @@ export function describeStagedDirectory(fd, pathname) {
14
17
  throw new FsSafeError("not-file", "staging parent must be a directory");
15
18
  }
16
19
  const receipt = Object.freeze({
17
- path: path.resolve(pathname),
20
+ path: resolvePathPreservingWindowsRoot(pathname),
18
21
  realPath: realpathSync(pathname),
19
22
  identity: Object.freeze({ dev: identity.dev, ino: identity.ino }),
20
23
  });
@@ -27,10 +30,58 @@ export function assertStagedDirectoryCurrent(receipt) {
27
30
  realpathSync(receipt.path) !== receipt.realPath) {
28
31
  throw new FsSafeError("path-mismatch", "staging directory pathname changed");
29
32
  }
33
+ return current;
34
+ }
35
+ function sameDirectoryMetadata(left, right) {
36
+ return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode &&
37
+ left.nlink === right.nlink;
38
+ }
39
+ // The policy hot path is Node/POSIX-only. It deliberately uses native
40
+ // canonicalization so a successful capture contains no JS realpath work.
41
+ export function describePolicyStagedDirectory(fd, pathname) {
42
+ const resolved = path.resolve(pathname);
43
+ const descriptor = fs.fstatSync(fd, { bigint: true });
44
+ if (!descriptor.isDirectory()) {
45
+ throw new FsSafeError("not-file", "staging parent must be a directory");
46
+ }
47
+ const before = inspectDirectoryIdentitySync(resolved);
48
+ const canonicalPath = path.resolve(realpathSync.native(resolved));
49
+ const after = inspectDirectoryIdentitySync(resolved, descriptor);
50
+ if (canonicalPath !== resolved || !sameDirectoryMetadata(descriptor, before) ||
51
+ !sameDirectoryMetadata(descriptor, after) ||
52
+ path.resolve(realpathSync.native(resolved)) !== canonicalPath) {
53
+ throw new FsSafeError("path-mismatch", "staging directory pathname changed");
54
+ }
55
+ const directory = Object.freeze({
56
+ path: resolved,
57
+ realPath: canonicalPath,
58
+ identity: Object.freeze({ dev: descriptor.dev, ino: descriptor.ino }),
59
+ });
60
+ return Object.freeze({
61
+ directory,
62
+ observation: checkedMutationDirectory(resolved, canonicalPath, after),
63
+ stat: after,
64
+ });
65
+ }
66
+ export function assertPolicyStagedDirectoryCurrent(captured) {
67
+ const current = inspectDirectoryIdentitySync(captured.directory.path, captured.directory.identity);
68
+ if (!sameDirectoryMetadata(captured.stat, current) ||
69
+ path.resolve(realpathSync.native(captured.directory.path)) !== captured.directory.realPath) {
70
+ throw new FsSafeError("path-mismatch", "staging directory pathname changed");
71
+ }
72
+ return current;
73
+ }
74
+ export function refreshPolicyStagedDirectoryObservation(captured) {
75
+ const current = inspectDirectoryIdentitySync(captured.directory.path, captured.directory.identity);
76
+ const canonicalPath = path.resolve(realpathSync.native(captured.directory.path));
77
+ if (canonicalPath !== captured.directory.realPath) {
78
+ throw new FsSafeError("path-mismatch", "staging directory pathname changed");
79
+ }
80
+ return checkedMutationDirectory(captured.directory.path, canonicalPath, current);
30
81
  }
31
82
  export function openStagedDirectory(directory) {
32
83
  // Copy supplied facts before any asynchronous work; receipts are not authority.
33
- const pathname = path.resolve(typeof directory === "string" ? directory : directory.path);
84
+ const pathname = resolvePathPreservingWindowsRoot(typeof directory === "string" ? directory : directory.path);
34
85
  const expected = typeof directory === "string" ? undefined : {
35
86
  realPath: directory.realPath, dev: directory.identity.dev, ino: directory.identity.ino,
36
87
  };
@@ -0,0 +1,2 @@
1
+ export declare function admitStandalonePublicationPath(value: string, message?: string): string;
2
+ //# sourceMappingURL=standalone-publication-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone-publication-path.d.ts","sourceRoot":"","sources":["../src/standalone-publication-path.ts"],"names":[],"mappings":"AAKA,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAItF"}
@@ -0,0 +1,6 @@
1
+ import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias, } from "./windows-path-alias.js";
2
+ export function admitStandalonePublicationPath(value, message) {
3
+ const admittedPath = anchorWindowsDriveRelativePath(value);
4
+ assertNoWindowsPathAlias(admittedPath, "filesystem", message);
5
+ return admittedPath;
6
+ }
@@ -0,0 +1,11 @@
1
+ import type { BigIntStats, Stats } from "node:fs";
2
+ export type ExactStatIdentity = Pick<BigIntStats, "dev" | "ino">;
3
+ export type StatObservationReceipt = {
4
+ stat: Stats | BigIntStats;
5
+ identity: ExactStatIdentity;
6
+ };
7
+ /** Operation-local metadata plus exact identity; never use projected PathStat IDs. */
8
+ export declare function inspectStatObservationSync(inspect: (bigint: boolean) => Stats | BigIntStats, expected?: ExactStatIdentity, initial?: Stats | BigIntStats, platform?: NodeJS.Platform): StatObservationReceipt;
9
+ /** Revalidate an exact identity without allocating a receipt that the caller will discard. */
10
+ export declare function assertStatObservationSync(inspect: (bigint: boolean) => Stats | BigIntStats, expected: ExactStatIdentity, initial?: Stats | BigIntStats, platform?: NodeJS.Platform): Stats | BigIntStats;
11
+ //# sourceMappingURL=stat-observation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stat-observation.d.ts","sourceRoot":"","sources":["../src/stat-observation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAKlD,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AACjE,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAC;IAC1B,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAqEF,sFAAsF;AACtF,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,GAAG,WAAW,EACjD,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,OAAO,CAAC,EAAE,KAAK,GAAG,WAAW,EAC7B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,CAExB;AAED,8FAA8F;AAC9F,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,GAAG,WAAW,EACjD,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,CAAC,EAAE,KAAK,GAAG,WAAW,EAC7B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,KAAK,GAAG,WAAW,CAErB"}
@@ -0,0 +1,64 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { recordFileObservationFailure } from "./file-observation.js";
3
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
4
+ function identityMismatch() {
5
+ const error = new FsSafeError("path-mismatch", "file identity changed or could not be verified");
6
+ recordFileObservationFailure(error, "identity");
7
+ throw error;
8
+ }
9
+ function safeNumber(value) {
10
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
11
+ }
12
+ function safeExpected(identity) {
13
+ return typeof identity.dev === "bigint" && typeof identity.ino === "bigint" &&
14
+ identity.dev >= 0n && identity.ino >= 0n &&
15
+ identity.dev <= 9007199254740991n && identity.ino <= 9007199254740991n;
16
+ }
17
+ function observeStatSync(inspect, expected, initial, platform = process.platform, captureIdentity) {
18
+ if (platform === "win32" || (!initial && expected && !safeExpected(expected))) {
19
+ // Preserve the strict Windows unknown-ID retry, including known components
20
+ // and validation of the expected identity before the first observation.
21
+ const stat = inspectFileIdentitySync(() => {
22
+ const current = initial;
23
+ initial = undefined;
24
+ return (current ?? inspect(true));
25
+ }, expected, platform);
26
+ return (captureIdentity
27
+ ? { stat, identity: expected ?? { dev: stat.dev, ino: stat.ino } }
28
+ : stat);
29
+ }
30
+ const stat = initial ?? inspect(false);
31
+ const { dev: rawDev, ino: rawIno } = stat;
32
+ const numericDev = safeNumber(rawDev);
33
+ const numericIno = safeNumber(rawIno);
34
+ const dev = numericDev ? BigInt(rawDev) : typeof rawDev === "bigint" ? rawDev : undefined;
35
+ const ino = numericIno ? BigInt(rawIno) : typeof rawIno === "bigint" ? rawIno : undefined;
36
+ if (expected && ((dev !== undefined && dev !== expected.dev) ||
37
+ (ino !== undefined && ino !== expected.ino)))
38
+ identityMismatch();
39
+ if (numericDev && numericIno) {
40
+ return (captureIdentity
41
+ ? { stat, identity: expected ?? { dev: dev, ino: ino } }
42
+ : stat);
43
+ }
44
+ // An unsafe numeric component is unknown, not a rounded identity. Retain
45
+ // every exact component from this observation while obtaining the full ID.
46
+ const exact = inspectFileIdentitySync(() => {
47
+ const current = inspect(true);
48
+ if ((dev !== undefined && current.dev !== dev) ||
49
+ (ino !== undefined && current.ino !== ino))
50
+ identityMismatch();
51
+ return current;
52
+ }, expected, platform);
53
+ return (captureIdentity
54
+ ? { stat: exact, identity: expected ?? { dev: exact.dev, ino: exact.ino } }
55
+ : exact);
56
+ }
57
+ /** Operation-local metadata plus exact identity; never use projected PathStat IDs. */
58
+ export function inspectStatObservationSync(inspect, expected, initial, platform = process.platform) {
59
+ return observeStatSync(inspect, expected, initial, platform, true);
60
+ }
61
+ /** Revalidate an exact identity without allocating a receipt that the caller will discard. */
62
+ export function assertStatObservationSync(inspect, expected, initial, platform = process.platform) {
63
+ return observeStatSync(inspect, expected, initial, platform, false);
64
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA+B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC7B,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,iBAAiB,EACjE,OAAO,EAAE,MAAM,CAAC,EAChB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,CAAC,CAOH"}
1
+ {"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA+B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC7B,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,iBAAiB,EACjE,OAAO,EAAE,MAAM,CAAC,EAChB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,CAAC,CAmCH"}
@@ -41,10 +41,47 @@ export async function inspectFileIdentity(inspect, expected, platform = process.
41
41
  throw identityMismatch();
42
42
  }
43
43
  export function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
44
- const check = identityCheck(expected, platform);
44
+ let knownDev;
45
+ let knownIno;
46
+ if (expected) {
47
+ knownDev = expected.dev;
48
+ if (typeof knownDev !== "bigint")
49
+ throw identityMismatch();
50
+ knownIno = expected.ino;
51
+ if (typeof knownIno !== "bigint")
52
+ throw identityMismatch();
53
+ // An unknown expected device must not bypass observation of its inode.
54
+ if (platform === "win32" && (knownDev === 0n || knownIno === 0n))
55
+ throw identityMismatch();
56
+ }
45
57
  for (let attempt = 0; attempt < 2; attempt++) {
46
58
  const stat = inspect();
47
- if (check(stat))
59
+ let complete = true;
60
+ // Keep dev-before-ino access and early mismatch errors without allocating
61
+ // a checker closure, known-component object, or field array per inspection.
62
+ const dev = stat.dev;
63
+ if (typeof dev !== "bigint")
64
+ throw identityMismatch();
65
+ if (platform === "win32" && dev === 0n) {
66
+ complete = false;
67
+ }
68
+ else {
69
+ if (knownDev !== undefined && knownDev !== dev)
70
+ throw identityMismatch();
71
+ knownDev = dev;
72
+ }
73
+ const ino = stat.ino;
74
+ if (typeof ino !== "bigint")
75
+ throw identityMismatch();
76
+ if (platform === "win32" && ino === 0n) {
77
+ complete = false;
78
+ }
79
+ else {
80
+ if (knownIno !== undefined && knownIno !== ino)
81
+ throw identityMismatch();
82
+ knownIno = ino;
83
+ }
84
+ if (complete)
48
85
  return stat;
49
86
  }
50
87
  throw identityMismatch();
@@ -1 +1 @@
1
- {"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAyBF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,6BAA6B,GACpC,IAAI,CA6BN"}
1
+ {"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAqCF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,6BAA6B,GACpC,IAAI,CA6BN"}
@@ -2,9 +2,14 @@ import fsSync from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { hasNodeErrorCode, isPathRelativeEscape } from "./path.js";
5
+ import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
5
6
  function resolvePathWalk(params) {
6
- const root = path.resolve(params.rootDir);
7
- const target = path.resolve(params.targetPath);
7
+ const rawRootDir = params.rootDir;
8
+ assertNoWindowsPathAlias(rawRootDir, "filesystem", "root dir uses a Windows filesystem namespace alias");
9
+ const rawTargetPath = params.targetPath;
10
+ assertNoWindowsPathAlias(rawTargetPath, "filesystem", "target path uses a Windows filesystem namespace alias");
11
+ const root = resolvePathPreservingWindowsRoot(rawRootDir);
12
+ const target = resolvePathPreservingWindowsRoot(rawTargetPath);
8
13
  const relative = path.relative(root, target);
9
14
  if (isPathRelativeEscape(relative)) {
10
15
  if (params.allowOutsideRoot) {
@@ -1,4 +1,5 @@
1
1
  import fsSync from "node:fs";
2
+ import type { TempWorkspaceCleanupSafety } from "./temp-workspace-owner.js";
2
3
  export type TempFile = {
3
4
  dir: string;
4
5
  path: string;
@@ -11,6 +12,7 @@ type TempFileOptions = {
11
12
  prefix: string;
12
13
  fileName?: string;
13
14
  onCleanupError?: (error: unknown) => void;
15
+ cleanupSafety?: TempWorkspaceCleanupSafety;
14
16
  };
15
17
  export declare function sanitizeTempFileName(fileName: string): string;
16
18
  export declare function buildRandomTempFilePath(params: {
@@ -1 +1 @@
1
- {"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAS7B,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,CAAC;AA8DF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAaT;AAyCD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAC1E,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CAC7D,CAAC,CA2BD;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEzE;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE,eAAe,EACvB,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
1
+ {"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAa7B,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,aAAa,CAAC,EAAE,0BAA0B,CAAC;CAC5C,CAAC;AA8DF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAeT;AAwDD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAC1E,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CAC7D,CAAC,CAmID;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEzE;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE,eAAe,EACvB,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -7,6 +7,7 @@ import { sameFileIdentityForCleanup } from "./file-identity.js";
7
7
  import { assertSafePathSegment, sanitizeSafePathSegment, trimHyphenEdges } from "./safe-path-segment.js";
8
8
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
9
9
  import { registerTempPathForExit } from "./temp-cleanup.js";
10
+ import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
10
11
  const HYPHEN_CHAR_CODE = 0x2d;
11
12
  const DOT_CHAR_CODE = 0x2e;
12
13
  const NUMBER_ZERO_CHAR_CODE = 0x30;
@@ -74,7 +75,9 @@ export function buildRandomTempFilePath(params) {
74
75
  const uuid = params.uuid
75
76
  ? assertSafePathSegment(params.uuid.trim(), { label: "temp uuid" })
76
77
  : crypto.randomUUID();
77
- return path.join(rootDir, `${prefix}-${now}-${uuid}${extension}`);
78
+ const filePath = path.join(rootDir, `${prefix}-${now}-${uuid}${extension}`);
79
+ assertNoWindowsPathAlias(filePath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
80
+ return filePath;
78
81
  }
79
82
  function isNodeErrorWithCode(err, code) {
80
83
  return (typeof err === "object" &&
@@ -106,17 +109,141 @@ async function cleanupTempDir(dir, identity, onCleanupError) {
106
109
  }
107
110
  }
108
111
  function resolveTempRoot(rootDir) {
109
- return path.resolve(rootDir ?? resolveSecureTempRoot({ fallbackPrefix: "fs-safe" }));
112
+ if (rootDir !== undefined) {
113
+ assertNoWindowsPathAlias(rootDir, "filesystem", "temp root uses a Windows filesystem namespace alias");
114
+ }
115
+ const selectedRoot = rootDir ?? resolveSecureTempRoot({ fallbackPrefix: "fs-safe" });
116
+ assertNoWindowsPathAlias(selectedRoot, "filesystem", "temp root uses a Windows filesystem namespace alias");
117
+ const resolvedRoot = resolvePathPreservingWindowsRoot(selectedRoot);
118
+ assertNoWindowsPathAlias(resolvedRoot, "filesystem", "temp root uses a Windows filesystem namespace alias");
119
+ return resolvedRoot;
120
+ }
121
+ function resolveTempFileCleanupSafety(value) {
122
+ if (value === undefined || value === "compatible")
123
+ return "compatible";
124
+ if (value === "require-bounded")
125
+ return value;
126
+ throw new TypeError("cleanupSafety must be compatible or require-bounded");
110
127
  }
111
128
  export async function createOwnedTempFile(params) {
129
+ const cleanupSafety = resolveTempFileCleanupSafety(params.cleanupSafety);
112
130
  const rootDir = resolveTempRoot(params.rootDir);
113
131
  const prefix = `${sanitizePrefix(params.prefix)}-`;
132
+ if (cleanupSafety === "require-bounded") {
133
+ const initialFileName = sanitizeTempFileName(params.fileName ?? "download.bin");
134
+ const [{ TempWorkspaceCleanupCapability, TempWorkspaceCleanupOwner }, { admitExistingTempWorkspaceRoot }, { TempWorkspaceRetainedChild }, { validateInitialTempWorkspaceChild, admitRetainedTempWorkspaceChild }, { inspectDirectoryIdentitySync },] = await Promise.all([
135
+ import("./temp-workspace-owner.js"),
136
+ import("./temp-workspace-admission.js"),
137
+ import("./temp-workspace-descriptor.js"),
138
+ import("./temp-workspace-child-admission.js"),
139
+ import("./directory-guard.js"),
140
+ ]);
141
+ const admission = admitExistingTempWorkspaceRoot(rootDir);
142
+ const childPrefix = path.join(admission.dir, prefix);
143
+ assertNoWindowsPathAlias(childPrefix, "filesystem", "temp directory uses a Windows filesystem namespace alias");
144
+ const capability = new TempWorkspaceCleanupCapability(admission.dir, cleanupSafety, admission, 0o700);
145
+ let dir;
146
+ let initialPath;
147
+ let identity;
148
+ let retainedChild;
149
+ let cleanupOwner;
150
+ let unregisterTempDir;
151
+ try {
152
+ capability.prepareChildCreation();
153
+ dir = await fs.mkdtemp(childPrefix);
154
+ assertNoWindowsPathAlias(dir, "filesystem", "temp directory uses a Windows filesystem namespace alias");
155
+ initialPath = path.join(dir, initialFileName);
156
+ assertNoWindowsPathAlias(initialPath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
157
+ capability.assertCurrent();
158
+ const initial = inspectDirectoryIdentitySync(dir);
159
+ identity = Object.freeze({ dev: initial.dev, ino: initial.ino });
160
+ const needsModeInitialization = validateInitialTempWorkspaceChild(initial, admission.ownerUid, 0o700);
161
+ retainedChild = TempWorkspaceRetainedChild.retain(dir, identity);
162
+ const modeInitialization = admitRetainedTempWorkspaceChild(retainedChild, needsModeInitialization, admission, 0o700);
163
+ if (modeInitialization)
164
+ await modeInitialization;
165
+ const retainDescriptor = capability.admitChildDescriptor(retainedChild.ensureReadable());
166
+ capability.assertAncestryCurrent();
167
+ retainedChild.finalizeAdmission(admission.ownerUid, 0o700);
168
+ cleanupOwner = new TempWorkspaceCleanupOwner(retainedChild, capability, retainDescriptor);
169
+ retainedChild = undefined;
170
+ unregisterTempDir = registerTempPathForExit(dir, {
171
+ cleanupSync: () => cleanupOwner.cleanupSync(),
172
+ });
173
+ }
174
+ catch (error) {
175
+ try {
176
+ if (cleanupOwner)
177
+ cleanupOwner.cleanupSync();
178
+ else {
179
+ const closeErrors = [];
180
+ try {
181
+ retainedChild?.close();
182
+ }
183
+ catch (closeError) {
184
+ closeErrors.push(closeError);
185
+ }
186
+ try {
187
+ capability.close();
188
+ }
189
+ catch (closeError) {
190
+ closeErrors.push(closeError);
191
+ }
192
+ if (closeErrors.length === 1)
193
+ throw closeErrors[0];
194
+ if (closeErrors.length > 1) {
195
+ throw new AggregateError(closeErrors, "temp file admission descriptor close failed");
196
+ }
197
+ }
198
+ }
199
+ catch (cleanupError) {
200
+ throw new AggregateError([error, cleanupError], "temp file creation and cleanup both failed");
201
+ }
202
+ throw error;
203
+ }
204
+ const owner = cleanupOwner;
205
+ const file = (fileName) => {
206
+ const filePath = path.join(dir, sanitizeTempFileName(fileName ?? params.fileName ?? "download.bin"));
207
+ assertNoWindowsPathAlias(filePath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
208
+ return filePath;
209
+ };
210
+ const cleanup = async () => {
211
+ try {
212
+ try {
213
+ await owner.cleanup();
214
+ }
215
+ catch (err) {
216
+ if (!isNodeErrorWithCode(err, "ENOENT")) {
217
+ params.onCleanupError?.(err);
218
+ }
219
+ }
220
+ }
221
+ finally {
222
+ unregisterTempDir();
223
+ }
224
+ };
225
+ return {
226
+ target: {
227
+ dir,
228
+ path: initialPath,
229
+ file,
230
+ cleanup,
231
+ [Symbol.asyncDispose]: cleanup,
232
+ },
233
+ identity,
234
+ };
235
+ }
114
236
  const dir = await fs.mkdtemp(path.join(rootDir, prefix));
237
+ assertNoWindowsPathAlias(dir, "filesystem", "temp directory uses a Windows filesystem namespace alias");
115
238
  // Windows file indexes can exceed Number.MAX_SAFE_INTEGER. Cleanup receipts
116
239
  // must retain the exact identity or adjacent directories can compare equal.
117
240
  const identity = fsSync.lstatSync(dir, { bigint: true });
118
241
  const unregisterTempDir = registerTempPathForExit(dir, { recursive: true, identity });
119
- const file = (fileName) => path.join(dir, sanitizeTempFileName(fileName ?? params.fileName ?? "download.bin"));
242
+ const file = (fileName) => {
243
+ const filePath = path.join(dir, sanitizeTempFileName(fileName ?? params.fileName ?? "download.bin"));
244
+ assertNoWindowsPathAlias(filePath, "filesystem", "temp file path uses a Windows filesystem namespace alias");
245
+ return filePath;
246
+ };
120
247
  const cleanup = async () => {
121
248
  try {
122
249
  await cleanupTempDir(dir, identity, params.onCleanupError);