@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,39 +1,44 @@
1
1
  import fsSync from "node:fs";
2
- import fs from "node:fs/promises";
3
2
  import path from "node:path";
4
- import { formatErrorDetail } from "./error-detail.js";
5
3
  import { FsSafeError } from "./errors.js";
6
- import { assertNoNulPathInput, hasNodeErrorCode, isNodeError, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
7
- import { root as openRoot } from "./root.js";
4
+ import { isNotFoundPathError, isPathInside, } from "./path.js";
8
5
  import { realpathSync } from "./realpath.js";
6
+ import { root as openRoot } from "./root.js";
7
+ import { hasWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
8
+ import { resolvePathWithinRoot } from "./root-paths-lexical.js";
9
+ import { resolvePathWithinNormalizedRoot } from "./path-scope-lexical.js";
10
+ import { assertNoSymlinkSegments, ensureDirectoryWithinRoot, resolveNearestExistingPath, } from "./root-directory.js";
11
+ export { resolvePathWithinRoot } from "./root-paths-lexical.js";
12
+ export { ensureDirectoryWithinRoot } from "./root-directory.js";
9
13
  function invalidPath(scopeLabel) {
10
14
  return {
11
15
  ok: false,
12
16
  error: `Invalid path: must stay within ${scopeLabel}`,
13
17
  };
14
18
  }
15
- function pathStaysWithinRoot(rootDir, candidatePath) {
16
- if (process.platform !== "win32") {
17
- return candidatePath !== rootDir && isPathInside(rootDir, candidatePath);
18
- }
19
- const relative = path.relative(rootDir, candidatePath);
20
- return Boolean(relative) && !isPathRelativeEscape(relative);
21
- }
19
+ const INVALID_REAL_PATH = Symbol("invalid-real-path");
22
20
  async function resolveRealPathIfExists(targetPath) {
21
+ if (hasWindowsPathAlias(targetPath, "filesystem"))
22
+ return INVALID_REAL_PATH;
23
23
  try {
24
- return realpathSync.native(targetPath);
24
+ const realPath = realpathSync.native(pathForWindowsFilesystem(targetPath));
25
+ return hasWindowsPathAlias(realPath, "filesystem") ? INVALID_REAL_PATH : realPath;
25
26
  }
26
27
  catch {
27
28
  return undefined;
28
29
  }
29
30
  }
30
31
  async function resolveTrustedRootRealPath(rootDir) {
32
+ if (hasWindowsPathAlias(rootDir, "filesystem"))
33
+ return undefined;
31
34
  try {
32
- const rootLstat = fsSync.lstatSync(rootDir);
35
+ const operationPath = pathForWindowsFilesystem(rootDir);
36
+ const rootLstat = fsSync.lstatSync(operationPath);
33
37
  if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
34
38
  return undefined;
35
39
  }
36
- return realpathSync.native(rootDir);
40
+ const realPath = realpathSync.native(operationPath);
41
+ return hasWindowsPathAlias(realPath, "filesystem") ? undefined : realPath;
37
42
  }
38
43
  catch {
39
44
  return undefined;
@@ -41,7 +46,8 @@ async function resolveTrustedRootRealPath(rootDir) {
41
46
  }
42
47
  async function validateCanonicalPathWithinRoot(params) {
43
48
  try {
44
- const candidateLstat = fsSync.lstatSync(params.candidatePath);
49
+ const operationPath = pathForWindowsFilesystem(params.candidatePath);
50
+ const candidateLstat = fsSync.lstatSync(operationPath);
45
51
  if (candidateLstat.isSymbolicLink()) {
46
52
  return "invalid";
47
53
  }
@@ -54,41 +60,33 @@ async function validateCanonicalPathWithinRoot(params) {
54
60
  if (params.expect === "file" && candidateLstat.nlink > 1) {
55
61
  return "invalid";
56
62
  }
57
- const candidateRealPath = realpathSync.native(params.candidatePath);
63
+ const candidateRealPath = realpathSync.native(operationPath);
64
+ if (hasWindowsPathAlias(candidateRealPath, "filesystem"))
65
+ return "invalid";
58
66
  return isPathInside(params.rootRealPath, candidateRealPath) ? "ok" : "invalid";
59
67
  }
60
68
  catch (err) {
61
69
  return isNotFoundPathError(err) ? "not-found" : "invalid";
62
70
  }
63
71
  }
64
- export function resolvePathWithinRoot(params) {
65
- const root = path.resolve(params.rootDir);
66
- const raw = params.requestedPath.trim();
67
- if (!raw) {
68
- if (!params.defaultFileName) {
69
- return { ok: false, error: "path is required" };
70
- }
71
- const defaultPath = path.resolve(root, params.defaultFileName);
72
- if (!pathStaysWithinRoot(root, defaultPath)) {
73
- return { ok: false, error: `Invalid path: must stay within ${params.scopeLabel}` };
74
- }
75
- return { ok: true, path: defaultPath };
76
- }
77
- const resolved = path.resolve(root, raw);
78
- if (!pathStaysWithinRoot(root, resolved)) {
79
- return { ok: false, error: `Invalid path: must stay within ${params.scopeLabel}` };
80
- }
81
- return { ok: true, path: resolved };
82
- }
83
72
  export async function resolveWritablePathWithinRoot(params) {
84
- const lexical = resolvePathWithinRoot(params);
73
+ const rootDirInput = params.rootDir;
74
+ const requestedPathInput = params.requestedPath;
75
+ const scopeLabel = params.scopeLabel;
76
+ const defaultFileName = params.defaultFileName;
77
+ const lexical = resolvePathWithinRoot({
78
+ rootDir: rootDirInput,
79
+ requestedPath: requestedPathInput,
80
+ scopeLabel,
81
+ defaultFileName,
82
+ });
85
83
  if (!lexical.ok) {
86
84
  return lexical;
87
85
  }
88
- const rootDir = path.resolve(params.rootDir);
86
+ const rootDir = resolvePathPreservingWindowsRoot(rootDirInput);
89
87
  const rootRealPath = await resolveTrustedRootRealPath(rootDir);
90
88
  if (!rootRealPath) {
91
- return invalidPath(params.scopeLabel);
89
+ return invalidPath(scopeLabel);
92
90
  }
93
91
  const requestedPath = lexical.path;
94
92
  const parentDir = path.dirname(requestedPath);
@@ -98,7 +96,7 @@ export async function resolveWritablePathWithinRoot(params) {
98
96
  expect: "directory",
99
97
  });
100
98
  if (parentStatus !== "ok") {
101
- return invalidPath(params.scopeLabel);
99
+ return invalidPath(scopeLabel);
102
100
  }
103
101
  const targetStatus = await validateCanonicalPathWithinRoot({
104
102
  rootRealPath,
@@ -106,139 +104,33 @@ export async function resolveWritablePathWithinRoot(params) {
106
104
  expect: "file",
107
105
  });
108
106
  if (targetStatus === "invalid") {
109
- return invalidPath(params.scopeLabel);
107
+ return invalidPath(scopeLabel);
110
108
  }
111
109
  return lexical;
112
110
  }
113
- async function resolveNearestExistingPath(targetPath) {
114
- let current = path.resolve(targetPath);
115
- while (true) {
116
- try {
117
- fsSync.lstatSync(current);
118
- return current;
119
- }
120
- catch (err) {
121
- if (!isNotFoundPathError(err)) {
122
- throw err;
123
- }
124
- }
125
- const parent = path.dirname(current);
126
- if (parent === current) {
127
- throw new Error(`failed to resolve existing path for ${targetPath}`);
128
- }
129
- current = parent;
130
- }
131
- }
132
- async function assertNoSymlinkSegments(params) {
133
- const relative = path.relative(params.rootDir, params.targetPath);
134
- if (isPathRelativeEscape(relative)) {
135
- throw new FsSafeError("outside-workspace", `Invalid path: must stay within ${params.scopeLabel}`);
136
- }
137
- let current = params.rootDir;
138
- for (const segment of relative.split(path.sep).filter(Boolean)) {
139
- current = path.join(current, segment);
140
- try {
141
- const stat = fsSync.lstatSync(current);
142
- if (stat.isSymbolicLink()) {
143
- throw new FsSafeError("symlink", `Invalid path: must not traverse symlinks within ${params.scopeLabel}`);
144
- }
145
- if (!stat.isDirectory()) {
146
- throw new FsSafeError("not-file", `Invalid path: existing segment must be a directory within ${params.scopeLabel}`);
147
- }
148
- }
149
- catch (err) {
150
- if (isNotFoundPathError(err)) {
151
- return;
152
- }
153
- throw err;
154
- }
155
- }
156
- }
157
- export async function ensureDirectoryWithinRoot(params) {
158
- const scopeLabel = formatErrorDetail(params.scopeLabel.slice(0, 80));
159
- try {
160
- assertNoNulPathInput(params.rootDir);
161
- assertNoNulPathInput(params.requestedPath);
162
- if (!params.requestedPath.trim())
163
- assertNoNulPathInput(params.defaultDirName ?? "");
164
- const lexical = resolvePathWithinRoot({
165
- ...params, scopeLabel, defaultFileName: params.defaultDirName,
166
- });
167
- if (!lexical.ok)
168
- return lexical;
169
- const rootDir = path.resolve(params.rootDir);
170
- const targetPath = lexical.path;
171
- const rootStat = fsSync.lstatSync(rootDir);
172
- if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
173
- return invalidPath(scopeLabel);
174
- }
175
- await assertNoSymlinkSegments({ rootDir, targetPath, scopeLabel });
176
- const rootReal = realpathSync.native(rootDir);
177
- const nearestExistingPath = await resolveNearestExistingPath(targetPath);
178
- const nearestExistingReal = realpathSync.native(nearestExistingPath);
179
- if (!isPathInside(rootReal, nearestExistingReal)) {
180
- return invalidPath(scopeLabel);
181
- }
182
- const relative = path.relative(rootDir, targetPath);
183
- let current = rootDir;
184
- for (const segment of relative.split(path.sep).filter(Boolean)) {
185
- current = path.join(current, segment);
186
- while (true) {
187
- try {
188
- const stat = fsSync.lstatSync(current);
189
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
190
- return invalidPath(scopeLabel);
191
- }
192
- break;
193
- }
194
- catch (err) {
195
- if (!isNotFoundPathError(err)) {
196
- throw err;
197
- }
198
- try {
199
- await fs.mkdir(current, { mode: params.mode });
200
- }
201
- catch (mkdirErr) {
202
- if (hasNodeErrorCode(mkdirErr, "EEXIST")) {
203
- continue;
204
- }
205
- throw mkdirErr;
206
- }
207
- }
208
- }
209
- const currentReal = realpathSync.native(current);
210
- if (!isPathInside(rootReal, currentReal)) {
211
- return invalidPath(scopeLabel);
212
- }
213
- }
214
- const targetReal = realpathSync.native(targetPath);
215
- if (!isPathInside(rootReal, targetReal)) {
216
- return invalidPath(scopeLabel);
217
- }
218
- return { ok: true, path: targetPath };
219
- }
220
- catch (cause) {
221
- if ((cause instanceof FsSafeError && cause.category === "policy") ||
222
- hasNodeErrorCode(cause, "ENOTDIR")) {
223
- return invalidPath(scopeLabel);
224
- }
225
- // Native messages include unbounded paths; display only bounded errno metadata.
226
- const code = isNodeError(cause) && typeof cause.code === "string"
227
- ? formatErrorDetail(cause.code.slice(0, 40)) : "filesystem operation failed";
228
- const syscall = isNodeError(cause) && typeof cause.syscall === "string"
229
- ? ` during ${formatErrorDetail(cause.syscall.slice(0, 40))}` : "";
230
- const diagnostic = new FsSafeError("helper-failed", `Could not prepare ${scopeLabel}: ${code}${syscall}`, { cause });
231
- return { ok: false, error: diagnostic.message, diagnostic };
232
- }
233
- }
234
111
  export function resolvePathsWithinRoot(params) {
235
112
  const resolvedPaths = [];
113
+ let previousRootDir;
114
+ let previousResolvedRoot = "";
236
115
  for (const raw of params.requestedPaths) {
237
- const pathResult = resolvePathWithinRoot({
116
+ const request = {
238
117
  rootDir: params.rootDir,
239
118
  requestedPath: raw,
240
119
  scopeLabel: params.scopeLabel,
241
- });
120
+ };
121
+ // Each lexical request owns its admitted root without consuming later items.
122
+ if (typeof request.rootDir !== "string")
123
+ path.resolve(request.rootDir);
124
+ if (hasWindowsPathAlias(request.rootDir, "filesystem"))
125
+ return invalidPath(request.scopeLabel);
126
+ // Relative roots may follow cwd changes made by an input iterator or getter.
127
+ const resolvedRoot = process.platform !== "win32" && typeof request.rootDir === "string" &&
128
+ request.rootDir.startsWith("/") && request.rootDir === previousRootDir
129
+ ? previousResolvedRoot
130
+ : resolvePathPreservingWindowsRoot(request.rootDir);
131
+ previousRootDir = request.rootDir;
132
+ previousResolvedRoot = resolvedRoot;
133
+ const pathResult = resolvePathWithinNormalizedRoot(request, resolvedRoot);
242
134
  if (!pathResult.ok) {
243
135
  return { ok: false, error: pathResult.error };
244
136
  }
@@ -253,10 +145,11 @@ export async function resolveStrictExistingPathsWithinRoot(params) {
253
145
  return await resolveCheckedPathsWithinRoot(params, false);
254
146
  }
255
147
  export function pathScope(rootDir, options) {
256
- const base = { rootDir, scopeLabel: options.label };
148
+ const label = options.label;
149
+ const base = { rootDir, scopeLabel: label };
257
150
  return {
258
151
  rootDir,
259
- label: options.label,
152
+ label,
260
153
  resolve: (requestedPath, pathOptions) => resolvePathWithinRoot({
261
154
  ...base,
262
155
  requestedPath,
@@ -288,8 +181,19 @@ export function pathScope(rootDir, options) {
288
181
  };
289
182
  }
290
183
  async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
291
- const rootDir = path.resolve(params.rootDir);
292
- const rootRealPath = await resolveRealPathIfExists(rootDir);
184
+ const rootDirInput = params.rootDir;
185
+ const scopeLabel = params.scopeLabel;
186
+ if (hasWindowsPathAlias(rootDirInput, "filesystem")) {
187
+ return invalidPath(scopeLabel);
188
+ }
189
+ const requestedPaths = [...params.requestedPaths];
190
+ const rootDir = resolvePathPreservingWindowsRoot(rootDirInput);
191
+ if (hasWindowsPathAlias(rootDir, "filesystem"))
192
+ return invalidPath(scopeLabel);
193
+ const rootRealPathResult = await resolveRealPathIfExists(rootDir);
194
+ if (rootRealPathResult === INVALID_REAL_PATH)
195
+ return invalidPath(scopeLabel);
196
+ const rootRealPath = rootRealPathResult;
293
197
  const root = rootRealPath ? await openRoot(rootDir) : undefined;
294
198
  const isInRoot = (relativePath) => Boolean(relativePath) &&
295
199
  relativePath !== ".." &&
@@ -300,7 +204,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
300
204
  const lexicalPathResult = resolvePathWithinRoot({
301
205
  rootDir,
302
206
  requestedPath,
303
- scopeLabel: params.scopeLabel,
207
+ scopeLabel,
304
208
  });
305
209
  if (lexicalPathResult.ok) {
306
210
  return {
@@ -309,11 +213,17 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
309
213
  fallbackPath: lexicalPathResult.path,
310
214
  };
311
215
  }
312
- if (!rootRealPath || !raw || !path.isAbsolute(raw)) {
216
+ if (!rootRealPath ||
217
+ !raw ||
218
+ !path.isAbsolute(raw) ||
219
+ hasWindowsPathAlias(raw, "filesystem")) {
313
220
  return lexicalPathResult;
314
221
  }
315
222
  try {
316
- const resolvedExistingPath = realpathSync.native(raw);
223
+ const resolvedExistingPath = realpathSync.native(pathForWindowsFilesystem(raw));
224
+ if (hasWindowsPathAlias(resolvedExistingPath, "filesystem")) {
225
+ return lexicalPathResult;
226
+ }
317
227
  const relativePath = path.relative(rootRealPath, resolvedExistingPath);
318
228
  if (!isInRoot(relativePath)) {
319
229
  return lexicalPathResult;
@@ -329,7 +239,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
329
239
  }
330
240
  };
331
241
  const resolvedPaths = [];
332
- for (const raw of params.requestedPaths) {
242
+ for (const raw of requestedPaths) {
333
243
  const pathResult = await resolveExistingRelativePath(raw);
334
244
  if (!pathResult.ok) {
335
245
  return { ok: false, error: pathResult.error };
@@ -352,16 +262,17 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
352
262
  await assertNoSymlinkSegments({
353
263
  rootDir,
354
264
  targetPath: pathResult.fallbackPath,
355
- scopeLabel: params.scopeLabel,
265
+ scopeLabel,
356
266
  });
357
267
  const existingPath = await resolveNearestExistingPath(pathResult.fallbackPath);
358
- const existingRealPath = realpathSync.native(existingPath);
359
- if (!isPathInside(rootRealPath, existingRealPath)) {
360
- return invalidPath(params.scopeLabel);
268
+ const existingRealPath = realpathSync.native(pathForWindowsFilesystem(existingPath));
269
+ if (hasWindowsPathAlias(existingRealPath, "filesystem") ||
270
+ !isPathInside(rootRealPath, existingRealPath)) {
271
+ return invalidPath(scopeLabel);
361
272
  }
362
273
  }
363
274
  catch {
364
- return invalidPath(params.scopeLabel);
275
+ return invalidPath(scopeLabel);
365
276
  }
366
277
  resolvedPaths.push(pathResult.fallbackPath);
367
278
  continue;
@@ -369,12 +280,12 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
369
280
  if (err instanceof FsSafeError && err.code === "outside-workspace") {
370
281
  return {
371
282
  ok: false,
372
- error: `File is outside ${params.scopeLabel}`,
283
+ error: `File is outside ${scopeLabel}`,
373
284
  };
374
285
  }
375
286
  return {
376
287
  ok: false,
377
- error: `Invalid path: must stay within ${params.scopeLabel} and be a regular non-symlink file`,
288
+ error: `Invalid path: must stay within ${scopeLabel} and be a regular non-symlink file`,
378
289
  };
379
290
  }
380
291
  finally {
@@ -0,0 +1,26 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import type { FileHandle } from "node:fs/promises";
3
+ import { type RootContext } from "./root-context.js";
4
+ import type { HardlinkPolicy } from "./root-options.js";
5
+ import type { SymlinkPolicy } from "./root-symlink-policy.js";
6
+ export declare function inspectOpenedPathIdentitySync(filePath: string, symlinks: SymlinkPolicy | undefined): BigIntStats;
7
+ type OwnedRootReadHandle = {
8
+ handle: FileHandle;
9
+ stat: {
10
+ nlink: number;
11
+ };
12
+ };
13
+ export declare function admitRootReadHandle<T extends OwnedRootReadHandle>(params: {
14
+ root: RootContext;
15
+ filePath: string;
16
+ opened: T;
17
+ identity: BigIntStats;
18
+ hardlinks?: HardlinkPolicy;
19
+ symlinks?: SymlinkPolicy;
20
+ beforeFinalFence?: (filePath: string, handle: FileHandle) => Promise<void> | void;
21
+ afterPathIdentityCheck?: (filePath: string, handle: FileHandle) => void;
22
+ }): Promise<T & {
23
+ realPath: string;
24
+ }>;
25
+ export {};
26
+ //# sourceMappingURL=root-read-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-read-admission.d.ts","sourceRoot":"","sources":["../src/root-read-admission.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,EAAiC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAmB9D,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,WAAW,CAQb;AA6CD,KAAK,mBAAmB,GAAG;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACzB,CAAC;AAEF,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,mBAAmB,EAAE,MAAM,EAAE;IAC/E,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClF,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CACzE,GAAG,OAAO,CAAC,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA8BpC"}
@@ -0,0 +1,94 @@
1
+ import fsSync, {} from "node:fs";
2
+ import { FsSafeError } from "./errors.js";
3
+ import { openedPathResolutionError, recordOpenedFileFailure } from "./opened-file-failure.js";
4
+ import { isNotFoundPathError } from "./path.js";
5
+ import { admitPathInsideRoot } from "./root-boundary.js";
6
+ import { assertRootIdentityCurrentSync } from "./root-context.js";
7
+ import { fileNotFoundError, hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
10
+ import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
11
+ function resolveFinalOpenedRealPath(filePath) {
12
+ try {
13
+ return realpathSync.native(filePath);
14
+ }
15
+ catch (error) {
16
+ // Windows can report a deleted-but-open file through either resolver
17
+ // failure. Only the same-descriptor unlink proof may consume this receipt.
18
+ if (process.platform === "win32" && error instanceof Error &&
19
+ ["EPERM", "EBADF"].includes(error.code ?? "")) {
20
+ throw openedPathResolutionError(error);
21
+ }
22
+ throw error;
23
+ }
24
+ }
25
+ export function inspectOpenedPathIdentitySync(filePath, symlinks) {
26
+ const stat = symlinks === "follow-within-root"
27
+ ? fsSync.statSync(filePath, { bigint: true })
28
+ : fsSync.lstatSync(filePath, { bigint: true });
29
+ if (stat.isSymbolicLink() && symlinks !== "follow-within-root") {
30
+ throw new FsSafeError("symlink", "symlink not allowed");
31
+ }
32
+ return stat;
33
+ }
34
+ function assertRootReadAdmissionCurrent(params) {
35
+ assertRootIdentityCurrentSync(params.root);
36
+ const admittedPath = (() => {
37
+ try {
38
+ inspectFileIdentitySync(() => inspectOpenedPathIdentitySync(params.filePath, params.symlinks), params.identity);
39
+ const canonicalPath = resolveFinalOpenedRealPath(params.filePath);
40
+ assertNoWindowsPathAlias(canonicalPath, "filesystem", "resolved file path uses a Windows filesystem namespace alias");
41
+ const admittedRealPath = admitPathInsideRoot({
42
+ rootPath: params.root.rootReal,
43
+ candidatePath: canonicalPath,
44
+ rootIdentity: params.root.rootIdentity,
45
+ });
46
+ if (!admittedRealPath) {
47
+ throw openedPathResolutionError(outsideWorkspaceError());
48
+ }
49
+ // This is a fresh no-follow observation even when canonicalPath and
50
+ // filePath have the same spelling; realpath may have raced a replacement.
51
+ inspectFileIdentitySync(() => inspectOpenedPathIdentitySync(canonicalPath, undefined), params.identity);
52
+ return admittedRealPath.path;
53
+ }
54
+ catch (error) {
55
+ throw isNotFoundPathError(error)
56
+ ? openedPathResolutionError(fileNotFoundError())
57
+ : error;
58
+ }
59
+ })();
60
+ params.afterPathIdentityCheck?.(params.filePath, params.handle);
61
+ assertRootIdentityCurrentSync(params.root);
62
+ return admittedPath;
63
+ }
64
+ export async function admitRootReadHandle(params) {
65
+ try {
66
+ if (params.hardlinks !== "allow" && params.opened.stat.nlink > 1) {
67
+ throw hardlinkedPathNotAllowedError();
68
+ }
69
+ if (params.beforeFinalFence) {
70
+ await params.beforeFinalFence(params.filePath, params.opened.handle);
71
+ }
72
+ try {
73
+ const finalRealPath = assertRootReadAdmissionCurrent({
74
+ root: params.root,
75
+ filePath: params.filePath,
76
+ handle: params.opened.handle,
77
+ identity: params.identity,
78
+ symlinks: params.symlinks,
79
+ afterPathIdentityCheck: params.afterPathIdentityCheck,
80
+ });
81
+ return { ...params.opened, realPath: finalRealPath };
82
+ }
83
+ catch (error) {
84
+ if (params.identity.nlink <= 1n) {
85
+ await recordOpenedFileFailure(error, params.opened.handle, params.filePath, params.identity);
86
+ }
87
+ throw error;
88
+ }
89
+ }
90
+ catch (error) {
91
+ await params.opened.handle.close().catch(() => { });
92
+ throw error;
93
+ }
94
+ }
@@ -0,0 +1,15 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ type ExactDirectoryIdentity = Readonly<Pick<BigIntStats, "dev" | "ino">>;
3
+ export type RemovalDirectoryAssertion = Readonly<{
4
+ path: string;
5
+ dev: bigint;
6
+ ino: bigint;
7
+ numericDev?: number;
8
+ numericIno?: number;
9
+ platform: NodeJS.Platform;
10
+ realPath?: string;
11
+ }>;
12
+ export declare function createRemovalDirectoryAssertion(path: string, identity: ExactDirectoryIdentity, realPath?: string, platform?: NodeJS.Platform): RemovalDirectoryAssertion;
13
+ export declare function assertRemovalDirectoryCurrent(assertion: RemovalDirectoryAssertion): void;
14
+ export {};
15
+ //# sourceMappingURL=root-remove-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-remove-identity.d.ts","sourceRoot":"","sources":["../src/root-remove-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAS,MAAM,SAAS,CAAC;AAOlD,KAAK,sBAAsB,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;AAGzE,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAAC;AAaH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,sBAAsB,EAChC,QAAQ,CAAC,EAAE,MAAM,EACjB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,yBAAyB,CAc3B;AA0DD,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,yBAAyB,GAAG,IAAI,CAUxF"}
@@ -0,0 +1,89 @@
1
+ import fsSync from "node:fs";
2
+ import { inspectDirectoryIdentitySync } from "./directory-guard.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { realpathSync } from "./realpath.js";
5
+ import { directoryComponentNotDirectoryError } from "./root-errors.js";
6
+ const MAX_SAFE_DIRECTORY_IDENTITY = BigInt(Number.MAX_SAFE_INTEGER);
7
+ function identityMismatch(cause) {
8
+ return new FsSafeError("path-mismatch", "removal ancestor identity changed or could not be verified", {
9
+ cause: cause instanceof Error ? cause : undefined,
10
+ });
11
+ }
12
+ function safeIdentityNumber(value) {
13
+ if (value < 0n || value > MAX_SAFE_DIRECTORY_IDENTITY)
14
+ return undefined;
15
+ return Number(value);
16
+ }
17
+ export function createRemovalDirectoryAssertion(path, identity, realPath, platform = process.platform) {
18
+ const dev = safeIdentityNumber(identity.dev);
19
+ const ino = safeIdentityNumber(identity.ino);
20
+ const numeric = dev !== undefined && ino !== undefined &&
21
+ (platform !== "win32" || (dev !== 0 && ino !== 0));
22
+ return Object.freeze({
23
+ path,
24
+ dev: identity.dev,
25
+ ino: identity.ino,
26
+ numericDev: numeric ? dev : undefined,
27
+ numericIno: numeric ? ino : undefined,
28
+ platform,
29
+ realPath,
30
+ });
31
+ }
32
+ function assertDirectoryType(stat) {
33
+ if (typeof stat?.isDirectory !== "function" || typeof stat.isSymbolicLink !== "function") {
34
+ throw identityMismatch();
35
+ }
36
+ if (stat.isSymbolicLink() || !stat.isDirectory())
37
+ throw directoryComponentNotDirectoryError();
38
+ }
39
+ function assertExactObservation(stat, expected, platform) {
40
+ assertDirectoryType(stat);
41
+ if (typeof stat.dev !== "bigint" || typeof stat.ino !== "bigint")
42
+ throw identityMismatch();
43
+ if (platform === "win32" && (stat.dev === 0n || stat.ino === 0n))
44
+ throw identityMismatch();
45
+ if (stat.dev !== expected.dev || stat.ino !== expected.ino)
46
+ throw identityMismatch();
47
+ }
48
+ function assertNumericObservation(assertion) {
49
+ const stat = fsSync.lstatSync(assertion.path);
50
+ assertDirectoryType(stat);
51
+ if (!Number.isSafeInteger(stat.dev) || stat.dev < 0 ||
52
+ !Number.isSafeInteger(stat.ino) || stat.ino < 0) {
53
+ throw identityMismatch();
54
+ }
55
+ let unknown = false;
56
+ const observedDev = stat.dev;
57
+ const expectedDev = assertion.numericDev;
58
+ if (assertion.platform === "win32" && observedDev === 0) {
59
+ unknown = true;
60
+ }
61
+ else if (observedDev !== expectedDev) {
62
+ // Reject a known mismatch before considering an unknown companion field.
63
+ throw identityMismatch();
64
+ }
65
+ const observedIno = stat.ino;
66
+ const expectedIno = assertion.numericIno;
67
+ if (assertion.platform === "win32" && observedIno === 0) {
68
+ unknown = true;
69
+ }
70
+ else if (observedIno !== expectedIno) {
71
+ throw identityMismatch();
72
+ }
73
+ if (unknown) {
74
+ // The numeric lstat is the first observation. Permit one exact retry only,
75
+ // retaining every known component and the original exact admission receipt.
76
+ assertExactObservation(fsSync.lstatSync(assertion.path, { bigint: true }), assertion, assertion.platform);
77
+ }
78
+ }
79
+ export function assertRemovalDirectoryCurrent(assertion) {
80
+ if (assertion.numericDev !== undefined && assertion.numericIno !== undefined) {
81
+ assertNumericObservation(assertion);
82
+ }
83
+ else {
84
+ inspectDirectoryIdentitySync(assertion.path, assertion, undefined, assertion.platform);
85
+ }
86
+ if (assertion.realPath !== undefined && realpathSync.native(assertion.path) !== assertion.realPath) {
87
+ throw identityMismatch();
88
+ }
89
+ }
@@ -0,0 +1,17 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ export type CapturedRemovalDirectory = Readonly<{
3
+ path: string;
4
+ stat: BigIntStats;
5
+ }>;
6
+ export declare class RemovalPathReceipts {
7
+ private rootStat;
8
+ private directories;
9
+ readonly observeRoot: (stat: BigIntStats) => void;
10
+ observeDirectory(dir: string, stat: BigIntStats): void;
11
+ complete(rootReal: string, targetPath: string): {
12
+ rootStat: BigIntStats;
13
+ parent: CapturedRemovalDirectory | undefined;
14
+ directories: readonly CapturedRemovalDirectory[];
15
+ } | undefined;
16
+ }
17
+ //# sourceMappingURL=root-remove-receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-remove-receipt.d.ts","sourceRoot":"","sources":["../src/root-remove-receipt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC,CAAC;AAKH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,WAAW,CAAoD;IAEvE,QAAQ,CAAC,WAAW,SAAU,WAAW,KAAG,IAAI,CAA8B;IAE9E,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAIrD;IAED,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG;QAC9C,QAAQ,EAAE,WAAW,CAAC;QACtB,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC;QAC7C,WAAW,EAAE,SAAS,wBAAwB,EAAE,CAAC;KAClD,GAAG,SAAS,CAiBZ;CACF"}