@openclaw/fs-safe 0.11.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 (416) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +15 -8
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +32 -11
  5. package/dist/advanced.d.ts +3 -1
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +3 -1
  8. package/dist/archive-entry.d.ts.map +1 -1
  9. package/dist/archive-entry.js +4 -3
  10. package/dist/archive-gzip-tail.d.ts.map +1 -1
  11. package/dist/archive-gzip-tail.js +13 -9
  12. package/dist/archive-input.d.ts.map +1 -1
  13. package/dist/archive-input.js +6 -2
  14. package/dist/archive-merge.d.ts.map +1 -1
  15. package/dist/archive-merge.js +15 -2
  16. package/dist/archive-native.d.ts.map +1 -1
  17. package/dist/archive-native.js +7 -19
  18. package/dist/archive-plan.js +1 -1
  19. package/dist/archive-read.d.ts.map +1 -1
  20. package/dist/archive-read.js +55 -46
  21. package/dist/archive-staging.d.ts.map +1 -1
  22. package/dist/archive-staging.js +43 -10
  23. package/dist/archive-tar-inspect.d.ts.map +1 -1
  24. package/dist/archive-tar-inspect.js +4 -1
  25. package/dist/archive-zip-admission.d.ts +1 -1
  26. package/dist/archive-zip-admission.d.ts.map +1 -1
  27. package/dist/archive-zip-admission.js +2 -2
  28. package/dist/archive-zip-directory.d.ts +3 -0
  29. package/dist/archive-zip-directory.d.ts.map +1 -1
  30. package/dist/archive-zip-directory.js +13 -2
  31. package/dist/archive-zip-loader.d.ts +3 -2
  32. package/dist/archive-zip-loader.d.ts.map +1 -1
  33. package/dist/archive-zip-loader.js +30 -4
  34. package/dist/archive-zip-manifest.d.ts +5 -0
  35. package/dist/archive-zip-manifest.d.ts.map +1 -0
  36. package/dist/archive-zip-manifest.js +22 -0
  37. package/dist/archive-zip-names.d.ts +6 -1
  38. package/dist/archive-zip-names.d.ts.map +1 -1
  39. package/dist/archive-zip-names.js +50 -17
  40. package/dist/archive-zip-preflight.d.ts.map +1 -1
  41. package/dist/archive-zip-preflight.js +3 -2
  42. package/dist/archive.d.ts.map +1 -1
  43. package/dist/archive.js +9 -4
  44. package/dist/bounded-read.js +2 -2
  45. package/dist/darwin-acl.d.ts +4 -0
  46. package/dist/darwin-acl.d.ts.map +1 -0
  47. package/dist/darwin-acl.js +24 -0
  48. package/dist/deny-mutations.d.ts.map +1 -1
  49. package/dist/deny-mutations.js +8 -2
  50. package/dist/device-path.d.ts.map +1 -1
  51. package/dist/device-path.js +5 -3
  52. package/dist/directory-durability.d.ts.map +1 -1
  53. package/dist/directory-durability.js +67 -23
  54. package/dist/directory-entry-path.d.ts +3 -0
  55. package/dist/directory-entry-path.d.ts.map +1 -0
  56. package/dist/directory-entry-path.js +21 -0
  57. package/dist/directory-guard.d.ts +17 -1
  58. package/dist/directory-guard.d.ts.map +1 -1
  59. package/dist/directory-guard.js +134 -48
  60. package/dist/directory-mode-node.d.ts +12 -0
  61. package/dist/directory-mode-node.d.ts.map +1 -1
  62. package/dist/directory-mode-node.js +102 -4
  63. package/dist/effective-uid.d.ts +2 -0
  64. package/dist/effective-uid.d.ts.map +1 -0
  65. package/dist/effective-uid.js +25 -0
  66. package/dist/file-handle-transfer.d.ts.map +1 -1
  67. package/dist/file-handle-transfer.js +98 -27
  68. package/dist/file-hash.d.ts.map +1 -1
  69. package/dist/file-hash.js +12 -2
  70. package/dist/file-lock-sync.d.ts.map +1 -1
  71. package/dist/file-lock-sync.js +36 -6
  72. package/dist/file-lock.d.ts.map +1 -1
  73. package/dist/file-lock.js +29 -9
  74. package/dist/file-observation.d.ts +1 -1
  75. package/dist/file-observation.d.ts.map +1 -1
  76. package/dist/file-store-boundary.d.ts +6 -2
  77. package/dist/file-store-boundary.d.ts.map +1 -1
  78. package/dist/file-store-boundary.js +20 -65
  79. package/dist/file-store-copy-source.d.ts +5 -0
  80. package/dist/file-store-copy-source.d.ts.map +1 -0
  81. package/dist/file-store-copy-source.js +31 -0
  82. package/dist/file-store-path.d.ts.map +1 -1
  83. package/dist/file-store-path.js +4 -1
  84. package/dist/file-store-prune.d.ts.map +1 -1
  85. package/dist/file-store-prune.js +9 -1
  86. package/dist/file-store-sync-directory.d.ts +16 -0
  87. package/dist/file-store-sync-directory.d.ts.map +1 -0
  88. package/dist/file-store-sync-directory.js +349 -0
  89. package/dist/file-store.d.ts.map +1 -1
  90. package/dist/file-store.js +11 -28
  91. package/dist/filename.d.ts +1 -0
  92. package/dist/filename.d.ts.map +1 -1
  93. package/dist/filename.js +85 -23
  94. package/dist/fs.d.ts.map +1 -1
  95. package/dist/fs.js +3 -2
  96. package/dist/guarded-mkdir.d.ts +10 -0
  97. package/dist/guarded-mkdir.d.ts.map +1 -1
  98. package/dist/guarded-mkdir.js +217 -33
  99. package/dist/guest.d.ts.map +1 -1
  100. package/dist/guest.js +16 -5
  101. package/dist/home-dir.d.ts.map +1 -1
  102. package/dist/home-dir.js +73 -10
  103. package/dist/install-path.d.ts +6 -0
  104. package/dist/install-path.d.ts.map +1 -1
  105. package/dist/install-path.js +67 -19
  106. package/dist/json-durable-queue-ownership.d.ts +8 -0
  107. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  108. package/dist/json-durable-queue-ownership.js +134 -43
  109. package/dist/json-durable-queue-paths.d.ts +11 -0
  110. package/dist/json-durable-queue-paths.d.ts.map +1 -0
  111. package/dist/json-durable-queue-paths.js +42 -0
  112. package/dist/json-durable-queue-read.d.ts +6 -0
  113. package/dist/json-durable-queue-read.d.ts.map +1 -0
  114. package/dist/json-durable-queue-read.js +61 -0
  115. package/dist/json-durable-queue.d.ts +1 -1
  116. package/dist/json-durable-queue.d.ts.map +1 -1
  117. package/dist/json-durable-queue.js +83 -134
  118. package/dist/json-store.d.ts.map +1 -1
  119. package/dist/json-store.js +5 -1
  120. package/dist/json.d.ts.map +1 -1
  121. package/dist/json.js +54 -22
  122. package/dist/local-file-access.d.ts.map +1 -1
  123. package/dist/local-file-access.js +4 -0
  124. package/dist/local-file-descriptor.d.ts +17 -0
  125. package/dist/local-file-descriptor.d.ts.map +1 -0
  126. package/dist/local-file-descriptor.js +84 -0
  127. package/dist/local-roots.d.ts.map +1 -1
  128. package/dist/local-roots.js +35 -7
  129. package/dist/move-path-cleanup.d.ts +2 -0
  130. package/dist/move-path-cleanup.d.ts.map +1 -1
  131. package/dist/move-path-cleanup.js +44 -18
  132. package/dist/move-path.d.ts.map +1 -1
  133. package/dist/move-path.js +31 -6
  134. package/dist/native-binding.d.ts +21 -0
  135. package/dist/native-binding.d.ts.map +1 -1
  136. package/dist/native-directory-observation.d.ts +17 -0
  137. package/dist/native-directory-observation.d.ts.map +1 -0
  138. package/dist/native-directory-observation.js +37 -0
  139. package/dist/native-parent-admission.d.ts +31 -0
  140. package/dist/native-parent-admission.d.ts.map +1 -0
  141. package/dist/native-parent-admission.js +124 -0
  142. package/dist/native-pinned-write-windows.d.ts +0 -1
  143. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  144. package/dist/native-pinned-write-windows.js +0 -3
  145. package/dist/native-pinned-write.d.ts.map +1 -1
  146. package/dist/native-pinned-write.js +309 -40
  147. package/dist/output.d.ts.map +1 -1
  148. package/dist/output.js +15 -5
  149. package/dist/overwrite-file-handle.d.ts.map +1 -1
  150. package/dist/overwrite-file-handle.js +5 -1
  151. package/dist/owner-dacl.d.ts.map +1 -1
  152. package/dist/owner-dacl.js +2 -0
  153. package/dist/path-policy.d.ts.map +1 -1
  154. package/dist/path-policy.js +7 -2
  155. package/dist/path-prefix.d.ts +7 -0
  156. package/dist/path-prefix.d.ts.map +1 -0
  157. package/dist/path-prefix.js +82 -0
  158. package/dist/path-scope-lexical.d.ts +14 -0
  159. package/dist/path-scope-lexical.d.ts.map +1 -0
  160. package/dist/path-scope-lexical.js +37 -0
  161. package/dist/path-segment-route.d.ts +7 -0
  162. package/dist/path-segment-route.d.ts.map +1 -0
  163. package/dist/path-segment-route.js +24 -0
  164. package/dist/path-suffix-aliases.d.ts +10 -0
  165. package/dist/path-suffix-aliases.d.ts.map +1 -0
  166. package/dist/path-suffix-aliases.js +386 -0
  167. package/dist/path.d.ts.map +1 -1
  168. package/dist/path.js +35 -6
  169. package/dist/permissions-windows.d.ts.map +1 -1
  170. package/dist/permissions-windows.js +14 -3
  171. package/dist/permissions.d.ts.map +1 -1
  172. package/dist/permissions.js +37 -8
  173. package/dist/pinned-mutation-admission.d.ts +25 -0
  174. package/dist/pinned-mutation-admission.d.ts.map +1 -0
  175. package/dist/pinned-mutation-admission.js +425 -0
  176. package/dist/pinned-mutation-observation.d.ts +34 -0
  177. package/dist/pinned-mutation-observation.d.ts.map +1 -0
  178. package/dist/pinned-mutation-observation.js +142 -0
  179. package/dist/pinned-mutation-shared-route.d.ts +24 -0
  180. package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
  181. package/dist/pinned-mutation-shared-route.js +70 -0
  182. package/dist/pinned-open.d.ts +6 -0
  183. package/dist/pinned-open.d.ts.map +1 -1
  184. package/dist/pinned-open.js +28 -10
  185. package/dist/pinned-write-types.d.ts +75 -0
  186. package/dist/pinned-write-types.d.ts.map +1 -0
  187. package/dist/pinned-write-types.js +1 -0
  188. package/dist/pinned-write.d.ts +7 -33
  189. package/dist/pinned-write.d.ts.map +1 -1
  190. package/dist/pinned-write.js +152 -17
  191. package/dist/private-directory.d.ts.map +1 -1
  192. package/dist/private-directory.js +2 -0
  193. package/dist/private-producer-handoff.d.ts +16 -0
  194. package/dist/private-producer-handoff.d.ts.map +1 -0
  195. package/dist/private-producer-handoff.js +272 -0
  196. package/dist/private-temp-workspace.d.ts +2 -39
  197. package/dist/private-temp-workspace.d.ts.map +1 -1
  198. package/dist/private-temp-workspace.js +183 -77
  199. package/dist/publish-file.d.ts.map +1 -1
  200. package/dist/publish-file.js +33 -29
  201. package/dist/regular-file.d.ts.map +1 -1
  202. package/dist/regular-file.js +56 -45
  203. package/dist/replace-directory.d.ts.map +1 -1
  204. package/dist/replace-directory.js +12 -5
  205. package/dist/replace-file-temp-owner.d.ts +1 -0
  206. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  207. package/dist/replace-file-temp-owner.js +12 -1
  208. package/dist/replace-file.d.ts.map +1 -1
  209. package/dist/replace-file.js +24 -24
  210. package/dist/root-boundary.d.ts +29 -0
  211. package/dist/root-boundary.d.ts.map +1 -0
  212. package/dist/root-boundary.js +182 -0
  213. package/dist/root-context.d.ts +12 -1
  214. package/dist/root-context.d.ts.map +1 -1
  215. package/dist/root-context.js +89 -16
  216. package/dist/root-directory-creation.d.ts +13 -0
  217. package/dist/root-directory-creation.d.ts.map +1 -0
  218. package/dist/root-directory-creation.js +212 -0
  219. package/dist/root-directory-list.d.ts +16 -4
  220. package/dist/root-directory-list.d.ts.map +1 -1
  221. package/dist/root-directory-list.js +182 -33
  222. package/dist/root-directory.d.ts +23 -0
  223. package/dist/root-directory.d.ts.map +1 -0
  224. package/dist/root-directory.js +141 -0
  225. package/dist/root-errors.d.ts.map +1 -1
  226. package/dist/root-errors.js +12 -1
  227. package/dist/root-file-final-admission.d.ts +21 -0
  228. package/dist/root-file-final-admission.d.ts.map +1 -0
  229. package/dist/root-file-final-admission.js +83 -0
  230. package/dist/root-file.d.ts.map +1 -1
  231. package/dist/root-file.js +103 -24
  232. package/dist/root-impl.d.ts +1 -0
  233. package/dist/root-impl.d.ts.map +1 -1
  234. package/dist/root-impl.js +316 -325
  235. package/dist/root-move-noreplace.d.ts +14 -0
  236. package/dist/root-move-noreplace.d.ts.map +1 -0
  237. package/dist/root-move-noreplace.js +202 -0
  238. package/dist/root-move-preflight.d.ts +8 -0
  239. package/dist/root-move-preflight.d.ts.map +1 -0
  240. package/dist/root-move-preflight.js +16 -0
  241. package/dist/root-observed-path.d.ts +9 -0
  242. package/dist/root-observed-path.d.ts.map +1 -0
  243. package/dist/root-observed-path.js +95 -0
  244. package/dist/root-path-errors.d.ts +12 -0
  245. package/dist/root-path-errors.d.ts.map +1 -0
  246. package/dist/root-path-errors.js +13 -0
  247. package/dist/root-path-existing.d.ts +2 -0
  248. package/dist/root-path-existing.d.ts.map +1 -1
  249. package/dist/root-path-existing.js +63 -22
  250. package/dist/root-path-observation.d.ts +63 -0
  251. package/dist/root-path-observation.d.ts.map +1 -0
  252. package/dist/root-path-observation.js +180 -0
  253. package/dist/root-path-stat.d.ts +5 -0
  254. package/dist/root-path-stat.d.ts.map +1 -0
  255. package/dist/root-path-stat.js +101 -0
  256. package/dist/root-path-symlink.d.ts.map +1 -1
  257. package/dist/root-path-symlink.js +23 -4
  258. package/dist/root-path.d.ts +13 -0
  259. package/dist/root-path.d.ts.map +1 -1
  260. package/dist/root-path.js +263 -73
  261. package/dist/root-paths-lexical.d.ts +9 -0
  262. package/dist/root-paths-lexical.d.ts.map +1 -0
  263. package/dist/root-paths-lexical.js +22 -0
  264. package/dist/root-paths.d.ts +3 -29
  265. package/dist/root-paths.d.ts.map +1 -1
  266. package/dist/root-paths.js +89 -178
  267. package/dist/root-read-admission.d.ts +26 -0
  268. package/dist/root-read-admission.d.ts.map +1 -0
  269. package/dist/root-read-admission.js +94 -0
  270. package/dist/root-remove-identity.d.ts +15 -0
  271. package/dist/root-remove-identity.d.ts.map +1 -0
  272. package/dist/root-remove-identity.js +89 -0
  273. package/dist/root-remove-receipt.d.ts +17 -0
  274. package/dist/root-remove-receipt.d.ts.map +1 -0
  275. package/dist/root-remove-receipt.js +37 -0
  276. package/dist/root-remove.d.ts +2 -1
  277. package/dist/root-remove.d.ts.map +1 -1
  278. package/dist/root-remove.js +172 -10
  279. package/dist/root-walk.d.ts.map +1 -1
  280. package/dist/root-walk.js +2 -1
  281. package/dist/root-write-admission.d.ts +68 -0
  282. package/dist/root-write-admission.d.ts.map +1 -0
  283. package/dist/root-write-admission.js +322 -0
  284. package/dist/root-write-compatibility.d.ts +13 -0
  285. package/dist/root-write-compatibility.d.ts.map +1 -0
  286. package/dist/root-write-compatibility.js +74 -0
  287. package/dist/root-write-complete-parent.d.ts +42 -0
  288. package/dist/root-write-complete-parent.d.ts.map +1 -0
  289. package/dist/root-write-complete-parent.js +195 -0
  290. package/dist/root-write-mode.d.ts +2 -0
  291. package/dist/root-write-mode.d.ts.map +1 -1
  292. package/dist/root-write-mode.js +20 -7
  293. package/dist/root-write-publication.d.ts +24 -0
  294. package/dist/root-write-publication.d.ts.map +1 -0
  295. package/dist/root-write-publication.js +85 -0
  296. package/dist/root-write-verification.d.ts.map +1 -1
  297. package/dist/root-write-verification.js +18 -7
  298. package/dist/safe-path-segment.d.ts.map +1 -1
  299. package/dist/safe-path-segment.js +3 -1
  300. package/dist/secret-file.d.ts.map +1 -1
  301. package/dist/secret-file.js +62 -24
  302. package/dist/secret-read-async.d.ts.map +1 -1
  303. package/dist/secret-read-async.js +18 -13
  304. package/dist/secret-read-policy.d.ts.map +1 -1
  305. package/dist/secret-read-policy.js +5 -1
  306. package/dist/secure-file-windows.d.ts +10 -0
  307. package/dist/secure-file-windows.d.ts.map +1 -0
  308. package/dist/secure-file-windows.js +186 -0
  309. package/dist/secure-file.d.ts.map +1 -1
  310. package/dist/secure-file.js +98 -10
  311. package/dist/secure-temp-dir.d.ts +6 -3
  312. package/dist/secure-temp-dir.d.ts.map +1 -1
  313. package/dist/secure-temp-dir.js +121 -101
  314. package/dist/secure-temp-repair.d.ts +35 -0
  315. package/dist/secure-temp-repair.d.ts.map +1 -0
  316. package/dist/secure-temp-repair.js +106 -0
  317. package/dist/sibling-staged-file.d.ts +3 -1
  318. package/dist/sibling-staged-file.d.ts.map +1 -1
  319. package/dist/sibling-staged-file.js +181 -50
  320. package/dist/sibling-temp.d.ts.map +1 -1
  321. package/dist/sibling-temp.js +27 -12
  322. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  323. package/dist/sidecar-lock-acquire.js +45 -15
  324. package/dist/sidecar-lock-root.d.ts.map +1 -1
  325. package/dist/sidecar-lock-root.js +4 -2
  326. package/dist/sidecar-lock.d.ts.map +1 -1
  327. package/dist/sidecar-lock.js +2 -3
  328. package/dist/staged-directory.d.ts +14 -5
  329. package/dist/staged-directory.d.ts.map +1 -1
  330. package/dist/staged-directory.js +54 -3
  331. package/dist/standalone-publication-path.d.ts +2 -0
  332. package/dist/standalone-publication-path.d.ts.map +1 -0
  333. package/dist/standalone-publication-path.js +6 -0
  334. package/dist/stat-observation.d.ts +11 -0
  335. package/dist/stat-observation.d.ts.map +1 -0
  336. package/dist/stat-observation.js +64 -0
  337. package/dist/strict-file-identity.d.ts.map +1 -1
  338. package/dist/strict-file-identity.js +39 -2
  339. package/dist/symlink-parents.d.ts.map +1 -1
  340. package/dist/symlink-parents.js +7 -2
  341. package/dist/temp-target.d.ts +2 -0
  342. package/dist/temp-target.d.ts.map +1 -1
  343. package/dist/temp-target.js +133 -4
  344. package/dist/temp-workspace-admission.d.ts +22 -0
  345. package/dist/temp-workspace-admission.d.ts.map +1 -0
  346. package/dist/temp-workspace-admission.js +374 -0
  347. package/dist/temp-workspace-child-admission.d.ts +13 -0
  348. package/dist/temp-workspace-child-admission.d.ts.map +1 -0
  349. package/dist/temp-workspace-child-admission.js +95 -0
  350. package/dist/temp-workspace-descriptor.d.ts +48 -0
  351. package/dist/temp-workspace-descriptor.d.ts.map +1 -0
  352. package/dist/temp-workspace-descriptor.js +363 -0
  353. package/dist/temp-workspace-identity.d.ts +15 -0
  354. package/dist/temp-workspace-identity.d.ts.map +1 -0
  355. package/dist/temp-workspace-identity.js +41 -0
  356. package/dist/temp-workspace-owner.d.ts +7 -6
  357. package/dist/temp-workspace-owner.d.ts.map +1 -1
  358. package/dist/temp-workspace-owner.js +121 -61
  359. package/dist/temp-workspace-permissions.d.ts +4 -0
  360. package/dist/temp-workspace-permissions.d.ts.map +1 -0
  361. package/dist/temp-workspace-permissions.js +32 -0
  362. package/dist/temp-workspace-types.d.ts +40 -0
  363. package/dist/temp-workspace-types.d.ts.map +1 -0
  364. package/dist/temp-workspace-types.js +1 -0
  365. package/dist/temp.d.ts +1 -1
  366. package/dist/temp.d.ts.map +1 -1
  367. package/dist/temp.js +1 -1
  368. package/dist/test-hooks.d.ts +7 -0
  369. package/dist/test-hooks.d.ts.map +1 -1
  370. package/dist/text-atomic.d.ts.map +1 -1
  371. package/dist/text-atomic.js +3 -1
  372. package/dist/trash.d.ts.map +1 -1
  373. package/dist/trash.js +54 -8
  374. package/dist/walk.d.ts.map +1 -1
  375. package/dist/walk.js +20 -16
  376. package/dist/windows-owner.d.ts.map +1 -1
  377. package/dist/windows-owner.js +5 -0
  378. package/dist/windows-path-alias.d.ts +39 -0
  379. package/dist/windows-path-alias.d.ts.map +1 -0
  380. package/dist/windows-path-alias.js +153 -0
  381. package/docs/advanced.md +28 -1
  382. package/docs/archive.md +29 -2
  383. package/docs/atomic.md +27 -3
  384. package/docs/contributing.md +4 -1
  385. package/docs/copy.md +4 -1
  386. package/docs/durability.md +18 -2
  387. package/docs/errors.md +14 -6
  388. package/docs/file-store.md +30 -3
  389. package/docs/filename.md +21 -7
  390. package/docs/guest.md +5 -0
  391. package/docs/install-path.md +61 -15
  392. package/docs/install.md +12 -8
  393. package/docs/json-store.md +5 -1
  394. package/docs/json.md +11 -0
  395. package/docs/mutation-policy-proof.md +65 -0
  396. package/docs/native-helper.md +28 -9
  397. package/docs/native.md +46 -10
  398. package/docs/output.md +33 -11
  399. package/docs/path-prefix.md +64 -0
  400. package/docs/path-suffix-aliases.md +159 -0
  401. package/docs/path.md +12 -1
  402. package/docs/permissions.md +26 -1
  403. package/docs/private-file-store.md +14 -0
  404. package/docs/public-api.md +14 -0
  405. package/docs/quickstart.md +1 -1
  406. package/docs/reading.md +8 -1
  407. package/docs/root.md +21 -4
  408. package/docs/secret-file.md +3 -0
  409. package/docs/secure-file.md +21 -16
  410. package/docs/security-model.md +76 -9
  411. package/docs/sidecar-lock.md +28 -1
  412. package/docs/store.md +27 -2
  413. package/docs/temp.md +262 -34
  414. package/docs/test-hooks.md +14 -0
  415. package/docs/writing.md +38 -8
  416. package/package.json +10 -10
@@ -1,14 +1,15 @@
1
1
  import { classifyArchiveParserError } from "./archive-parser-errors.js";
2
2
  import { constants as fsConstants } from "node:fs";
3
3
  import fs from "node:fs/promises";
4
- import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
5
- import { stripArchivePath, validateArchiveEntryPath } from "./archive-entry.js";
4
+ import { ArchiveFormatError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
5
+ import { validateArchiveEntryPath } from "./archive-entry.js";
6
6
  import { createArchiveEntryPlanner } from "./archive-plan.js";
7
7
  import { stageArchiveFileForExtraction } from "./archive-input.js";
8
8
  import { assertArchiveEntryCountWithinLimit, } from "./archive-limits.js";
9
9
  import { prepareArchiveDestinationGuard, withStagedArchiveDestination, } from "./archive-staging.js";
10
10
  import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
11
11
  import { admitZipFile } from "./archive-zip-admission.js";
12
+ import { validateNativeZipManifest } from "./archive-zip-manifest.js";
12
13
  export function throwMappedNativeArchiveError(error) {
13
14
  if (error instanceof Error) {
14
15
  const mapped = classifyArchiveParserError(error.message, { cause: error });
@@ -32,9 +33,9 @@ export async function extractNativeArchive(params) {
32
33
  });
33
34
  try {
34
35
  const zipEntries = [];
35
- const physicalCount = params.kind === "zip"
36
- ? await admitZipFile(stagedArchive.path, limits, params.deadline, (entry) => { zipEntries.push(entry); })
37
- : undefined;
36
+ if (params.kind === "zip") {
37
+ await admitZipFile(stagedArchive.path, limits, params.deadline, (entry) => { zipEntries.push(entry); });
38
+ }
38
39
  const destinationGuard = await prepareArchiveDestinationGuard(params.destDir);
39
40
  const destinationRealDir = destinationGuard.realPath;
40
41
  await withStagedArchiveDestination({
@@ -47,21 +48,8 @@ export async function extractNativeArchive(params) {
47
48
  .catch(throwMappedNativeArchiveError);
48
49
  params.deadline.check();
49
50
  assertArchiveEntryCountWithinLimit(manifest.length, limits);
50
- if (physicalCount !== undefined && manifest.length !== physicalCount) {
51
- throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
52
- }
53
51
  if (params.kind === "zip") {
54
- for (const [ordinal, entry] of manifest.entries()) {
55
- const physical = zipEntries[ordinal];
56
- // Native ZIP indices are physical central-directory ordinals. Legacy
57
- // filename decoding remains native-selected; compare names when known.
58
- if (!physical || physical.index !== ordinal || entry.index !== ordinal ||
59
- entry.size !== physical.size ||
60
- (physical.path !== undefined && stripArchivePath(entry.path, 0) !== stripArchivePath(physical.path, 0)) ||
61
- (physical.creatorSystem === 3 && entry.mode !== physical.externalAttributes >>> 16)) {
62
- throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
63
- }
64
- }
52
+ validateNativeZipManifest(manifest, zipEntries);
65
53
  }
66
54
  // Recheck the native manifest at the shared policy boundary before
67
55
  // any caller callback observes an entry.
@@ -17,7 +17,7 @@ export function createArchiveEntryPlanner(params, archiveKind) {
17
17
  const canonicalPath = stripArchivePath(entry.path, 0);
18
18
  if (!canonicalPath)
19
19
  return null;
20
- const relPath = stripArchivePath(canonicalPath, strip);
20
+ const relPath = strip === 0 ? canonicalPath : stripArchivePath(canonicalPath, strip);
21
21
  if (!relPath)
22
22
  return null;
23
23
  validateArchiveEntryPath(relPath, { escapeLabel: params.escapeLabel });
@@ -1 +1 @@
1
- {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAyOpG,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
1
+ {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA0OpG,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
@@ -1,7 +1,8 @@
1
1
  import { classifyArchiveParserError } from "./archive-parser-errors.js";
2
2
  import fsSync from "node:fs";
3
3
  import fs from "node:fs/promises";
4
- import { Readable } from "node:stream";
4
+ import { Readable, Writable } from "node:stream";
5
+ import { pipeline } from "node:stream/promises";
5
6
  import { readBoundedAsync } from "./bounded-read.js";
6
7
  import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
7
8
  import { formatErrorDetail } from "./error-detail.js";
@@ -18,7 +19,9 @@ import { resolveReadOpenFlags } from "./read-open-flags.js";
18
19
  import { realpathSync } from "./realpath.js";
19
20
  import { getNativeBinding } from "./native.js";
20
21
  import { admitZipBuffer } from "./archive-zip-admission.js";
22
+ import { validateNativeZipManifest } from "./archive-zip-manifest.js";
21
23
  import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
24
+ import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
22
25
  const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
23
26
  const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
24
27
  function canonicalEntryPath(entryPath) {
@@ -32,40 +35,26 @@ function normalizedRequestedEntry(entryPath) {
32
35
  }
33
36
  return normalized;
34
37
  }
35
- async function readStreamBounded(stream, maxBytes) {
36
- if (!(Symbol.asyncIterator in Object(stream))) {
37
- return await new Promise((resolve, reject) => {
38
- const readable = stream;
39
- const chunks = [];
40
- let total = 0;
41
- readable.on("data", (chunk) => {
42
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
43
- total += buffer.length;
44
- if (total > maxBytes) {
45
- readable.pause();
46
- reject(new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT));
47
- return;
48
- }
49
- chunks.push(buffer);
50
- });
51
- readable.once("end", () => resolve(Buffer.concat(chunks, total)));
52
- readable.once("error", reject);
53
- });
54
- }
55
- const chunks = [];
38
+ async function readAdmittedTarPayload(stream, size) {
39
+ // Complete admission has bounded this exact range. Copy as it arrives so
40
+ // decoder chunks can be released before the caller receives the owned result.
41
+ const result = Buffer.allocUnsafe(size);
56
42
  let total = 0;
57
43
  for await (const chunk of stream) {
58
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
59
- total += buffer.length;
60
- if (total > maxBytes) {
61
- throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
44
+ if (chunk.length > size - total) {
45
+ throw new ArchiveFormatError("invalid admitted TAR payload size");
62
46
  }
63
- chunks.push(buffer);
47
+ chunk.copy(result, total);
48
+ total += chunk.length;
64
49
  }
65
- return Buffer.concat(chunks, total);
50
+ if (total !== size)
51
+ throw new ArchiveFormatError("truncated admitted TAR range");
52
+ return result;
66
53
  }
67
54
  async function readArchiveInput(archivePath) {
55
+ assertNoWindowsPathAlias(archivePath);
68
56
  const resolved = realpathSync.native(archivePath);
57
+ assertNoWindowsPathAlias(resolved);
69
58
  const before = await inspectFileIdentity(async () => {
70
59
  const stat = fsSync.lstatSync(archivePath, { bigint: true });
71
60
  if (stat.isSymbolicLink() || !stat.isFile()) {
@@ -104,8 +93,8 @@ async function readArchiveInput(archivePath) {
104
93
  await handle.close().catch(() => undefined);
105
94
  }
106
95
  }
107
- async function readZipEntry(buffer, entryPath, maxBytes, physicalCount) {
108
- const archive = await loadAdmittedZipArchive(buffer, physicalCount);
96
+ async function readZipEntry(buffer, entryPath, maxBytes, admitted) {
97
+ const archive = await loadAdmittedZipArchive(buffer, admitted);
109
98
  let entry;
110
99
  // JSZip keys retain some aliases and may use Unicode Path metadata. Scan the
111
100
  // effective entries once, after raw ZIP admission has rejected collisions.
@@ -124,12 +113,31 @@ async function readZipEntry(buffer, entryPath, maxBytes, physicalCount) {
124
113
  (entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE) {
125
114
  throw new Error(`archive entry is a link: ${formatErrorDetail(entryPath)}`);
126
115
  }
116
+ const integrity = createZipIntegrityTransform(entry);
127
117
  const stream = typeof entry.nodeStream === "function"
128
118
  ? entry.nodeStream()
129
119
  : Readable.from(await entry.async("nodebuffer"));
130
- const integrity = createZipIntegrityTransform(entry);
131
- stream.once("error", (error) => integrity.destroy(normalizeZipIntegrityError(error)));
132
- return await readStreamBounded(stream.pipe(integrity), maxBytes);
120
+ const chunks = [];
121
+ let total = 0;
122
+ const destination = new Writable({
123
+ write(chunk, _encoding, callback) {
124
+ total += chunk.length;
125
+ if (total > maxBytes) {
126
+ callback(new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT));
127
+ return;
128
+ }
129
+ chunks.push(chunk);
130
+ callback();
131
+ },
132
+ });
133
+ try {
134
+ // Iterator teardown can mask limit errors on Node 22; keep them in stream callbacks.
135
+ await pipeline(stream, integrity, destination);
136
+ return Buffer.concat(chunks, total);
137
+ }
138
+ catch (error) {
139
+ throw normalizeZipIntegrityError(error);
140
+ }
133
141
  }
134
142
  async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
135
143
  const seenPaths = new Set();
@@ -162,15 +170,12 @@ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
162
170
  return Buffer.from(archiveBuffer.subarray(selected.offset, end));
163
171
  }
164
172
  let result;
165
- await replayTar({ archiveBuffer, limits, members: [selected], async consume(_member, payload) {
166
- result = await readStreamBounded(payload, maxBytes);
173
+ await replayTar({ archiveBuffer, limits, members: [selected], async consume(member, payload) {
174
+ result = await readAdmittedTarPayload(payload, member.size);
167
175
  } });
168
176
  return result;
169
177
  }
170
- function selectNativeEntry(manifest, requested, displayPath, physicalCount) {
171
- if (physicalCount !== undefined && manifest.length !== physicalCount) {
172
- throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
173
- }
178
+ function selectNativeEntry(manifest, requested, displayPath) {
174
179
  const seen = new Set();
175
180
  let selected;
176
181
  for (const entry of manifest) {
@@ -199,14 +204,17 @@ function throwNativeReadError(error) {
199
204
  }
200
205
  throw error;
201
206
  }
202
- async function readNativeBufferEntry(native, buffer, kind, requested, displayPath, maxBytes, physicalCount) {
207
+ async function readNativeBufferEntry(native, buffer, kind, requested, displayPath, maxBytes, zipEntries) {
203
208
  try {
204
209
  const signal = new AbortController().signal;
205
210
  const limits = resolveTarMeterLimits();
206
211
  const reader = kind === "zip"
207
212
  ? await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]))
208
213
  : await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
209
- const selected = selectNativeEntry(reader.entries, requested, displayPath, physicalCount);
214
+ const manifest = reader.entries;
215
+ if (kind === "zip")
216
+ validateNativeZipManifest(manifest, zipEntries);
217
+ const selected = selectNativeEntry(manifest, requested, displayPath);
210
218
  return await reader.readEntry(selected.index, maxBytes, AbortSignal.any([signal]));
211
219
  }
212
220
  catch (error) {
@@ -222,14 +230,15 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
222
230
  throw new Error(`unsupported archive: ${archivePath}`);
223
231
  }
224
232
  const requestedEntry = normalizedRequestedEntry(entryPath);
233
+ assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
225
234
  const buffer = await readArchiveInput(archivePath);
226
- const physicalCount = kind === "zip"
227
- ? admitZipBuffer(buffer, resolveExtractLimits())
228
- : undefined;
235
+ const zipEntries = [];
236
+ if (kind === "zip")
237
+ admitZipBuffer(buffer, resolveExtractLimits(), entry => { zipEntries.push(entry); });
229
238
  const native = getNativeBinding();
230
239
  if (native)
231
- return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, physicalCount);
240
+ return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, zipEntries);
232
241
  assertPortableArchiveKind(kind);
233
- return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes, physicalCount)
242
+ return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes, zipEntries)
234
243
  : await readTarEntry(buffer, requestedEntry, options.maxBytes);
235
244
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAgB7B,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;AAMrE,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAS1F,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAS9F;AAED,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY9F;AAED,wBAAsB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyCpG;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnF;AA0BD,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB;AAiBD,KAAK,uBAAuB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7F;AAED,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,uBAAuB,EAAE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnE,gBAAgB,CAAC,EAAE,qBAAqB,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAkHD,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Bb;AAED,OAAO,EAAE,iCAAiC,EAAE,MAAM,oBAAoB,CAAC;AAEvE,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}
1
+ {"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAiB7B,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;AAMrE,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAS1F,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAY9F;AAED,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY9F;AAED,wBAAsB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA2CpG;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnF;AA0BD,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhB;AAiBD,KAAK,uBAAuB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAkBF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7F;AAED,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,uBAAuB,EAAE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnE,gBAAgB,CAAC,EAAE,qBAAqB,GACvC,OAAO,CAAC,IAAI,CAAC,CAOf;AAkHD,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CAiCb;AAED,OAAO,EAAE,iCAAiC,EAAE,MAAM,oBAAoB,CAAC;AAEvE,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}
@@ -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":"AAIA,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,CAiCrB"}
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"}