@openclaw/fs-safe 0.12.0 → 0.13.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 (383) hide show
  1. package/CHANGELOG.md +61 -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/darwin-acl.d.ts +4 -0
  41. package/dist/darwin-acl.d.ts.map +1 -0
  42. package/dist/darwin-acl.js +24 -0
  43. package/dist/deny-mutations.d.ts.map +1 -1
  44. package/dist/deny-mutations.js +8 -2
  45. package/dist/directory-durability.d.ts.map +1 -1
  46. package/dist/directory-durability.js +67 -23
  47. package/dist/directory-entry-path.d.ts +3 -0
  48. package/dist/directory-entry-path.d.ts.map +1 -0
  49. package/dist/directory-entry-path.js +21 -0
  50. package/dist/directory-guard.d.ts +17 -1
  51. package/dist/directory-guard.d.ts.map +1 -1
  52. package/dist/directory-guard.js +134 -48
  53. package/dist/directory-mode-node.d.ts +12 -0
  54. package/dist/directory-mode-node.d.ts.map +1 -1
  55. package/dist/directory-mode-node.js +102 -4
  56. package/dist/effective-uid.d.ts +2 -0
  57. package/dist/effective-uid.d.ts.map +1 -0
  58. package/dist/effective-uid.js +25 -0
  59. package/dist/file-handle-transfer.d.ts.map +1 -1
  60. package/dist/file-handle-transfer.js +98 -27
  61. package/dist/file-hash.d.ts.map +1 -1
  62. package/dist/file-hash.js +3 -0
  63. package/dist/file-lock-sync.d.ts.map +1 -1
  64. package/dist/file-lock-sync.js +36 -6
  65. package/dist/file-lock.d.ts.map +1 -1
  66. package/dist/file-lock.js +29 -9
  67. package/dist/file-observation.d.ts +1 -1
  68. package/dist/file-observation.d.ts.map +1 -1
  69. package/dist/file-store-boundary.d.ts +6 -2
  70. package/dist/file-store-boundary.d.ts.map +1 -1
  71. package/dist/file-store-boundary.js +20 -65
  72. package/dist/file-store-copy-source.d.ts +5 -0
  73. package/dist/file-store-copy-source.d.ts.map +1 -0
  74. package/dist/file-store-copy-source.js +31 -0
  75. package/dist/file-store-path.d.ts.map +1 -1
  76. package/dist/file-store-path.js +4 -1
  77. package/dist/file-store-sync-directory.d.ts +16 -0
  78. package/dist/file-store-sync-directory.d.ts.map +1 -0
  79. package/dist/file-store-sync-directory.js +349 -0
  80. package/dist/file-store.d.ts.map +1 -1
  81. package/dist/file-store.js +11 -28
  82. package/dist/filename.d.ts.map +1 -1
  83. package/dist/filename.js +65 -20
  84. package/dist/fs.d.ts.map +1 -1
  85. package/dist/fs.js +3 -2
  86. package/dist/guarded-mkdir.d.ts +8 -0
  87. package/dist/guarded-mkdir.d.ts.map +1 -1
  88. package/dist/guarded-mkdir.js +176 -27
  89. package/dist/guest.d.ts.map +1 -1
  90. package/dist/guest.js +4 -1
  91. package/dist/home-dir.d.ts.map +1 -1
  92. package/dist/home-dir.js +73 -10
  93. package/dist/install-path.d.ts.map +1 -1
  94. package/dist/install-path.js +54 -19
  95. package/dist/json-durable-queue-ownership.d.ts +6 -0
  96. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  97. package/dist/json-durable-queue-ownership.js +89 -42
  98. package/dist/json-durable-queue-paths.d.ts +11 -0
  99. package/dist/json-durable-queue-paths.d.ts.map +1 -0
  100. package/dist/json-durable-queue-paths.js +42 -0
  101. package/dist/json-durable-queue-read.d.ts.map +1 -1
  102. package/dist/json-durable-queue-read.js +2 -0
  103. package/dist/json-durable-queue.d.ts.map +1 -1
  104. package/dist/json-durable-queue.js +50 -58
  105. package/dist/json-store.d.ts.map +1 -1
  106. package/dist/json-store.js +5 -1
  107. package/dist/json.d.ts.map +1 -1
  108. package/dist/json.js +54 -22
  109. package/dist/local-file-access.d.ts.map +1 -1
  110. package/dist/local-file-access.js +4 -0
  111. package/dist/local-file-descriptor.d.ts +17 -0
  112. package/dist/local-file-descriptor.d.ts.map +1 -0
  113. package/dist/local-file-descriptor.js +84 -0
  114. package/dist/local-roots.d.ts.map +1 -1
  115. package/dist/local-roots.js +35 -7
  116. package/dist/move-path-cleanup.d.ts +2 -0
  117. package/dist/move-path-cleanup.d.ts.map +1 -1
  118. package/dist/move-path-cleanup.js +44 -18
  119. package/dist/move-path.d.ts.map +1 -1
  120. package/dist/move-path.js +31 -6
  121. package/dist/native-binding.d.ts +14 -0
  122. package/dist/native-binding.d.ts.map +1 -1
  123. package/dist/native-directory-observation.d.ts +17 -0
  124. package/dist/native-directory-observation.d.ts.map +1 -0
  125. package/dist/native-directory-observation.js +37 -0
  126. package/dist/native-parent-admission.d.ts +31 -0
  127. package/dist/native-parent-admission.d.ts.map +1 -0
  128. package/dist/native-parent-admission.js +124 -0
  129. package/dist/native-pinned-write-windows.d.ts +0 -1
  130. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  131. package/dist/native-pinned-write-windows.js +0 -3
  132. package/dist/native-pinned-write.d.ts.map +1 -1
  133. package/dist/native-pinned-write.js +309 -40
  134. package/dist/output.d.ts.map +1 -1
  135. package/dist/output.js +15 -5
  136. package/dist/overwrite-file-handle.d.ts.map +1 -1
  137. package/dist/overwrite-file-handle.js +5 -1
  138. package/dist/owner-dacl.d.ts.map +1 -1
  139. package/dist/owner-dacl.js +2 -0
  140. package/dist/path-policy.d.ts.map +1 -1
  141. package/dist/path-policy.js +7 -2
  142. package/dist/path-prefix.d.ts +7 -0
  143. package/dist/path-prefix.d.ts.map +1 -0
  144. package/dist/path-prefix.js +82 -0
  145. package/dist/path-scope-lexical.d.ts.map +1 -1
  146. package/dist/path-scope-lexical.js +18 -8
  147. package/dist/path-segment-route.d.ts +7 -0
  148. package/dist/path-segment-route.d.ts.map +1 -0
  149. package/dist/path-segment-route.js +24 -0
  150. package/dist/path-suffix-aliases.d.ts +10 -0
  151. package/dist/path-suffix-aliases.d.ts.map +1 -0
  152. package/dist/path-suffix-aliases.js +386 -0
  153. package/dist/path.d.ts.map +1 -1
  154. package/dist/path.js +16 -5
  155. package/dist/permissions-windows.d.ts.map +1 -1
  156. package/dist/permissions-windows.js +14 -3
  157. package/dist/permissions.d.ts.map +1 -1
  158. package/dist/permissions.js +37 -8
  159. package/dist/pinned-mutation-admission.d.ts +25 -0
  160. package/dist/pinned-mutation-admission.d.ts.map +1 -0
  161. package/dist/pinned-mutation-admission.js +425 -0
  162. package/dist/pinned-mutation-observation.d.ts +34 -0
  163. package/dist/pinned-mutation-observation.d.ts.map +1 -0
  164. package/dist/pinned-mutation-observation.js +142 -0
  165. package/dist/pinned-mutation-shared-route.d.ts +24 -0
  166. package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
  167. package/dist/pinned-mutation-shared-route.js +70 -0
  168. package/dist/pinned-open.d.ts +6 -0
  169. package/dist/pinned-open.d.ts.map +1 -1
  170. package/dist/pinned-open.js +28 -10
  171. package/dist/pinned-write-types.d.ts +75 -0
  172. package/dist/pinned-write-types.d.ts.map +1 -0
  173. package/dist/pinned-write-types.js +1 -0
  174. package/dist/pinned-write.d.ts +7 -33
  175. package/dist/pinned-write.d.ts.map +1 -1
  176. package/dist/pinned-write.js +151 -17
  177. package/dist/private-directory.d.ts.map +1 -1
  178. package/dist/private-directory.js +2 -0
  179. package/dist/private-producer-handoff.d.ts +16 -0
  180. package/dist/private-producer-handoff.d.ts.map +1 -0
  181. package/dist/private-producer-handoff.js +272 -0
  182. package/dist/private-temp-workspace.d.ts +2 -39
  183. package/dist/private-temp-workspace.d.ts.map +1 -1
  184. package/dist/private-temp-workspace.js +183 -77
  185. package/dist/publish-file.d.ts.map +1 -1
  186. package/dist/publish-file.js +33 -29
  187. package/dist/regular-file.d.ts.map +1 -1
  188. package/dist/regular-file.js +56 -45
  189. package/dist/replace-directory.d.ts.map +1 -1
  190. package/dist/replace-directory.js +12 -5
  191. package/dist/replace-file-temp-owner.d.ts +1 -0
  192. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  193. package/dist/replace-file-temp-owner.js +12 -1
  194. package/dist/replace-file.d.ts.map +1 -1
  195. package/dist/replace-file.js +24 -24
  196. package/dist/root-boundary.d.ts +4 -0
  197. package/dist/root-boundary.d.ts.map +1 -1
  198. package/dist/root-boundary.js +6 -1
  199. package/dist/root-context.d.ts +12 -1
  200. package/dist/root-context.d.ts.map +1 -1
  201. package/dist/root-context.js +57 -11
  202. package/dist/root-directory-creation.d.ts +13 -0
  203. package/dist/root-directory-creation.d.ts.map +1 -0
  204. package/dist/root-directory-creation.js +212 -0
  205. package/dist/root-directory-list.d.ts +16 -4
  206. package/dist/root-directory-list.d.ts.map +1 -1
  207. package/dist/root-directory-list.js +180 -39
  208. package/dist/root-directory.d.ts +23 -0
  209. package/dist/root-directory.d.ts.map +1 -0
  210. package/dist/root-directory.js +141 -0
  211. package/dist/root-errors.d.ts.map +1 -1
  212. package/dist/root-errors.js +12 -1
  213. package/dist/root-file-final-admission.d.ts +21 -0
  214. package/dist/root-file-final-admission.d.ts.map +1 -0
  215. package/dist/root-file-final-admission.js +83 -0
  216. package/dist/root-file.d.ts.map +1 -1
  217. package/dist/root-file.js +103 -24
  218. package/dist/root-impl.d.ts +1 -0
  219. package/dist/root-impl.d.ts.map +1 -1
  220. package/dist/root-impl.js +289 -317
  221. package/dist/root-move-noreplace.d.ts +14 -0
  222. package/dist/root-move-noreplace.d.ts.map +1 -0
  223. package/dist/root-move-noreplace.js +202 -0
  224. package/dist/root-observed-path.d.ts +9 -0
  225. package/dist/root-observed-path.d.ts.map +1 -0
  226. package/dist/root-observed-path.js +95 -0
  227. package/dist/root-path-errors.d.ts +12 -0
  228. package/dist/root-path-errors.d.ts.map +1 -0
  229. package/dist/root-path-errors.js +13 -0
  230. package/dist/root-path-existing.d.ts.map +1 -1
  231. package/dist/root-path-existing.js +53 -20
  232. package/dist/root-path-observation.d.ts +63 -0
  233. package/dist/root-path-observation.d.ts.map +1 -0
  234. package/dist/root-path-observation.js +180 -0
  235. package/dist/root-path-stat.d.ts +5 -0
  236. package/dist/root-path-stat.d.ts.map +1 -0
  237. package/dist/root-path-stat.js +101 -0
  238. package/dist/root-path-symlink.d.ts.map +1 -1
  239. package/dist/root-path-symlink.js +23 -4
  240. package/dist/root-path.d.ts +11 -0
  241. package/dist/root-path.d.ts.map +1 -1
  242. package/dist/root-path.js +215 -53
  243. package/dist/root-paths-lexical.d.ts +9 -0
  244. package/dist/root-paths-lexical.d.ts.map +1 -0
  245. package/dist/root-paths-lexical.js +22 -0
  246. package/dist/root-paths.d.ts +3 -25
  247. package/dist/root-paths.d.ts.map +1 -1
  248. package/dist/root-paths.js +77 -154
  249. package/dist/root-read-admission.d.ts +26 -0
  250. package/dist/root-read-admission.d.ts.map +1 -0
  251. package/dist/root-read-admission.js +94 -0
  252. package/dist/root-remove-identity.d.ts +15 -0
  253. package/dist/root-remove-identity.d.ts.map +1 -0
  254. package/dist/root-remove-identity.js +89 -0
  255. package/dist/root-remove-receipt.d.ts +17 -0
  256. package/dist/root-remove-receipt.d.ts.map +1 -0
  257. package/dist/root-remove-receipt.js +37 -0
  258. package/dist/root-remove.d.ts +2 -1
  259. package/dist/root-remove.d.ts.map +1 -1
  260. package/dist/root-remove.js +172 -10
  261. package/dist/root-write-admission.d.ts +68 -0
  262. package/dist/root-write-admission.d.ts.map +1 -0
  263. package/dist/root-write-admission.js +322 -0
  264. package/dist/root-write-compatibility.d.ts +13 -0
  265. package/dist/root-write-compatibility.d.ts.map +1 -0
  266. package/dist/root-write-compatibility.js +74 -0
  267. package/dist/root-write-complete-parent.d.ts +42 -0
  268. package/dist/root-write-complete-parent.d.ts.map +1 -0
  269. package/dist/root-write-complete-parent.js +195 -0
  270. package/dist/root-write-publication.d.ts +24 -0
  271. package/dist/root-write-publication.d.ts.map +1 -0
  272. package/dist/root-write-publication.js +85 -0
  273. package/dist/root-write-verification.d.ts.map +1 -1
  274. package/dist/root-write-verification.js +6 -4
  275. package/dist/secret-file.d.ts.map +1 -1
  276. package/dist/secret-file.js +62 -24
  277. package/dist/secret-read-async.d.ts.map +1 -1
  278. package/dist/secret-read-async.js +18 -13
  279. package/dist/secret-read-policy.d.ts.map +1 -1
  280. package/dist/secret-read-policy.js +5 -1
  281. package/dist/secure-file.d.ts.map +1 -1
  282. package/dist/secure-file.js +92 -7
  283. package/dist/secure-temp-dir.d.ts +6 -3
  284. package/dist/secure-temp-dir.d.ts.map +1 -1
  285. package/dist/secure-temp-dir.js +121 -101
  286. package/dist/secure-temp-repair.d.ts +35 -0
  287. package/dist/secure-temp-repair.d.ts.map +1 -0
  288. package/dist/secure-temp-repair.js +106 -0
  289. package/dist/sibling-staged-file.d.ts +3 -2
  290. package/dist/sibling-staged-file.d.ts.map +1 -1
  291. package/dist/sibling-staged-file.js +179 -55
  292. package/dist/sibling-temp.d.ts.map +1 -1
  293. package/dist/sibling-temp.js +27 -13
  294. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  295. package/dist/sidecar-lock-acquire.js +44 -14
  296. package/dist/sidecar-lock-root.d.ts.map +1 -1
  297. package/dist/sidecar-lock-root.js +4 -2
  298. package/dist/staged-directory.d.ts +14 -5
  299. package/dist/staged-directory.d.ts.map +1 -1
  300. package/dist/staged-directory.js +54 -3
  301. package/dist/standalone-publication-path.d.ts +2 -0
  302. package/dist/standalone-publication-path.d.ts.map +1 -0
  303. package/dist/standalone-publication-path.js +6 -0
  304. package/dist/stat-observation.d.ts +11 -0
  305. package/dist/stat-observation.d.ts.map +1 -0
  306. package/dist/stat-observation.js +64 -0
  307. package/dist/strict-file-identity.d.ts.map +1 -1
  308. package/dist/strict-file-identity.js +39 -2
  309. package/dist/symlink-parents.d.ts.map +1 -1
  310. package/dist/symlink-parents.js +7 -2
  311. package/dist/temp-target.d.ts +2 -0
  312. package/dist/temp-target.d.ts.map +1 -1
  313. package/dist/temp-target.js +130 -3
  314. package/dist/temp-workspace-admission.d.ts +22 -0
  315. package/dist/temp-workspace-admission.d.ts.map +1 -0
  316. package/dist/temp-workspace-admission.js +374 -0
  317. package/dist/temp-workspace-child-admission.d.ts +13 -0
  318. package/dist/temp-workspace-child-admission.d.ts.map +1 -0
  319. package/dist/temp-workspace-child-admission.js +95 -0
  320. package/dist/temp-workspace-descriptor.d.ts +48 -0
  321. package/dist/temp-workspace-descriptor.d.ts.map +1 -0
  322. package/dist/temp-workspace-descriptor.js +363 -0
  323. package/dist/temp-workspace-identity.d.ts +15 -0
  324. package/dist/temp-workspace-identity.d.ts.map +1 -0
  325. package/dist/temp-workspace-identity.js +41 -0
  326. package/dist/temp-workspace-owner.d.ts +7 -6
  327. package/dist/temp-workspace-owner.d.ts.map +1 -1
  328. package/dist/temp-workspace-owner.js +121 -61
  329. package/dist/temp-workspace-permissions.d.ts +4 -0
  330. package/dist/temp-workspace-permissions.d.ts.map +1 -0
  331. package/dist/temp-workspace-permissions.js +32 -0
  332. package/dist/temp-workspace-types.d.ts +40 -0
  333. package/dist/temp-workspace-types.d.ts.map +1 -0
  334. package/dist/temp-workspace-types.js +1 -0
  335. package/dist/temp.d.ts +1 -1
  336. package/dist/temp.d.ts.map +1 -1
  337. package/dist/temp.js +1 -1
  338. package/dist/test-hooks.d.ts +7 -0
  339. package/dist/test-hooks.d.ts.map +1 -1
  340. package/dist/text-atomic.d.ts.map +1 -1
  341. package/dist/text-atomic.js +3 -1
  342. package/dist/trash.d.ts.map +1 -1
  343. package/dist/trash.js +54 -8
  344. package/dist/walk.d.ts.map +1 -1
  345. package/dist/walk.js +9 -6
  346. package/dist/windows-owner.d.ts.map +1 -1
  347. package/dist/windows-owner.js +5 -0
  348. package/dist/windows-path-alias.d.ts +39 -0
  349. package/dist/windows-path-alias.d.ts.map +1 -0
  350. package/dist/windows-path-alias.js +153 -0
  351. package/docs/advanced.md +25 -0
  352. package/docs/archive.md +23 -2
  353. package/docs/atomic.md +27 -3
  354. package/docs/copy.md +3 -0
  355. package/docs/durability.md +14 -0
  356. package/docs/errors.md +14 -6
  357. package/docs/file-store.md +24 -3
  358. package/docs/filename.md +14 -7
  359. package/docs/install-path.md +2 -2
  360. package/docs/install.md +8 -7
  361. package/docs/json-store.md +5 -1
  362. package/docs/json.md +11 -0
  363. package/docs/mutation-policy-proof.md +65 -0
  364. package/docs/native-helper.md +26 -9
  365. package/docs/native.md +32 -8
  366. package/docs/output.md +33 -11
  367. package/docs/path-prefix.md +64 -0
  368. package/docs/path-suffix-aliases.md +159 -0
  369. package/docs/path.md +11 -0
  370. package/docs/private-file-store.md +14 -0
  371. package/docs/public-api.md +9 -0
  372. package/docs/quickstart.md +1 -1
  373. package/docs/reading.md +8 -1
  374. package/docs/root.md +18 -3
  375. package/docs/secret-file.md +3 -0
  376. package/docs/secure-file.md +6 -2
  377. package/docs/security-model.md +75 -8
  378. package/docs/sidecar-lock.md +28 -1
  379. package/docs/store.md +5 -1
  380. package/docs/temp.md +260 -36
  381. package/docs/test-hooks.md +14 -0
  382. package/docs/writing.md +38 -8
  383. package/package.json +10 -10
@@ -14,6 +14,7 @@ import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
14
14
  import { expandRelativePathWithHome } from "./root-context.js";
15
15
  import { resolveRootPath } from "./root-path.js";
16
16
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
17
+ import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
17
18
  import { realpathSync } from "./realpath.js";
18
19
  const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
19
20
  const ARCHIVE_STAGING_MODE = 0o700;
@@ -25,8 +26,11 @@ function symlinkTraversalError(originalPath) {
25
26
  return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${formatErrorDetail(originalPath)}`);
26
27
  }
27
28
  export async function createDirectoryIdentityGuard(dir) {
29
+ assertNoWindowsPathAlias(dir);
28
30
  try {
29
- return await createAsyncDirectoryGuard(dir, { bigint: true });
31
+ const guard = await createAsyncDirectoryGuard(dir, { bigint: true });
32
+ assertNoWindowsPathAlias(guard.realPath);
33
+ return guard;
30
34
  }
31
35
  catch (err) {
32
36
  if (err instanceof FsSafeError && err.code === "not-file") {
@@ -47,6 +51,7 @@ export async function assertDirectoryIdentityGuard(guard) {
47
51
  }
48
52
  }
49
53
  export async function prepareArchiveDestinationGuard(destDir) {
54
+ assertNoWindowsPathAlias(destDir);
50
55
  let stat;
51
56
  try {
52
57
  stat = inspectFileIdentitySync(() => {
@@ -69,6 +74,7 @@ export async function prepareArchiveDestinationGuard(destDir) {
69
74
  throw err;
70
75
  }
71
76
  const realPath = realpathSync.native(destDir);
77
+ assertNoWindowsPathAlias(realPath);
72
78
  const guard = { dir: destDir, realPath, stat };
73
79
  try {
74
80
  inspectFileIdentitySync(() => fsSync.statSync(realPath, { bigint: true }), stat);
@@ -106,9 +112,14 @@ async function assertNoSymlinkTraversal(params) {
106
112
  }
107
113
  }
108
114
  export async function assertResolvedInsideDestination(params) {
115
+ const destinationRealDir = params.destinationRealDir;
116
+ const targetPath = params.targetPath;
117
+ const originalPath = params.originalPath;
118
+ assertNoWindowsPathAlias(destinationRealDir);
119
+ assertNoWindowsPathAlias(targetPath);
109
120
  let resolved;
110
121
  try {
111
- resolved = realpathSync.native(params.targetPath);
122
+ resolved = realpathSync.native(targetPath);
112
123
  }
113
124
  catch (err) {
114
125
  if (isNotFoundPathError(err)) {
@@ -116,8 +127,9 @@ export async function assertResolvedInsideDestination(params) {
116
127
  }
117
128
  throw err;
118
129
  }
119
- if (!isPathInside(params.destinationRealDir, resolved)) {
120
- throw symlinkTraversalError(params.originalPath);
130
+ assertNoWindowsPathAlias(resolved);
131
+ if (!isPathInside(destinationRealDir, resolved)) {
132
+ throw symlinkTraversalError(originalPath);
121
133
  }
122
134
  }
123
135
  async function mkdirArchiveOutput(params) {
@@ -131,14 +143,28 @@ async function mkdirArchiveOutput(params) {
131
143
  throw error;
132
144
  }
133
145
  }
146
+ function ownArchiveOutputPathParams(params) {
147
+ const deadline = params.deadline;
148
+ checkExtractionDeadline(deadline);
149
+ const destinationDir = params.destinationDir;
150
+ const destinationRealDir = params.destinationRealDir;
151
+ const relPath = params.relPath;
152
+ const outPath = params.outPath;
153
+ const originalPath = params.originalPath;
154
+ const isDirectory = params.isDirectory;
155
+ assertNoWindowsPathAlias(destinationDir);
156
+ assertNoWindowsPathAlias(destinationRealDir);
157
+ assertNoWindowsPathAlias(relPath, "relative");
158
+ assertNoWindowsPathAlias(outPath);
159
+ return { destinationDir, destinationRealDir, relPath, outPath, originalPath, isDirectory, deadline };
160
+ }
134
161
  export async function prepareArchiveOutputPath(params) {
135
- await prepareOutputPath(params);
162
+ await prepareOutputPath(ownArchiveOutputPathParams(params));
136
163
  }
137
164
  export async function preparePrivateArchiveOutputPath(params, assertGuards, destinationGuard) {
138
- await prepareOutputPath(params, assertGuards, true, destinationGuard);
165
+ await prepareOutputPath(ownArchiveOutputPathParams(params), assertGuards, true, destinationGuard);
139
166
  }
140
167
  async function prepareOutputPath(params, assertGuards, privateWorkingMode = false, existingDestinationGuard) {
141
- checkExtractionDeadline(params.deadline);
142
168
  const targetRoot = privateWorkingMode ? {
143
169
  async mkdir(relativePath) {
144
170
  // Retain Root.mkdir's strict alias admission before the shared traversal,
@@ -231,6 +257,7 @@ async function prepareOutputPath(params, assertGuards, privateWorkingMode = fals
231
257
  checkExtractionDeadline(params.deadline);
232
258
  }
233
259
  function assertSafeArchiveStagingPrefix(prefix) {
260
+ assertNoWindowsPathAlias(prefix, "relative");
234
261
  if (!prefix ||
235
262
  prefix === "." ||
236
263
  prefix === ".." ||
@@ -242,16 +269,22 @@ function assertSafeArchiveStagingPrefix(prefix) {
242
269
  return prefix;
243
270
  }
244
271
  export async function withStagedArchiveDestination(params) {
272
+ const destinationRealDir = params.destinationRealDir;
273
+ assertNoWindowsPathAlias(destinationRealDir);
245
274
  const stagingRoot = resolveSecureTempRoot({
246
275
  fallbackPrefix: "fs-safe-archive",
247
276
  unsafeFallbackLabel: "archive staging temp dir",
248
277
  warn: () => undefined,
249
278
  });
250
- if (isPathInside(params.destinationRealDir, stagingRoot)) {
279
+ assertNoWindowsPathAlias(stagingRoot);
280
+ if (isPathInside(destinationRealDir, stagingRoot)) {
251
281
  throw new Error(`archive staging root must be outside destination: ${stagingRoot}`);
252
282
  }
253
- const stagingPrefix = assertSafeArchiveStagingPrefix(params.stagingDirPrefix ?? "fs-safe-archive-");
254
- const stagingDir = await fs.mkdtemp(path.join(stagingRoot, stagingPrefix));
283
+ const stagingDirPrefix = params.stagingDirPrefix;
284
+ const stagingPrefix = assertSafeArchiveStagingPrefix(stagingDirPrefix ?? "fs-safe-archive-");
285
+ const stagingPathPrefix = path.join(stagingRoot, stagingPrefix);
286
+ assertNoWindowsPathAlias(stagingPathPrefix);
287
+ const stagingDir = await fs.mkdtemp(stagingPathPrefix);
255
288
  const stagingGuard = await createDirectoryIdentityGuard(stagingDir);
256
289
  try {
257
290
  await fs.chmod(stagingDir, ARCHIVE_STAGING_MODE).catch(() => undefined);
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-inspect.d.ts","sourceRoot":"","sources":["../src/archive-tar-inspect.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAOrF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,qBAAqB,EAC/D,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,aAAa,GAAG,YAAY,CAAC,CAAC;AACzE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;AAE3F,2FAA2F;AAC3F,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA2C/G"}
1
+ {"version":3,"file":"archive-tar-inspect.d.ts","sourceRoot":"","sources":["../src/archive-tar-inspect.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQrF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,qBAAqB,EAC/D,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,aAAa,GAAG,YAAY,CAAC,CAAC;AACzE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;AAE3F,2FAA2F;AAC3F,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CA6C/G"}
@@ -8,14 +8,17 @@ import { resolveArchiveFilteredEntryPolicy } from "./archive-policy.js";
8
8
  import { createTarEntryPlanner } from "./archive-tar.js";
9
9
  import { inspectTar } from "./archive-tar-stream.js";
10
10
  import { getNativeBinding } from "./native.js";
11
+ import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
11
12
  /** Complete TAR/gzip admission and zero-strip extraction policy, without output writes. */
12
13
  export async function inspectTarArchive(params) {
14
+ const archivePath = params.archivePath;
13
15
  const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
14
16
  const limits = resolveExtractLimits(params.limits);
15
17
  const tarLimits = resolveTarMeterLimits(limits);
16
18
  const native = getNativeBinding();
19
+ assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
17
20
  return await withExtractionDeadline(params.timeoutMs, "inspect tar", async (deadline) => {
18
- const staged = await stageArchiveFileForExtraction({ archivePath: params.archivePath, limits, deadline });
21
+ const staged = await stageArchiveFileForExtraction({ archivePath, limits, deadline });
19
22
  try {
20
23
  // Staging closes descriptors asynchronously; expiry there must not start a decoder.
21
24
  deadline.check();
@@ -2,7 +2,7 @@ import type { ExtractionDeadline } from "./archive-deadline.js";
2
2
  import { type ResolvedArchiveExtractLimits } from "./archive-limits.js";
3
3
  import { type ZipDirectoryEntry } from "./archive-zip-directory.js";
4
4
  /** Admit physical records before a decoder can normalize or collapse names. */
5
- export declare function admitZipBuffer(input: Uint8Array, limits: ResolvedArchiveExtractLimits): number;
5
+ export declare function admitZipBuffer(input: Uint8Array, limits: ResolvedArchiveExtractLimits, onEntry?: (entry: ZipDirectoryEntry) => void): number;
6
6
  /** The extraction input is private and immutable; read bounded metadata windows. */
7
7
  export declare function admitZipFile(archivePath: string, limits: ResolvedArchiveExtractLimits, deadline: ExtractionDeadline, onEntry?: (entry: ZipDirectoryEntry) => void): Promise<number>;
8
8
  //# sourceMappingURL=archive-zip-admission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-admission.d.ts","sourceRoot":"","sources":["../src/archive-zip-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAA+C,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAErH,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAUtF,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,GAAG,MAAM,CAU9F;AAMD,oFAAoF;AACpF,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,kBAAkB,EACvF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC,CA8DjB"}
1
+ {"version":3,"file":"archive-zip-admission.d.ts","sourceRoot":"","sources":["../src/archive-zip-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAA+C,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAErH,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAUtF,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GACpG,MAAM,CAUR;AAMD,oFAAoF;AACpF,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,kBAAkB,EACvF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC,CA8DjB"}
@@ -12,10 +12,10 @@ function checkSize(size, limits) {
12
12
  zipFormat("invalid archive size");
13
13
  }
14
14
  /** Admit physical records before a decoder can normalize or collapse names. */
15
- export function admitZipBuffer(input, limits) {
15
+ export function admitZipBuffer(input, limits, onEntry) {
16
16
  checkSize(input.byteLength, limits);
17
17
  const buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
18
- const scan = scanZipDirectory(buffer.length, limits);
18
+ const scan = scanZipDirectory(buffer.length, limits, onEntry);
19
19
  let step = scan.next();
20
20
  while (!step.done) {
21
21
  const { offset, length } = step.value;
@@ -1,4 +1,5 @@
1
1
  import { type ResolvedArchiveExtractLimits } from "./archive-limits.js";
2
+ import type { ArchiveEntryKind } from "./archive-policy.js";
2
3
  export type ZipRead = {
3
4
  offset: number;
4
5
  length: number;
@@ -10,6 +11,8 @@ export type ZipDirectoryEntry = {
10
11
  externalAttributes: number;
11
12
  size: number;
12
13
  path?: string;
14
+ portableKey: string;
15
+ kind: ArchiveEntryKind;
13
16
  };
14
17
  export declare function scanZipDirectory(size: number, limits: ResolvedArchiveExtractLimits, onEntry?: (entry: ZipDirectoryEntry) => void): ZipScan;
15
18
  //# sourceMappingURL=archive-zip-directory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAmIF,wBAAiB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC/F,OAAO,CAkET"}
1
+ {"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AA4IF,wBAAiB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC/F,OAAO,CAmET"}
@@ -1,5 +1,15 @@
1
1
  import { assertArchiveEntryCountWithinLimit, } from "./archive-limits.js";
2
2
  import { admitZipNames, zipExtraFields, zipFormat, zipUInt64 } from "./archive-zip-names.js";
3
+ function entryKind(attributes, directory) {
4
+ const type = (attributes >>> 16) & 0o170000;
5
+ // High-word symlinks remain links regardless of the creator or directory bits.
6
+ if (type === 0o120000)
7
+ return "symlink";
8
+ if ((attributes & 0x10) || type === 0o040000 || directory)
9
+ return "directory";
10
+ // Preserve the native decoder's rejection of unrecognized link-like types.
11
+ return (type & 0o120000) === 0o120000 ? "other" : "file";
12
+ }
3
13
  function* read(offset, length, bound) {
4
14
  if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(length) || offset < 0 || length < 0 || length > bound - offset) {
5
15
  zipFormat("record outside archive bounds");
@@ -193,7 +203,7 @@ export function* scanZipDirectory(size, limits, onEntry) {
193
203
  const localExtraLength = local.readUInt16LE(28);
194
204
  const localNames = yield* read(localAt + 30, localNameLength + localExtraLength, directory.start);
195
205
  const localExtra = zipExtraFields(localNames.subarray(localNameLength));
196
- const entryPath = admitZipNames({ central: centralName, local: localNames.subarray(0, localNameLength), flags, centralExtra, localExtra, seen });
206
+ const admittedName = admitZipNames({ central: centralName, local: localNames.subarray(0, localNameLength), flags, centralExtra, localExtra, seen });
197
207
  const localValues = wideValues(local, localExtra, false);
198
208
  const crc = central.readUInt32LE(16);
199
209
  if (!(flags & 8) && (local.readUInt32LE(14) !== crc || localValues.compressed !== values.compressed || localValues.uncompressed !== values.uncompressed)) {
@@ -213,7 +223,8 @@ export function* scanZipDirectory(size, limits, onEntry) {
213
223
  spans.push({ start: localAt, end: dataEnd });
214
224
  onEntry?.({
215
225
  index: count - 1, creatorSystem: central[5], externalAttributes: central.readUInt32LE(38),
216
- size: values.uncompressed, path: entryPath,
226
+ size: values.uncompressed, path: admittedName.path, portableKey: admittedName.portableKey,
227
+ kind: entryKind(central.readUInt32LE(38), admittedName.directory),
217
228
  });
218
229
  at = next;
219
230
  }
@@ -1,6 +1,7 @@
1
+ import type { ZipDirectoryEntry } from "./archive-zip-directory.js";
1
2
  export type ZipArchiveWithFiles = {
2
3
  files: Record<string, unknown>;
3
4
  };
4
- /** Internal: the caller has admitted these unchanged bytes and their physical count. */
5
- export declare function loadAdmittedZipArchive(buffer: Buffer | Uint8Array, entryCount: number): Promise<ZipArchiveWithFiles>;
5
+ /** Internal: the caller has admitted these unchanged bytes and their metadata. */
6
+ export declare function loadAdmittedZipArchive(buffer: Buffer | Uint8Array, admitted: ZipDirectoryEntry[]): Promise<ZipArchiveWithFiles>;
6
7
  //# sourceMappingURL=archive-zip-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-loader.d.ts","sourceRoot":"","sources":["../src/archive-zip-loader.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAMF,wFAAwF;AACxF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAoB9B"}
1
+ {"version":3,"file":"archive-zip-loader.d.ts","sourceRoot":"","sources":["../src/archive-zip-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAMF,kFAAkF;AAClF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CA6C9B"}
@@ -1,7 +1,9 @@
1
1
  import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
2
  import { validateArchiveEntryPath } from "./archive-entry.js";
3
- /** Internal: the caller has admitted these unchanged bytes and their physical count. */
4
- export async function loadAdmittedZipArchive(buffer, entryCount) {
3
+ import { isZipSymlinkEntry } from "./archive-zip-entry.js";
4
+ import { zipPathKey } from "./archive-zip-names.js";
5
+ /** Internal: the caller has admitted these unchanged bytes and their metadata. */
6
+ export async function loadAdmittedZipArchive(buffer, admitted) {
5
7
  const JSZip = await importOptionalJsZip();
6
8
  let archive;
7
9
  try {
@@ -11,11 +13,35 @@ export async function loadAdmittedZipArchive(buffer, entryCount) {
11
13
  throw new ArchiveFormatError(`invalid ZIP archive: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
12
14
  }
13
15
  const names = Object.keys(archive.files);
14
- if (names.length !== entryCount) {
16
+ if (names.length !== admitted.length) {
15
17
  throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
16
18
  }
17
- for (const name of names)
19
+ // Object key order is not central-directory order (numeric names reorder).
20
+ // Use the admitted portable interpretation, including legacy UTF-8 decoding.
21
+ const physicalByPath = new Map();
22
+ for (const entry of admitted) {
23
+ physicalByPath.set(entry.portableKey, entry);
24
+ }
25
+ for (const name of names) {
18
26
  validateArchiveEntryPath(name);
27
+ const key = zipPathKey(name);
28
+ const physical = physicalByPath.get(key);
29
+ const entry = archive.files[name];
30
+ const entryName = entry.name;
31
+ let entryKey = key;
32
+ if (entryName !== name) {
33
+ validateArchiveEntryPath(entryName);
34
+ entryKey = zipPathKey(entryName);
35
+ }
36
+ const kind = isZipSymlinkEntry(entry) ? "symlink" : entry.dir ? "directory" : "file";
37
+ if (!physical || entryKey !== key || kind !== physical.kind) {
38
+ throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
39
+ }
40
+ physicalByPath.delete(key);
41
+ }
42
+ if (physicalByPath.size) {
43
+ throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
44
+ }
19
45
  return archive;
20
46
  }
21
47
  async function importOptionalJsZip() {
@@ -0,0 +1,5 @@
1
+ import type { NativeArchiveEntry } from "./native-binding.js";
2
+ import type { ZipDirectoryEntry } from "./archive-zip-directory.js";
3
+ /** Associate all native entries with physical admission before exposing any. */
4
+ export declare function validateNativeZipManifest(manifest: NativeArchiveEntry[], admitted: ZipDirectoryEntry[]): void;
5
+ //# sourceMappingURL=archive-zip-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-zip-manifest.d.ts","sourceRoot":"","sources":["../src/archive-zip-manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,gFAAgF;AAChF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,kBAAkB,EAAE,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAC5D,IAAI,CAkBN"}
@@ -0,0 +1,22 @@
1
+ import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
+ import { stripArchivePath } from "./archive-entry.js";
3
+ /** Associate all native entries with physical admission before exposing any. */
4
+ export function validateNativeZipManifest(manifest, admitted) {
5
+ if (manifest.length !== admitted.length) {
6
+ throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
7
+ }
8
+ for (const [ordinal, entry] of manifest.entries()) {
9
+ const physical = admitted[ordinal];
10
+ const physicalPath = physical?.path;
11
+ const pathMatches = physicalPath === undefined || entry.path === physicalPath ||
12
+ stripArchivePath(entry.path, 0) === stripArchivePath(physicalPath, 0);
13
+ // Native indices are physical central-directory ordinals. Legacy name
14
+ // decoding remains native-selected; compare names when admission knows them.
15
+ if (!physical || physical.index !== ordinal || entry.index !== ordinal ||
16
+ entry.size !== physical.size || entry.kind !== physical.kind ||
17
+ !pathMatches ||
18
+ (physical.creatorSystem === 3 && entry.mode !== physical.externalAttributes >>> 16)) {
19
+ throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
20
+ }
21
+ }
22
+ }
@@ -1,6 +1,7 @@
1
1
  export declare function zipFormat(message: string): never;
2
2
  export declare function zipUInt64(bytes: Buffer, offset: number): number;
3
3
  export declare function zipExtraFields(bytes: Buffer): Map<number, Buffer>;
4
+ export declare function zipPathKey(name: string): string;
4
5
  export declare function admitZipNames(params: {
5
6
  central: Buffer;
6
7
  local: Buffer;
@@ -8,5 +9,9 @@ export declare function admitZipNames(params: {
8
9
  centralExtra: Map<number, Buffer>;
9
10
  localExtra: Map<number, Buffer>;
10
11
  seen: Set<string>;
11
- }): string | undefined;
12
+ }): {
13
+ path?: string;
14
+ portableKey: string;
15
+ directory: boolean;
16
+ };
12
17
  //# sourceMappingURL=archive-zip-names.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AA+BD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG,MAAM,GAAG,SAAS,CA8CrB"}
1
+ {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AAUD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAoBD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAiE7D"}
@@ -41,7 +41,10 @@ function utf8(bytes) {
41
41
  return zipFormat("invalid UTF-8 name");
42
42
  }
43
43
  }
44
- function key(name) {
44
+ // Canonical identity only; this does not replace raw path validation.
45
+ export function zipPathKey(name) {
46
+ if (name && name !== "." && !name.includes("/") && !name.includes("\\"))
47
+ return name;
45
48
  return stripArchivePath(name, 0) ?? "";
46
49
  }
47
50
  function originalName(name, rawName, flags) {
@@ -87,31 +90,49 @@ export function admitZipNames(params) {
87
90
  (centralField === localField ||
88
91
  (centralField !== undefined && localField !== undefined && centralField.equals(localField)));
89
92
  const localUnicode = sameName && sameUnicode ? centralUnicode : unicodeName(local, localExtra);
90
- const centralKey = key(centralRaw);
91
- if (!sameName && centralKey !== key(localRaw)) {
93
+ const centralKey = zipPathKey(centralRaw);
94
+ if (!sameName && centralKey !== zipPathKey(localRaw)) {
92
95
  zipFormat("central and local names disagree");
93
96
  }
94
97
  const interpretations = [centralUtf8, localUtf8, centralUnicode, localUnicode].filter((value) => value !== undefined);
95
- const interpretationKey = interpretations.length ? key(interpretations[0]) : undefined;
96
- if (interpretations.some((value) => value !== interpretations[0] && key(value) !== interpretationKey)) {
98
+ // Canonical paths erase terminal separators. Preserve their kind meaning in
99
+ // every interpretation, including Unicode overrides of legacy-encoded names.
100
+ const directory = /[/\\]$/.test(centralRaw);
101
+ if (/[/\\]$/.test(localRaw) !== directory ||
102
+ interpretations.some((value) => /[/\\]$/.test(value) !== directory)) {
103
+ zipFormat("conflicting terminal directory markers");
104
+ }
105
+ const interpretationKey = interpretations.length ? zipPathKey(interpretations[0]) : undefined;
106
+ if (interpretations.some((value) => value !== interpretations[0] && zipPathKey(value) !== interpretationKey)) {
97
107
  zipFormat("conflicting Unicode name interpretations");
98
108
  }
99
109
  // JSZip checks the central Unicode field against the local name. A slash-only
100
110
  // spelling difference must not make one decoder ignore a meaningful override.
101
111
  if (centralUnicode && !central.equals(local) &&
102
- key(centralUnicode) !== key(local.toString("utf8"))) {
112
+ zipPathKey(centralUnicode) !== zipPathKey(local.toString("utf8"))) {
103
113
  zipFormat("Unicode override disagrees with local decoder name");
104
114
  }
105
- if (!centralUnicode && localUnicode && key(localUnicode) !== key(local.toString("utf8"))) {
115
+ if (!centralUnicode && localUnicode && zipPathKey(localUnicode) !== zipPathKey(local.toString("utf8"))) {
106
116
  zipFormat("local-only Unicode override changes the name");
107
117
  }
108
- const identities = new Set([centralKey]);
109
- if (centralUnicode !== undefined)
110
- identities.add(key(Buffer.from(centralUnicode).toString("latin1")));
111
- if ([...identities].some((identity) => seen.has(identity))) {
118
+ const unicodeKey = centralUnicode === undefined
119
+ ? undefined : zipPathKey(Buffer.from(centralUnicode).toString("latin1"));
120
+ if (seen.has(centralKey) ||
121
+ (unicodeKey !== undefined && unicodeKey !== centralKey && seen.has(unicodeKey))) {
112
122
  throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
113
123
  }
114
- for (const identity of identities)
115
- seen.add(identity);
116
- return centralUnicode ?? centralUtf8 ?? (central.every((byte) => byte < 128) ? central.toString("ascii") : undefined);
124
+ seen.add(centralKey);
125
+ if (unicodeKey !== undefined && unicodeKey !== centralKey)
126
+ seen.add(unicodeKey);
127
+ const path = centralUnicode ?? centralUtf8 ?? (central.every((byte) => byte < 128) ? central.toString("ascii") : undefined);
128
+ const portablePath = centralUnicode ?? localUtf8 ?? (sameName && path !== undefined ? path : local.toString("utf8"));
129
+ // Reuse the admitted interpretation, never the Latin-1 Unicode collision key.
130
+ // Unflagged legacy names can decode differently and need their own key.
131
+ const portableKey = portablePath === centralRaw ? centralKey
132
+ : interpretations.includes(portablePath) ? interpretationKey : zipPathKey(portablePath);
133
+ return {
134
+ path,
135
+ portableKey,
136
+ directory,
137
+ };
117
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,iCAAiC,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAO9B"}
1
+ {"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE3F,OAAO,EAAE,iCAAiC,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAQ9B"}
@@ -7,6 +7,7 @@ export async function loadZipArchiveWithPreflight(buffer, limits) {
7
7
  if (buffer.byteLength > resolvedLimits.maxArchiveBytes) {
8
8
  throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
9
9
  }
10
- const entryCount = admitZipBuffer(buffer, resolvedLimits);
11
- return await loadAdmittedZipArchive(buffer, entryCount);
10
+ const entries = [];
11
+ admitZipBuffer(buffer, resolvedLimits, entry => { entries.push(entry); });
12
+ return await loadAdmittedZipArchive(buffer, entries);
12
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDjF"}
package/dist/archive.js CHANGED
@@ -22,6 +22,7 @@ import { stageArchiveFileForExtraction } from "./archive-input.js";
22
22
  import { getNativeBinding } from "./native.js";
23
23
  import { resolveArchiveFilteredEntryPolicy, shouldExtractArchiveEntry, } from "./archive-policy.js";
24
24
  import { writeSiblingTempFile } from "./sibling-temp.js";
25
+ import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
25
26
  import { realpathSync } from "./realpath.js";
26
27
  export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
27
28
  export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
@@ -51,7 +52,7 @@ function resolveZipOutputPath(params) {
51
52
  const canonicalPath = stripArchivePath(params.entryPath, 0);
52
53
  if (!canonicalPath)
53
54
  return null;
54
- const relPath = stripArchivePath(canonicalPath, params.strip);
55
+ const relPath = params.strip === 0 ? canonicalPath : stripArchivePath(canonicalPath, params.strip);
55
56
  if (!relPath) {
56
57
  return null;
57
58
  }
@@ -203,19 +204,23 @@ async function extractZip(params) {
203
204
  }
204
205
  }
205
206
  export async function extractArchive(params) {
207
+ const archivePath = params.archivePath;
208
+ const destDir = params.destDir;
206
209
  const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
207
- const kind = params.kind ?? resolveArchiveKind(params.archivePath);
210
+ const kind = params.kind ?? resolveArchiveKind(archivePath);
208
211
  if (!kind) {
209
- throw new Error(`unsupported archive: ${params.archivePath}`);
212
+ throw new Error(`unsupported archive: ${archivePath}`);
210
213
  }
211
214
  const label = kind === "zip" ? "extract zip" : "extract tar";
212
215
  const limits = resolveExtractLimits(params.limits);
213
216
  const tarLimits = resolveTarMeterLimits(limits);
214
217
  const native = getNativeBinding();
218
+ assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
219
+ assertNoWindowsPathAlias(destDir, "filesystem", "archive destination uses a Windows filesystem namespace alias");
215
220
  // Read the declared public fields before private adapters copy options: class
216
221
  // getters and inherited policy must survive identically on every backend.
217
222
  const options = {
218
- archivePath: params.archivePath, destDir: params.destDir,
223
+ archivePath, destDir,
219
224
  durable: params.durable,
220
225
  stripComponents: params.stripComponents, limits,
221
226
  entryModes: params.entryModes, entryFilter: params.entryFilter, onFiltered,
@@ -0,0 +1,4 @@
1
+ import type { NativeDarwinAclFacts } from "./native-binding.js";
2
+ /** Internal descriptor facts only; callers own identity, lifetime, and ACL policy. */
3
+ export declare function inspectDarwinAcl(fd: number): NativeDarwinAclFacts;
4
+ //# sourceMappingURL=darwin-acl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"darwin-acl.d.ts","sourceRoot":"","sources":["../src/darwin-acl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,CAmBjE"}
@@ -0,0 +1,24 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { getNativeBinding } from "./native.js";
3
+ /** Internal descriptor facts only; callers own identity, lifetime, and ACL policy. */
4
+ export function inspectDarwinAcl(fd) {
5
+ if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fff_ffff) {
6
+ throw new FsSafeError("permission-unverified", "Darwin ACL inspection requires a valid descriptor");
7
+ }
8
+ const native = getNativeBinding();
9
+ if (typeof native?.inspectDarwinAcl !== "function") {
10
+ throw new FsSafeError("helper-unavailable", "Darwin ACL inspection requires the matching native capability");
11
+ }
12
+ let facts;
13
+ try {
14
+ facts = native.inspectDarwinAcl(fd);
15
+ }
16
+ catch (cause) {
17
+ throw new FsSafeError("permission-unverified", "Darwin descriptor ACL could not be inspected", { cause });
18
+ }
19
+ const state = facts && typeof facts === "object" && "state" in facts ? facts.state : undefined;
20
+ if (state !== "absent" && state !== "empty" && state !== "present") {
21
+ throw new FsSafeError("permission-unverified", "Darwin ACL inspection returned invalid facts");
22
+ }
23
+ return { state };
24
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"deny-mutations.d.ts","sourceRoot":"","sources":["../src/deny-mutations.ts"],"names":[],"mappings":"AAKA,wBAAsB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAI1F;AAMD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAqBF,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GAAG,SAAS,EACtC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,kBAAkB,GAAG,SAAS,EAC7C,UAAU,EAAE,kBAAkB,GAAG,SAAS,GACzC,kBAAkB,GAAG,SAAS,CAWhC"}
1
+ {"version":3,"file":"deny-mutations.d.ts","sourceRoot":"","sources":["../src/deny-mutations.ts"],"names":[],"mappings":"AASA,wBAAsB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAQ1F;AAMD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAsBF,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GAAG,SAAS,EACtC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,kBAAkB,GAAG,SAAS,EAC7C,UAAU,EAAE,kBAAkB,GAAG,SAAS,GACzC,kBAAkB,GAAG,SAAS,CAWhC"}
@@ -2,10 +2,15 @@ import path from "node:path";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { assertNoNulPathInput, isPathInside } from "./path.js";
4
4
  import { resolvePathViaExistingAncestor } from "./root-path-existing.js";
5
+ import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
5
6
  export async function resolveMutationComparablePaths(rawPath) {
6
7
  assertNoNulPathInput(rawPath, "path contains a NUL byte");
7
- const resolved = path.resolve(rawPath);
8
- return new Set([resolved, await resolvePathViaExistingAncestor(resolved)]);
8
+ assertNoWindowsPathAlias(rawPath, "filesystem", "mutation path uses a Windows filesystem namespace alias");
9
+ const resolved = resolvePathPreservingWindowsRoot(rawPath);
10
+ assertNoWindowsPathAlias(resolved, "filesystem", "mutation path uses a Windows filesystem namespace alias");
11
+ const canonical = await resolvePathViaExistingAncestor(resolved);
12
+ assertNoWindowsPathAlias(canonical, "filesystem", "mutation path uses a Windows filesystem namespace alias");
13
+ return new Set([resolved, canonical]);
9
14
  }
10
15
  function isSamePath(left, right) {
11
16
  return isPathInside(left, right) && isPathInside(right, left);
@@ -20,6 +25,7 @@ function policyPathEntries(entries) {
20
25
  throw new FsSafeError("invalid-path", "deny mutation paths must be non-empty");
21
26
  }
22
27
  assertNoNulPathInput(entry, "deny mutation path contains a NUL byte");
28
+ assertNoWindowsPathAlias(entry, "filesystem", "deny mutation path uses a Windows filesystem namespace alias");
23
29
  if (!path.isAbsolute(entry)) {
24
30
  throw new FsSafeError("invalid-path", "deny mutation paths must be absolute");
25
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
1
+ {"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAwOF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAe1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAqB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAyCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAiCD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA2FlC"}