@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,18 +1,76 @@
1
- import fsSync from "node:fs";
1
+ import fsSync, {} from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
4
+ import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { isNotFoundPathError, isPathRelativeEscape } from "./path.js";
7
- import { directoryComponentNotDirectoryError } from "./root-errors.js";
7
+ import { directoryComponentNotDirectoryError, rootPathChangedError } from "./root-errors.js";
8
+ import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
8
9
  import { realpathSync } from "./realpath.js";
9
- function isSameOrChildPath(candidate, parent) {
10
- const parentPrefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`;
11
- return candidate === parent || candidate.startsWith(parentPrefix);
10
+ import { admitPathInsideRoot } from "./root-boundary.js";
11
+ import { checkedMutationDirectory } from "./pinned-mutation-observation.js";
12
+ import { createPathSegmentRoute, joinPathSegmentRoute, } from "./path-segment-route.js";
13
+ function sameDirectoryFacts(left, right) {
14
+ return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode &&
15
+ left.nlink === right.nlink;
16
+ }
17
+ function inspectGuardCurrent(parent) {
18
+ const stat = inspectDirectoryIdentitySync(parent.dir, parent.stat);
19
+ if (realpathSync.native(parent.dir) !== parent.realPath) {
20
+ throw new FsSafeError("path-mismatch", "directory changed during operation");
21
+ }
22
+ return stat;
23
+ }
24
+ function observedGuard(parent) {
25
+ return checkedMutationDirectory(parent.dir, parent.realPath, parent.stat);
26
+ }
27
+ function createdDirectoryEvidence(admission, parent, childPath) {
28
+ try {
29
+ const parentAfter = inspectGuardCurrent(parent);
30
+ const childBefore = inspectDirectoryIdentitySync(childPath);
31
+ const realPath = realpathSync.native(childPath);
32
+ if (realPath !== childPath)
33
+ return undefined;
34
+ const childAfter = inspectDirectoryIdentitySync(childPath, childBefore);
35
+ if (!sameDirectoryFacts(childBefore, childAfter))
36
+ return undefined;
37
+ const child = checkedMutationDirectory(childPath, realPath, childAfter);
38
+ const receipt = Object.freeze({
39
+ admission,
40
+ parent: checkedMutationDirectory(parent.dir, parent.realPath, parentAfter),
41
+ child,
42
+ });
43
+ return Object.freeze({
44
+ receipt,
45
+ guard: Object.freeze({ dir: childPath, realPath, stat: childAfter }),
46
+ });
47
+ }
48
+ catch {
49
+ // Failed optional evidence leaves the existing ordered admission in charge.
50
+ return undefined;
51
+ }
52
+ }
53
+ function sameAbsolutePath(left, right) {
54
+ return path.relative(path.resolve(left), path.resolve(right)) === "";
55
+ }
56
+ function suppliedExactRootIdentity(identity) {
57
+ return typeof identity?.dev === "bigint" && typeof identity.ino === "bigint"
58
+ ? { dev: identity.dev, ino: identity.ino }
59
+ : undefined;
60
+ }
61
+ function assertGuardMatchesRootIdentity(guard, expected) {
62
+ if (guard.stat.dev !== expected.dev || guard.stat.ino !== expected.ino) {
63
+ throw rootPathChangedError();
64
+ }
65
+ }
66
+ function isPromiseLike(value) {
67
+ return typeof value?.then === "function";
12
68
  }
13
69
  async function realpathOrThrowNotFile(target) {
14
70
  try {
15
- return path.resolve(realpathSync.native(target));
71
+ const canonical = realpathSync.native(pathForWindowsFilesystem(target));
72
+ assertNoWindowsPathAlias(canonical, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
73
+ return resolvePathPreservingWindowsRoot(canonical);
16
74
  }
17
75
  catch (error) {
18
76
  if (isNotFoundPathError(error)) {
@@ -30,39 +88,154 @@ async function realpathOrThrowNotFile(target) {
30
88
  * re-deriving it from the original, possibly-symlinked, lexical path.
31
89
  */
32
90
  export async function mkdirPathComponentsWithGuards(params) {
33
- const root = path.resolve(params.rootReal);
34
- const rootCanonical = path.resolve(realpathSync.native(root));
35
- const target = path.resolve(params.targetPath);
36
- const relative = path.relative(root, target);
37
- if (isPathRelativeEscape(relative)) {
91
+ const rawRootReal = params.rootReal;
92
+ assertNoWindowsPathAlias(rawRootReal, "filesystem", "root directory uses a Windows filesystem namespace alias");
93
+ const rawTargetPath = params.targetPath;
94
+ assertNoWindowsPathAlias(rawTargetPath, "filesystem", "target directory uses a Windows filesystem namespace alias");
95
+ const root = resolvePathPreservingWindowsRoot(rawRootReal);
96
+ const target = resolvePathPreservingWindowsRoot(rawTargetPath);
97
+ const suppliedIdentity = suppliedExactRootIdentity(params.rootIdentity);
98
+ const configuredRootGuard = await createAsyncDirectoryGuard(root, { bigint: true });
99
+ const checkedRootIdentity = suppliedIdentity ?? {
100
+ dev: configuredRootGuard.stat.dev,
101
+ ino: configuredRootGuard.stat.ino,
102
+ };
103
+ assertGuardMatchesRootIdentity(configuredRootGuard, checkedRootIdentity);
104
+ assertNoWindowsPathAlias(configuredRootGuard.realPath);
105
+ const rootCanonical = resolvePathPreservingWindowsRoot(configuredRootGuard.realPath);
106
+ const rootGuard = rootCanonical === root
107
+ ? configuredRootGuard
108
+ : await createAsyncDirectoryGuard(rootCanonical, { bigint: true });
109
+ assertGuardMatchesRootIdentity(rootGuard, checkedRootIdentity);
110
+ const admissionParams = {
111
+ candidatePath: target,
112
+ rootIdentity: checkedRootIdentity,
113
+ };
114
+ // Derive the suffix from the caller's trusted root spelling first. The
115
+ // canonical spelling is also accepted after both names have been bound to
116
+ // the same exact root object above.
117
+ const admittedTarget = admitPathInsideRoot({ rootPath: root, ...admissionParams }) ??
118
+ (rootCanonical === root
119
+ ? undefined
120
+ : admitPathInsideRoot({ rootPath: rootCanonical, ...admissionParams }));
121
+ if (!admittedTarget || isPathRelativeEscape(admittedTarget.relativePath)) {
38
122
  throw new FsSafeError("outside-workspace", "directory is outside workspace root");
39
123
  }
40
- let current = root;
41
- for (const part of relative.split(path.sep).filter(Boolean)) {
124
+ let current = rootCanonical;
125
+ let currentGuard = rootGuard;
126
+ const parts = admittedTarget.relativePath.split(path.sep).filter(Boolean);
127
+ let partRoute;
128
+ let retainedTargetPath = params.retainedTargetPath &&
129
+ sameAbsolutePath(path.dirname(params.retainedTargetPath), target)
130
+ ? params.retainedTargetPath
131
+ : undefined;
132
+ let retainedParentPath = retainedTargetPath ? path.dirname(retainedTargetPath) : undefined;
133
+ for (let index = 0; index < parts.length; index += 1) {
134
+ const part = parts[index];
42
135
  const next = path.join(current, part);
43
- const parentGuard = await createAsyncDirectoryGuard(current);
44
- await assertAsyncDirectoryGuard(parentGuard);
136
+ const parentGuard = currentGuard;
137
+ assertNoWindowsPathAlias(parentGuard.realPath, "filesystem", "canonical parent directory uses a Windows filesystem namespace alias");
138
+ let created = false;
139
+ let createReceipt;
140
+ if (!params.revalidateParentAfterBeforeComponent) {
141
+ await assertAsyncDirectoryGuard(parentGuard);
142
+ }
45
143
  await params.beforeComponent?.(next);
46
- params.assertBeforeMutation?.();
47
- try {
48
- await fs.mkdir(next, { mode: params.mode });
144
+ if (params.revalidateParentAfterBeforeComponent) {
145
+ await assertAsyncDirectoryGuard(parentGuard);
49
146
  }
50
- catch (error) {
51
- if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
52
- throw error;
147
+ if (params.beforeCreateComponent) {
148
+ let missing = false;
149
+ try {
150
+ fsSync.lstatSync(next);
151
+ }
152
+ catch (error) {
153
+ if (!isNotFoundPathError(error))
154
+ throw error;
155
+ missing = true;
156
+ }
157
+ if (missing) {
158
+ let prospectiveParent = retainedParentPath;
159
+ if (!prospectiveParent) {
160
+ partRoute ??= createPathSegmentRoute(parts);
161
+ prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
162
+ }
163
+ const authorization = params.beforeCreateComponent(next, prospectiveParent, retainedTargetPath, observedGuard(parentGuard));
164
+ if (isPromiseLike(authorization)) {
165
+ createReceipt = await authorization;
166
+ await assertAsyncDirectoryGuard(parentGuard);
167
+ }
168
+ else {
169
+ createReceipt = authorization;
170
+ // A synchronous authorization must still end at a live pathname
171
+ // fence immediately before the authority callback and mkdir.
172
+ if (!params.synchronousAuthorizationIncludesFence)
173
+ inspectGuardCurrent(parentGuard);
174
+ }
175
+ params.assertBeforeMutation?.();
176
+ // Authority callbacks are an explicit freshness boundary. Recheck the
177
+ // exact parent synchronously so no callback-observed authority is
178
+ // carried into a pathname mkdir after the parent was swapped.
179
+ inspectGuardCurrent(parentGuard);
180
+ try {
181
+ await fs.mkdir(next, { mode: params.mode });
182
+ created = true;
183
+ }
184
+ catch (error) {
185
+ if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
186
+ throw error;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ else {
192
+ params.assertBeforeMutation?.();
193
+ try {
194
+ await fs.mkdir(next, { mode: params.mode });
195
+ }
196
+ catch (error) {
197
+ if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
198
+ throw error;
199
+ }
200
+ }
201
+ }
202
+ let createdAuthorization;
203
+ let createdEvidence;
204
+ if (created && createReceipt && params.afterCreateComponent) {
205
+ createdEvidence = createdDirectoryEvidence(createReceipt, parentGuard, next);
206
+ if (createdEvidence) {
207
+ createdAuthorization = params.afterCreateComponent(createdEvidence.receipt);
53
208
  }
54
209
  }
55
- const stat = fsSync.lstatSync(next);
56
- if ((params.rejectSymlinks && stat.isSymbolicLink()) || (!stat.isSymbolicLink() && !stat.isDirectory())) {
210
+ if (params.beforeUseComponent && !createdAuthorization) {
211
+ let prospectiveParent = retainedParentPath;
212
+ if (!prospectiveParent) {
213
+ partRoute ??= createPathSegmentRoute(parts);
214
+ prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
215
+ }
216
+ await params.beforeUseComponent(next, prospectiveParent, retainedTargetPath);
217
+ await assertAsyncDirectoryGuard(parentGuard);
218
+ }
219
+ const stat = createdAuthorization ? undefined : fsSync.lstatSync(next);
220
+ if (stat && ((params.rejectSymlinks && stat.isSymbolicLink()) ||
221
+ (!stat.isSymbolicLink() && !stat.isDirectory()))) {
57
222
  throw directoryComponentNotDirectoryError();
58
223
  }
59
224
  // Node's recursive mkdir follows symlinks in missing components. Build one
60
225
  // segment at a time and realpath-check each segment before descending.
61
- const nextReal = await realpathOrThrowNotFile(next);
62
- if (!isSameOrChildPath(nextReal, rootCanonical)) {
226
+ const nextReal = createdAuthorization && createdEvidence
227
+ ? createdEvidence.receipt.child.canonicalPath
228
+ : await realpathOrThrowNotFile(next);
229
+ const admittedNextReal = admitPathInsideRoot({
230
+ rootPath: rootCanonical,
231
+ candidatePath: nextReal,
232
+ rootIdentity: checkedRootIdentity,
233
+ });
234
+ if (!admittedNextReal) {
63
235
  throw new FsSafeError("outside-workspace", "directory escaped workspace root");
64
236
  }
65
- if (stat.isSymbolicLink()) {
237
+ const admittedNextPath = admittedNextReal.path;
238
+ if (stat?.isSymbolicLink()) {
66
239
  // An existing path component may legitimately be a symlink to a real
67
240
  // directory inside the root (e.g. a skill-bank layout). We already
68
241
  // verified above that it resolves inside the root, so treat the
@@ -72,18 +245,29 @@ export async function mkdirPathComponentsWithGuards(params) {
72
245
  // every subsequent segment. Callers that need the final directory
73
246
  // (e.g. to guard it themselves after this function returns) must use
74
247
  // the returned resolved path, not their own lexical parent path.
75
- const targetStat = fsSync.statSync(nextReal);
248
+ const targetStat = fsSync.statSync(admittedNextPath);
76
249
  if (!targetStat.isDirectory()) {
77
250
  throw directoryComponentNotDirectoryError();
78
251
  }
79
- await createAsyncDirectoryGuard(nextReal);
252
+ currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
253
+ assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
80
254
  await assertAsyncDirectoryGuard(parentGuard);
81
- current = nextReal;
255
+ retainedTargetPath = undefined;
256
+ retainedParentPath = undefined;
257
+ current = admittedNextPath;
82
258
  continue;
83
259
  }
84
- await createAsyncDirectoryGuard(next);
85
- await assertAsyncDirectoryGuard(parentGuard);
86
- current = next;
260
+ if (createdAuthorization && createdEvidence) {
261
+ currentGuard = createdEvidence.guard;
262
+ }
263
+ else {
264
+ currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
265
+ }
266
+ assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
267
+ if (!createdAuthorization || !createdEvidence) {
268
+ await assertAsyncDirectoryGuard(parentGuard);
269
+ }
270
+ current = admittedNextPath;
87
271
  }
88
272
  return current;
89
273
  }
@@ -1 +1 @@
1
- {"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,8EAA8E;AAC9E,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAE3D,gGAAgG;AAChG,eAAO,MAAM,uBAAuB,QAmZxB,CAAC;AAEb,OAAO,EAAE,yCAAyC,EAAE,CAAC"}
1
+ {"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,8EAA8E;AAC9E,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAE3D,gGAAgG;AAChG,eAAO,MAAM,uBAAuB,QA8ZxB,CAAC;AAEb,OAAO,EAAE,yCAAyC,EAAE,CAAC"}
package/dist/guest.js CHANGED
@@ -57,7 +57,10 @@ export const GUEST_FILESYSTEM_PYTHON = [
57
57
  " except FileNotFoundError:",
58
58
  " if not mkdir_enabled:",
59
59
  " raise",
60
- " os.mkdir(segment, 0o777, dir_fd=current_fd)",
60
+ " try:",
61
+ " os.mkdir(segment, 0o777, dir_fd=current_fd)",
62
+ " except FileExistsError:",
63
+ " pass # The competing entry must still pass the no-follow directory open.",
61
64
  " next_fd = open_dir(segment, dir_fd=current_fd)",
62
65
  " os.close(current_fd)",
63
66
  " current_fd = next_fd",
@@ -356,11 +359,19 @@ export const GUEST_FILESYSTEM_PYTHON = [
356
359
  " return",
357
360
  " if stat.S_ISLNK(src_stat.st_mode):",
358
361
  " link_target = os.readlink(src_basename, dir_fd=src_parent_fd)",
362
+ " temp_dir_name, temp_dir_fd = create_staging_dir(dst_parent_fd)",
359
363
  " try:",
360
- " os.unlink(dst_basename, dir_fd=dst_parent_fd)",
361
- " except FileNotFoundError:",
362
- " pass",
363
- " os.symlink(link_target, dst_basename, dir_fd=dst_parent_fd)",
364
+ " os.symlink(link_target, 'link', dir_fd=temp_dir_fd)",
365
+ " os.replace('link', dst_basename, src_dir_fd=temp_dir_fd, dst_dir_fd=dst_parent_fd)",
366
+ " finally:",
367
+ " try:",
368
+ " try:",
369
+ " os.unlink('link', dir_fd=temp_dir_fd)",
370
+ " except FileNotFoundError:",
371
+ " pass",
372
+ " finally:",
373
+ " os.close(temp_dir_fd)",
374
+ " os.rmdir(temp_dir_name, dir_fd=dst_parent_fd)",
364
375
  " os.unlink(src_basename, dir_fd=src_parent_fd)",
365
376
  " os.fsync(dst_parent_fd)",
366
377
  " os.fsync(src_parent_fd)",
@@ -1 +1 @@
1
- {"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAoBA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAGpB;AAwCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAER;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAaR"}
1
+ {"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAsCA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAOpB;AAwCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAIR;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AA4DD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAoBR"}
package/dist/home-dir.js CHANGED
@@ -1,6 +1,17 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
+ import process from "node:process";
3
4
  import { normalizeOptionalString } from "./string-coerce.js";
5
+ import { assertNoWindowsPathAlias, assertNoWindowsPathAliasForPlatform, repairResolvedWindowsRoot, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
6
+ const PATH_ALIAS_MESSAGE = "path uses a Windows filesystem namespace alias";
7
+ function isOrdinaryRootedWindowsDrivePath(input) {
8
+ const drive = input.charCodeAt(0);
9
+ return input.length >= 3 &&
10
+ ((drive >= 0x41 && drive <= 0x5a) || (drive >= 0x61 && drive <= 0x7a)) &&
11
+ input.charCodeAt(1) === 0x3a &&
12
+ (input.charCodeAt(2) === 0x5c || input.charCodeAt(2) === 0x2f) &&
13
+ input.indexOf(":", 2) === -1;
14
+ }
4
15
  function hasHomePrefix(input) {
5
16
  return input === "~" || input.startsWith("~/") ||
6
17
  (path.sep === "\\" && input.startsWith("~\\"));
@@ -17,7 +28,12 @@ function normalize(value) {
17
28
  }
18
29
  export function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) {
19
30
  const raw = resolveRawHomeDir(env, homedir);
20
- return raw ? path.resolve(raw) : undefined;
31
+ if (!raw)
32
+ return undefined;
33
+ assertNoWindowsPathAlias(raw, "filesystem", "home path uses a Windows filesystem namespace alias");
34
+ const resolved = resolvePathPreservingWindowsRoot(raw);
35
+ assertNoWindowsPathAlias(resolved, "filesystem", "home path uses a Windows filesystem namespace alias");
36
+ return resolved;
21
37
  }
22
38
  function resolveRawHomeDir(env, homedir) {
23
39
  const explicitHome = normalize(env.OPENCLAW_HOME);
@@ -56,7 +72,9 @@ function normalizeSafe(homedir) {
56
72
  }
57
73
  }
58
74
  export function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) {
59
- return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
75
+ const resolved = resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
76
+ assertNoWindowsPathAlias(resolved, "filesystem", "home path uses a Windows filesystem namespace alias");
77
+ return resolved;
60
78
  }
61
79
  export function expandHomePrefix(input, opts) {
62
80
  if (!hasHomePrefix(input)) {
@@ -70,17 +88,62 @@ export function expandHomePrefix(input, opts) {
70
88
  // Expand before normalizing so a following .. traverses the actual home.
71
89
  return path.join(home, input.slice(2));
72
90
  }
73
- export function resolveHomeRelativePath(input, opts) {
74
- if (!input) {
75
- return input;
76
- }
77
- if (!hasHomePrefix(input)) {
78
- return path.resolve(input);
79
- }
91
+ function resolveExpandedHomePath(input, opts) {
80
92
  const expanded = expandHomePrefix(input, {
81
93
  home: resolveRequiredHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir),
82
94
  env: opts?.env,
83
95
  homedir: opts?.homedir,
84
96
  });
85
- return path.resolve(expanded);
97
+ const resolved = resolvePathPreservingWindowsRoot(expanded);
98
+ assertNoWindowsPathAlias(resolved, "filesystem", PATH_ALIAS_MESSAGE);
99
+ return resolved;
100
+ }
101
+ function admitFinalHomePath(resolved, platform) {
102
+ assertNoWindowsPathAliasForPlatform(resolved, "filesystem", PATH_ALIAS_MESSAGE, platform);
103
+ return resolved;
104
+ }
105
+ function finishOrdinaryHomePath(input, resolved) {
106
+ const resolvedPlatform = process.platform;
107
+ if (resolved === input && resolvedPlatform !== undefined)
108
+ return resolved;
109
+ return admitFinalHomePath(resolved, resolvedPlatform);
110
+ }
111
+ function repairAndFinishOrdinaryHomePath(input, resolved) {
112
+ return finishOrdinaryHomePath(input, repairResolvedWindowsRoot(input, resolved));
113
+ }
114
+ function resolveHomePathCold(input, opts, rawPlatform, ordinaryRawAdmitted) {
115
+ if (ordinaryRawAdmitted) {
116
+ return finishOrdinaryHomePath(input, resolvePathPreservingWindowsRoot(input));
117
+ }
118
+ assertNoWindowsPathAliasForPlatform(input, "filesystem", PATH_ALIAS_MESSAGE, rawPlatform);
119
+ if (!hasHomePrefix(input)) {
120
+ const resolved = resolvePathPreservingWindowsRoot(input);
121
+ // This cold branch was not admitted as an ordinary rooted drive. Recheck
122
+ // even an unchanged result because the live platform can change between
123
+ // raw admission and resolved-path admission. Passing undefined preserves
124
+ // the classifier's default process.platform read.
125
+ return admitFinalHomePath(resolved, process.platform);
126
+ }
127
+ return resolveExpandedHomePath(input, opts);
128
+ }
129
+ export function resolveHomeRelativePath(input, opts) {
130
+ if (!input) {
131
+ return input;
132
+ }
133
+ const rawPlatform = process.platform;
134
+ // Primitive ordinary drive paths have exactly one structural colon. Record
135
+ // that admission so an unchanged resolved string does not need rescanning.
136
+ const ordinaryRawAdmitted = rawPlatform === "win32" &&
137
+ typeof input === "string" &&
138
+ isOrdinaryRootedWindowsDrivePath(input);
139
+ // Keep the overwhelmingly common rooted-drive path close to path.resolve
140
+ // itself. Seven-byte inputs retain the general helper's observable
141
+ // namespace-root preflight; a rare six-byte result retains its repair.
142
+ if (!ordinaryRawAdmitted || input.length === 7) {
143
+ return resolveHomePathCold(input, opts, rawPlatform, ordinaryRawAdmitted);
144
+ }
145
+ const resolved = path.resolve(input);
146
+ return resolved.length === 6
147
+ ? repairAndFinishOrdinaryHomePath(input, resolved)
148
+ : finishOrdinaryHomePath(input, resolved);
86
149
  }
@@ -1,5 +1,11 @@
1
1
  export declare function safeDirName(input: string): string;
2
+ /** Legacy readable encoding; distinct IDs can share a result. Use V2 for untrusted IDs. */
2
3
  export declare function safePathSegmentHashed(input: string): string;
4
+ /**
5
+ * Versioned install-ID encoding: every trimmed ID receives a domain-separated
6
+ * SHA-256 digest. Only surrounding whitespace is intentionally equivalent.
7
+ */
8
+ export declare function safePathSegmentHashedV2(input: string): string;
3
9
  export declare function resolveSafeInstallDir(params: {
4
10
  baseDir: string;
5
11
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAe5D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDhB"}
1
+ {"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"AAWA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB3D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA+B5D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhB"}
@@ -3,6 +3,7 @@ import fsSync from "node:fs";
3
3
  import path from "node:path";
4
4
  import { isPathInside } from "./path.js";
5
5
  import { realpathSync } from "./realpath.js";
6
+ import { hasWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
6
7
  export function safeDirName(input) {
7
8
  const trimmed = input.trim();
8
9
  if (!trimmed) {
@@ -10,6 +11,7 @@ export function safeDirName(input) {
10
11
  }
11
12
  return trimmed.replaceAll("/", "__").replaceAll("\\", "__");
12
13
  }
14
+ /** Legacy readable encoding; distinct IDs can share a result. Use V2 for untrusted IDs. */
13
15
  export function safePathSegmentHashed(input) {
14
16
  const trimmed = input.trim();
15
17
  const base = trimmed
@@ -26,11 +28,37 @@ export function safePathSegmentHashed(input) {
26
28
  }
27
29
  return safe;
28
30
  }
31
+ /**
32
+ * Versioned install-ID encoding: every trimmed ID receives a domain-separated
33
+ * SHA-256 digest. Only surrounding whitespace is intentionally equivalent.
34
+ */
35
+ export function safePathSegmentHashedV2(input) {
36
+ const hash = createHash("sha256")
37
+ .update("@openclaw/fs-safe:install-path:v2\0", "utf8")
38
+ // UTF-16LE preserves every JavaScript code unit, including lone surrogates.
39
+ .update(input.trim(), "utf16le")
40
+ .digest("hex");
41
+ return `id-v2-${hash}`;
42
+ }
29
43
  export function resolveSafeInstallDir(params) {
44
+ const baseDir = params.baseDir;
45
+ if (hasWindowsPathAlias(baseDir, "filesystem")) {
46
+ return { ok: false, error: params.invalidNameMessage };
47
+ }
30
48
  const encodedName = (params.nameEncoder ?? safeDirName)(params.id);
31
- const targetDir = path.join(params.baseDir, encodedName);
32
- const resolvedBase = path.resolve(params.baseDir);
49
+ if (hasWindowsPathAlias(encodedName, "relative")) {
50
+ return { ok: false, error: params.invalidNameMessage };
51
+ }
52
+ const targetDir = path.join(baseDir, encodedName);
53
+ if (hasWindowsPathAlias(targetDir, "filesystem")) {
54
+ return { ok: false, error: params.invalidNameMessage };
55
+ }
56
+ const resolvedBase = resolvePathPreservingWindowsRoot(baseDir);
33
57
  const resolvedTarget = path.resolve(targetDir);
58
+ if (hasWindowsPathAlias(resolvedBase, "filesystem") ||
59
+ hasWindowsPathAlias(resolvedTarget, "filesystem")) {
60
+ return { ok: false, error: params.invalidNameMessage };
61
+ }
34
62
  const relative = path.relative(resolvedBase, resolvedTarget);
35
63
  if (!relative ||
36
64
  relative === ".." ||
@@ -41,40 +69,58 @@ export function resolveSafeInstallDir(params) {
41
69
  return { ok: true, path: resolvedTarget };
42
70
  }
43
71
  export async function assertCanonicalPathWithinBase(params) {
44
- const baseDir = path.resolve(params.baseDir);
45
- const candidatePath = path.resolve(params.candidatePath);
72
+ const baseDirInput = params.baseDir;
73
+ const candidatePathInput = params.candidatePath;
74
+ const boundaryLabel = params.boundaryLabel;
75
+ const invalidPath = () => new Error(`Invalid path: must stay within ${boundaryLabel}`);
76
+ const assertAdmittedPath = (value) => {
77
+ if (hasWindowsPathAlias(value, "filesystem"))
78
+ throw invalidPath();
79
+ };
80
+ assertAdmittedPath(baseDirInput);
81
+ assertAdmittedPath(candidatePathInput);
82
+ const baseDir = resolvePathPreservingWindowsRoot(baseDirInput);
83
+ const candidatePath = resolvePathPreservingWindowsRoot(candidatePathInput);
84
+ assertAdmittedPath(baseDir);
85
+ assertAdmittedPath(candidatePath);
46
86
  if (!isPathInside(baseDir, candidatePath)) {
47
- throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
87
+ throw invalidPath();
48
88
  }
49
- const baseLstat = fsSync.lstatSync(baseDir);
89
+ const baseOperationPath = pathForWindowsFilesystem(baseDir);
90
+ const baseLstat = fsSync.lstatSync(baseOperationPath);
50
91
  if (baseLstat.isSymbolicLink()) {
51
- const baseStat = fsSync.statSync(baseDir);
92
+ const baseStat = fsSync.statSync(baseOperationPath);
52
93
  if (!baseStat.isDirectory()) {
53
- throw new Error(`Invalid ${params.boundaryLabel}: base directory must resolve to a directory`);
94
+ throw new Error(`Invalid ${boundaryLabel}: base directory must resolve to a directory`);
54
95
  }
55
96
  }
56
97
  else if (!baseLstat.isDirectory()) {
57
- throw new Error(`Invalid ${params.boundaryLabel}: base directory must be a directory`);
98
+ throw new Error(`Invalid ${boundaryLabel}: base directory must be a directory`);
58
99
  }
59
- const baseRealPath = realpathSync.native(baseDir);
100
+ const baseRealPath = realpathSync.native(baseOperationPath);
101
+ assertAdmittedPath(baseRealPath);
60
102
  const validateDirectory = async (dirPath) => {
61
- const resolvedDirPath = path.resolve(dirPath);
62
- const dirLstat = fsSync.lstatSync(dirPath);
103
+ assertAdmittedPath(dirPath);
104
+ const resolvedDirPath = resolvePathPreservingWindowsRoot(dirPath);
105
+ assertAdmittedPath(resolvedDirPath);
106
+ const operationPath = pathForWindowsFilesystem(dirPath);
107
+ const dirLstat = fsSync.lstatSync(operationPath);
63
108
  if (dirLstat.isSymbolicLink()) {
64
109
  if (resolvedDirPath !== baseDir) {
65
- throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
110
+ throw new Error(`Invalid path: must stay within ${boundaryLabel}`);
66
111
  }
67
- const dirStat = fsSync.statSync(dirPath);
112
+ const dirStat = fsSync.statSync(operationPath);
68
113
  if (!dirStat.isDirectory()) {
69
- throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
114
+ throw new Error(`Invalid path: must stay within ${boundaryLabel}`);
70
115
  }
71
116
  }
72
117
  else if (!dirLstat.isDirectory()) {
73
- throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
118
+ throw new Error(`Invalid path: must stay within ${boundaryLabel}`);
74
119
  }
75
- const dirRealPath = realpathSync.native(dirPath);
120
+ const dirRealPath = realpathSync.native(operationPath);
121
+ assertAdmittedPath(dirRealPath);
76
122
  if (!isPathInside(baseRealPath, dirRealPath)) {
77
- throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
123
+ throw invalidPath();
78
124
  }
79
125
  };
80
126
  try {
@@ -87,5 +133,7 @@ export async function assertCanonicalPathWithinBase(params) {
87
133
  throw err;
88
134
  }
89
135
  }
90
- await validateDirectory(path.dirname(candidatePath));
136
+ const candidateParent = path.dirname(candidatePath);
137
+ assertAdmittedPath(candidateParent);
138
+ await validateDirectory(candidateParent);
91
139
  }
@@ -1,12 +1,20 @@
1
+ import { type BigIntStats } from "node:fs";
1
2
  export type DurableQueueEntryPathsLike = {
2
3
  jsonPath: string;
3
4
  deliveredPath: string;
4
5
  processingPath?: string;
5
6
  };
7
+ export type ValidatedDurableQueueEntryPaths = Readonly<{
8
+ jsonPath: string;
9
+ deliveredPath: string;
10
+ processingPath: string;
11
+ }>;
12
+ export declare function validateDurableQueueEntryPaths(paths: DurableQueueEntryPathsLike): ValidatedDurableQueueEntryPaths;
6
13
  export declare function durableQueueProcessingPath(paths: DurableQueueEntryPathsLike): string;
7
14
  export declare function claimDurableQueueEntry(paths: DurableQueueEntryPathsLike, options?: {
8
15
  skipUnowned?: boolean;
9
16
  }): Promise<string | null>;
17
+ export declare function migrateDurableQueueEntry<T>(paths: DurableQueueEntryPathsLike, expected: BigIntStats, releaseReadPin: () => Promise<void>, run: (filePath: string, beforePublish: () => Promise<void>) => Promise<T>): Promise<T>;
10
18
  export declare function completeDeliveredQueueEntry(paths: DurableQueueEntryPathsLike): Promise<boolean>;
11
19
  export declare function acknowledgeDurableQueueEntry(paths: DurableQueueEntryPathsLike): Promise<void>;
12
20
  export declare function moveDurableQueueEntryToFailed(params: {
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAKpF;AA+DD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI1D"}
1
+ {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAEA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAUnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CAAC;AAUH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,0BAA0B,GAChC,+BAA+B,CAkBjC;AAiBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAIpF;AA0ED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB;AAGD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,KAAK,EAAE,0BAA0B,EACjC,QAAQ,EAAE,WAAW,EACrB,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnC,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACxE,OAAO,CAAC,CAAC,CAAC,CAmCZ;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,OAAO,CAAC,CAclB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDhB;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI1D"}