@openclaw/fs-safe 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +12 -7
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +32 -11
  5. package/dist/advanced.d.ts +2 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +2 -0
  8. package/dist/archive-input.d.ts.map +1 -1
  9. package/dist/archive-input.js +6 -2
  10. package/dist/archive-merge.d.ts.map +1 -1
  11. package/dist/archive-merge.js +15 -2
  12. package/dist/archive-native.d.ts.map +1 -1
  13. package/dist/archive-native.js +7 -19
  14. package/dist/archive-plan.js +1 -1
  15. package/dist/archive-read.d.ts.map +1 -1
  16. package/dist/archive-read.js +18 -13
  17. package/dist/archive-staging.d.ts.map +1 -1
  18. package/dist/archive-staging.js +43 -10
  19. package/dist/archive-tar-inspect.d.ts.map +1 -1
  20. package/dist/archive-tar-inspect.js +4 -1
  21. package/dist/archive-zip-admission.d.ts +1 -1
  22. package/dist/archive-zip-admission.d.ts.map +1 -1
  23. package/dist/archive-zip-admission.js +2 -2
  24. package/dist/archive-zip-directory.d.ts +3 -0
  25. package/dist/archive-zip-directory.d.ts.map +1 -1
  26. package/dist/archive-zip-directory.js +13 -2
  27. package/dist/archive-zip-loader.d.ts +3 -2
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +30 -4
  30. package/dist/archive-zip-manifest.d.ts +5 -0
  31. package/dist/archive-zip-manifest.d.ts.map +1 -0
  32. package/dist/archive-zip-manifest.js +22 -0
  33. package/dist/archive-zip-names.d.ts +6 -1
  34. package/dist/archive-zip-names.d.ts.map +1 -1
  35. package/dist/archive-zip-names.js +35 -14
  36. package/dist/archive-zip-preflight.d.ts.map +1 -1
  37. package/dist/archive-zip-preflight.js +3 -2
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -4
  40. package/dist/darwin-acl.d.ts +4 -0
  41. package/dist/darwin-acl.d.ts.map +1 -0
  42. package/dist/darwin-acl.js +24 -0
  43. package/dist/deny-mutations.d.ts.map +1 -1
  44. package/dist/deny-mutations.js +8 -2
  45. package/dist/directory-durability.d.ts.map +1 -1
  46. package/dist/directory-durability.js +67 -23
  47. package/dist/directory-entry-path.d.ts +3 -0
  48. package/dist/directory-entry-path.d.ts.map +1 -0
  49. package/dist/directory-entry-path.js +21 -0
  50. package/dist/directory-guard.d.ts +17 -1
  51. package/dist/directory-guard.d.ts.map +1 -1
  52. package/dist/directory-guard.js +134 -48
  53. package/dist/directory-mode-node.d.ts +12 -0
  54. package/dist/directory-mode-node.d.ts.map +1 -1
  55. package/dist/directory-mode-node.js +102 -4
  56. package/dist/effective-uid.d.ts +2 -0
  57. package/dist/effective-uid.d.ts.map +1 -0
  58. package/dist/effective-uid.js +25 -0
  59. package/dist/file-handle-transfer.d.ts.map +1 -1
  60. package/dist/file-handle-transfer.js +98 -27
  61. package/dist/file-hash.d.ts.map +1 -1
  62. package/dist/file-hash.js +3 -0
  63. package/dist/file-lock-sync.d.ts.map +1 -1
  64. package/dist/file-lock-sync.js +36 -6
  65. package/dist/file-lock.d.ts.map +1 -1
  66. package/dist/file-lock.js +29 -9
  67. package/dist/file-observation.d.ts +1 -1
  68. package/dist/file-observation.d.ts.map +1 -1
  69. package/dist/file-store-boundary.d.ts +6 -2
  70. package/dist/file-store-boundary.d.ts.map +1 -1
  71. package/dist/file-store-boundary.js +20 -65
  72. package/dist/file-store-copy-source.d.ts +5 -0
  73. package/dist/file-store-copy-source.d.ts.map +1 -0
  74. package/dist/file-store-copy-source.js +31 -0
  75. package/dist/file-store-path.d.ts.map +1 -1
  76. package/dist/file-store-path.js +4 -1
  77. package/dist/file-store-sync-directory.d.ts +16 -0
  78. package/dist/file-store-sync-directory.d.ts.map +1 -0
  79. package/dist/file-store-sync-directory.js +349 -0
  80. package/dist/file-store.d.ts.map +1 -1
  81. package/dist/file-store.js +11 -28
  82. package/dist/filename.d.ts.map +1 -1
  83. package/dist/filename.js +65 -20
  84. package/dist/fs.d.ts.map +1 -1
  85. package/dist/fs.js +3 -2
  86. package/dist/guarded-mkdir.d.ts +8 -0
  87. package/dist/guarded-mkdir.d.ts.map +1 -1
  88. package/dist/guarded-mkdir.js +176 -27
  89. package/dist/guest.d.ts.map +1 -1
  90. package/dist/guest.js +4 -1
  91. package/dist/home-dir.d.ts.map +1 -1
  92. package/dist/home-dir.js +73 -10
  93. package/dist/install-path.d.ts.map +1 -1
  94. package/dist/install-path.js +54 -19
  95. package/dist/json-durable-queue-ownership.d.ts +6 -0
  96. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  97. package/dist/json-durable-queue-ownership.js +89 -42
  98. package/dist/json-durable-queue-paths.d.ts +11 -0
  99. package/dist/json-durable-queue-paths.d.ts.map +1 -0
  100. package/dist/json-durable-queue-paths.js +42 -0
  101. package/dist/json-durable-queue-read.d.ts.map +1 -1
  102. package/dist/json-durable-queue-read.js +2 -0
  103. package/dist/json-durable-queue.d.ts.map +1 -1
  104. package/dist/json-durable-queue.js +50 -58
  105. package/dist/json-store.d.ts.map +1 -1
  106. package/dist/json-store.js +5 -1
  107. package/dist/json.d.ts.map +1 -1
  108. package/dist/json.js +54 -22
  109. package/dist/local-file-access.d.ts.map +1 -1
  110. package/dist/local-file-access.js +4 -0
  111. package/dist/local-file-descriptor.d.ts +17 -0
  112. package/dist/local-file-descriptor.d.ts.map +1 -0
  113. package/dist/local-file-descriptor.js +84 -0
  114. package/dist/local-roots.d.ts.map +1 -1
  115. package/dist/local-roots.js +35 -7
  116. package/dist/move-path-cleanup.d.ts +2 -0
  117. package/dist/move-path-cleanup.d.ts.map +1 -1
  118. package/dist/move-path-cleanup.js +44 -18
  119. package/dist/move-path.d.ts.map +1 -1
  120. package/dist/move-path.js +31 -6
  121. package/dist/native-binding.d.ts +14 -0
  122. package/dist/native-binding.d.ts.map +1 -1
  123. package/dist/native-directory-observation.d.ts +17 -0
  124. package/dist/native-directory-observation.d.ts.map +1 -0
  125. package/dist/native-directory-observation.js +37 -0
  126. package/dist/native-parent-admission.d.ts +31 -0
  127. package/dist/native-parent-admission.d.ts.map +1 -0
  128. package/dist/native-parent-admission.js +124 -0
  129. package/dist/native-pinned-write-windows.d.ts +0 -1
  130. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  131. package/dist/native-pinned-write-windows.js +0 -3
  132. package/dist/native-pinned-write.d.ts.map +1 -1
  133. package/dist/native-pinned-write.js +309 -40
  134. package/dist/output.d.ts.map +1 -1
  135. package/dist/output.js +15 -5
  136. package/dist/overwrite-file-handle.d.ts.map +1 -1
  137. package/dist/overwrite-file-handle.js +5 -1
  138. package/dist/owner-dacl.d.ts.map +1 -1
  139. package/dist/owner-dacl.js +2 -0
  140. package/dist/path-policy.d.ts.map +1 -1
  141. package/dist/path-policy.js +7 -2
  142. package/dist/path-prefix.d.ts +7 -0
  143. package/dist/path-prefix.d.ts.map +1 -0
  144. package/dist/path-prefix.js +82 -0
  145. package/dist/path-scope-lexical.d.ts.map +1 -1
  146. package/dist/path-scope-lexical.js +18 -8
  147. package/dist/path-segment-route.d.ts +7 -0
  148. package/dist/path-segment-route.d.ts.map +1 -0
  149. package/dist/path-segment-route.js +24 -0
  150. package/dist/path-suffix-aliases.d.ts +10 -0
  151. package/dist/path-suffix-aliases.d.ts.map +1 -0
  152. package/dist/path-suffix-aliases.js +386 -0
  153. package/dist/path.d.ts.map +1 -1
  154. package/dist/path.js +16 -5
  155. package/dist/permissions-windows.d.ts.map +1 -1
  156. package/dist/permissions-windows.js +14 -3
  157. package/dist/permissions.d.ts.map +1 -1
  158. package/dist/permissions.js +37 -8
  159. package/dist/pinned-mutation-admission.d.ts +25 -0
  160. package/dist/pinned-mutation-admission.d.ts.map +1 -0
  161. package/dist/pinned-mutation-admission.js +425 -0
  162. package/dist/pinned-mutation-observation.d.ts +34 -0
  163. package/dist/pinned-mutation-observation.d.ts.map +1 -0
  164. package/dist/pinned-mutation-observation.js +142 -0
  165. package/dist/pinned-mutation-shared-route.d.ts +24 -0
  166. package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
  167. package/dist/pinned-mutation-shared-route.js +70 -0
  168. package/dist/pinned-open.d.ts +6 -0
  169. package/dist/pinned-open.d.ts.map +1 -1
  170. package/dist/pinned-open.js +28 -10
  171. package/dist/pinned-write-types.d.ts +75 -0
  172. package/dist/pinned-write-types.d.ts.map +1 -0
  173. package/dist/pinned-write-types.js +1 -0
  174. package/dist/pinned-write.d.ts +7 -33
  175. package/dist/pinned-write.d.ts.map +1 -1
  176. package/dist/pinned-write.js +151 -17
  177. package/dist/private-directory.d.ts.map +1 -1
  178. package/dist/private-directory.js +2 -0
  179. package/dist/private-producer-handoff.d.ts +16 -0
  180. package/dist/private-producer-handoff.d.ts.map +1 -0
  181. package/dist/private-producer-handoff.js +272 -0
  182. package/dist/private-temp-workspace.d.ts +2 -39
  183. package/dist/private-temp-workspace.d.ts.map +1 -1
  184. package/dist/private-temp-workspace.js +183 -77
  185. package/dist/publish-file.d.ts.map +1 -1
  186. package/dist/publish-file.js +33 -29
  187. package/dist/regular-file.d.ts.map +1 -1
  188. package/dist/regular-file.js +56 -45
  189. package/dist/replace-directory.d.ts.map +1 -1
  190. package/dist/replace-directory.js +12 -5
  191. package/dist/replace-file-temp-owner.d.ts +1 -0
  192. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  193. package/dist/replace-file-temp-owner.js +12 -1
  194. package/dist/replace-file.d.ts.map +1 -1
  195. package/dist/replace-file.js +24 -24
  196. package/dist/root-boundary.d.ts +4 -0
  197. package/dist/root-boundary.d.ts.map +1 -1
  198. package/dist/root-boundary.js +6 -1
  199. package/dist/root-context.d.ts +12 -1
  200. package/dist/root-context.d.ts.map +1 -1
  201. package/dist/root-context.js +57 -11
  202. package/dist/root-directory-creation.d.ts +13 -0
  203. package/dist/root-directory-creation.d.ts.map +1 -0
  204. package/dist/root-directory-creation.js +212 -0
  205. package/dist/root-directory-list.d.ts +16 -4
  206. package/dist/root-directory-list.d.ts.map +1 -1
  207. package/dist/root-directory-list.js +180 -39
  208. package/dist/root-directory.d.ts +23 -0
  209. package/dist/root-directory.d.ts.map +1 -0
  210. package/dist/root-directory.js +141 -0
  211. package/dist/root-errors.d.ts.map +1 -1
  212. package/dist/root-errors.js +12 -1
  213. package/dist/root-file-final-admission.d.ts +21 -0
  214. package/dist/root-file-final-admission.d.ts.map +1 -0
  215. package/dist/root-file-final-admission.js +83 -0
  216. package/dist/root-file.d.ts.map +1 -1
  217. package/dist/root-file.js +103 -24
  218. package/dist/root-impl.d.ts +1 -0
  219. package/dist/root-impl.d.ts.map +1 -1
  220. package/dist/root-impl.js +289 -317
  221. package/dist/root-move-noreplace.d.ts +14 -0
  222. package/dist/root-move-noreplace.d.ts.map +1 -0
  223. package/dist/root-move-noreplace.js +202 -0
  224. package/dist/root-observed-path.d.ts +9 -0
  225. package/dist/root-observed-path.d.ts.map +1 -0
  226. package/dist/root-observed-path.js +95 -0
  227. package/dist/root-path-errors.d.ts +12 -0
  228. package/dist/root-path-errors.d.ts.map +1 -0
  229. package/dist/root-path-errors.js +13 -0
  230. package/dist/root-path-existing.d.ts.map +1 -1
  231. package/dist/root-path-existing.js +53 -20
  232. package/dist/root-path-observation.d.ts +63 -0
  233. package/dist/root-path-observation.d.ts.map +1 -0
  234. package/dist/root-path-observation.js +180 -0
  235. package/dist/root-path-stat.d.ts +5 -0
  236. package/dist/root-path-stat.d.ts.map +1 -0
  237. package/dist/root-path-stat.js +101 -0
  238. package/dist/root-path-symlink.d.ts.map +1 -1
  239. package/dist/root-path-symlink.js +23 -4
  240. package/dist/root-path.d.ts +11 -0
  241. package/dist/root-path.d.ts.map +1 -1
  242. package/dist/root-path.js +215 -53
  243. package/dist/root-paths-lexical.d.ts +9 -0
  244. package/dist/root-paths-lexical.d.ts.map +1 -0
  245. package/dist/root-paths-lexical.js +22 -0
  246. package/dist/root-paths.d.ts +3 -25
  247. package/dist/root-paths.d.ts.map +1 -1
  248. package/dist/root-paths.js +77 -154
  249. package/dist/root-read-admission.d.ts +26 -0
  250. package/dist/root-read-admission.d.ts.map +1 -0
  251. package/dist/root-read-admission.js +94 -0
  252. package/dist/root-remove-identity.d.ts +15 -0
  253. package/dist/root-remove-identity.d.ts.map +1 -0
  254. package/dist/root-remove-identity.js +89 -0
  255. package/dist/root-remove-receipt.d.ts +17 -0
  256. package/dist/root-remove-receipt.d.ts.map +1 -0
  257. package/dist/root-remove-receipt.js +37 -0
  258. package/dist/root-remove.d.ts +2 -1
  259. package/dist/root-remove.d.ts.map +1 -1
  260. package/dist/root-remove.js +172 -10
  261. package/dist/root-write-admission.d.ts +68 -0
  262. package/dist/root-write-admission.d.ts.map +1 -0
  263. package/dist/root-write-admission.js +322 -0
  264. package/dist/root-write-compatibility.d.ts +13 -0
  265. package/dist/root-write-compatibility.d.ts.map +1 -0
  266. package/dist/root-write-compatibility.js +74 -0
  267. package/dist/root-write-complete-parent.d.ts +42 -0
  268. package/dist/root-write-complete-parent.d.ts.map +1 -0
  269. package/dist/root-write-complete-parent.js +195 -0
  270. package/dist/root-write-publication.d.ts +24 -0
  271. package/dist/root-write-publication.d.ts.map +1 -0
  272. package/dist/root-write-publication.js +85 -0
  273. package/dist/root-write-verification.d.ts.map +1 -1
  274. package/dist/root-write-verification.js +6 -4
  275. package/dist/secret-file.d.ts.map +1 -1
  276. package/dist/secret-file.js +62 -24
  277. package/dist/secret-read-async.d.ts.map +1 -1
  278. package/dist/secret-read-async.js +18 -13
  279. package/dist/secret-read-policy.d.ts.map +1 -1
  280. package/dist/secret-read-policy.js +5 -1
  281. package/dist/secure-file.d.ts.map +1 -1
  282. package/dist/secure-file.js +92 -7
  283. package/dist/secure-temp-dir.d.ts +6 -3
  284. package/dist/secure-temp-dir.d.ts.map +1 -1
  285. package/dist/secure-temp-dir.js +121 -101
  286. package/dist/secure-temp-repair.d.ts +35 -0
  287. package/dist/secure-temp-repair.d.ts.map +1 -0
  288. package/dist/secure-temp-repair.js +106 -0
  289. package/dist/sibling-staged-file.d.ts +3 -2
  290. package/dist/sibling-staged-file.d.ts.map +1 -1
  291. package/dist/sibling-staged-file.js +179 -55
  292. package/dist/sibling-temp.d.ts.map +1 -1
  293. package/dist/sibling-temp.js +27 -13
  294. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  295. package/dist/sidecar-lock-acquire.js +44 -14
  296. package/dist/sidecar-lock-root.d.ts.map +1 -1
  297. package/dist/sidecar-lock-root.js +4 -2
  298. package/dist/staged-directory.d.ts +14 -5
  299. package/dist/staged-directory.d.ts.map +1 -1
  300. package/dist/staged-directory.js +54 -3
  301. package/dist/standalone-publication-path.d.ts +2 -0
  302. package/dist/standalone-publication-path.d.ts.map +1 -0
  303. package/dist/standalone-publication-path.js +6 -0
  304. package/dist/stat-observation.d.ts +11 -0
  305. package/dist/stat-observation.d.ts.map +1 -0
  306. package/dist/stat-observation.js +64 -0
  307. package/dist/strict-file-identity.d.ts.map +1 -1
  308. package/dist/strict-file-identity.js +39 -2
  309. package/dist/symlink-parents.d.ts.map +1 -1
  310. package/dist/symlink-parents.js +7 -2
  311. package/dist/temp-target.d.ts +2 -0
  312. package/dist/temp-target.d.ts.map +1 -1
  313. package/dist/temp-target.js +130 -3
  314. package/dist/temp-workspace-admission.d.ts +22 -0
  315. package/dist/temp-workspace-admission.d.ts.map +1 -0
  316. package/dist/temp-workspace-admission.js +374 -0
  317. package/dist/temp-workspace-child-admission.d.ts +13 -0
  318. package/dist/temp-workspace-child-admission.d.ts.map +1 -0
  319. package/dist/temp-workspace-child-admission.js +95 -0
  320. package/dist/temp-workspace-descriptor.d.ts +48 -0
  321. package/dist/temp-workspace-descriptor.d.ts.map +1 -0
  322. package/dist/temp-workspace-descriptor.js +363 -0
  323. package/dist/temp-workspace-identity.d.ts +15 -0
  324. package/dist/temp-workspace-identity.d.ts.map +1 -0
  325. package/dist/temp-workspace-identity.js +41 -0
  326. package/dist/temp-workspace-owner.d.ts +7 -6
  327. package/dist/temp-workspace-owner.d.ts.map +1 -1
  328. package/dist/temp-workspace-owner.js +121 -61
  329. package/dist/temp-workspace-permissions.d.ts +4 -0
  330. package/dist/temp-workspace-permissions.d.ts.map +1 -0
  331. package/dist/temp-workspace-permissions.js +32 -0
  332. package/dist/temp-workspace-types.d.ts +40 -0
  333. package/dist/temp-workspace-types.d.ts.map +1 -0
  334. package/dist/temp-workspace-types.js +1 -0
  335. package/dist/temp.d.ts +1 -1
  336. package/dist/temp.d.ts.map +1 -1
  337. package/dist/temp.js +1 -1
  338. package/dist/test-hooks.d.ts +7 -0
  339. package/dist/test-hooks.d.ts.map +1 -1
  340. package/dist/text-atomic.d.ts.map +1 -1
  341. package/dist/text-atomic.js +3 -1
  342. package/dist/trash.d.ts.map +1 -1
  343. package/dist/trash.js +54 -8
  344. package/dist/walk.d.ts.map +1 -1
  345. package/dist/walk.js +9 -6
  346. package/dist/windows-owner.d.ts.map +1 -1
  347. package/dist/windows-owner.js +5 -0
  348. package/dist/windows-path-alias.d.ts +39 -0
  349. package/dist/windows-path-alias.d.ts.map +1 -0
  350. package/dist/windows-path-alias.js +153 -0
  351. package/docs/advanced.md +25 -0
  352. package/docs/archive.md +23 -2
  353. package/docs/atomic.md +27 -3
  354. package/docs/copy.md +3 -0
  355. package/docs/durability.md +14 -0
  356. package/docs/errors.md +14 -6
  357. package/docs/file-store.md +24 -3
  358. package/docs/filename.md +14 -7
  359. package/docs/install-path.md +2 -2
  360. package/docs/install.md +8 -7
  361. package/docs/json-store.md +5 -1
  362. package/docs/json.md +11 -0
  363. package/docs/mutation-policy-proof.md +65 -0
  364. package/docs/native-helper.md +26 -9
  365. package/docs/native.md +32 -8
  366. package/docs/output.md +33 -11
  367. package/docs/path-prefix.md +64 -0
  368. package/docs/path-suffix-aliases.md +159 -0
  369. package/docs/path.md +11 -0
  370. package/docs/private-file-store.md +14 -0
  371. package/docs/public-api.md +9 -0
  372. package/docs/quickstart.md +1 -1
  373. package/docs/reading.md +8 -1
  374. package/docs/root.md +18 -3
  375. package/docs/secret-file.md +3 -0
  376. package/docs/secure-file.md +6 -2
  377. package/docs/security-model.md +75 -8
  378. package/docs/sidecar-lock.md +28 -1
  379. package/docs/store.md +5 -1
  380. package/docs/temp.md +260 -36
  381. package/docs/test-hooks.md +14 -0
  382. package/docs/writing.md +38 -8
  383. package/package.json +10 -10
@@ -2,21 +2,27 @@ import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
3
  import { syncDirectory } from "./directory-durability.js";
4
4
  import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
5
- import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, migrateDurableQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
5
+ import { queueValidationRoot, queueValidationRoots, resolveQueueFilesystemPath, } from "./json-durable-queue-paths.js";
6
+ import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, migrateDurableQueueEntry, moveDurableQueueEntryToFailed, validateDurableQueueEntryPaths, } from "./json-durable-queue-ownership.js";
6
7
  import { withJsonDurableQueueEntry } from "./json-durable-queue-read.js";
7
8
  import { stringifyJsonDocument } from "./json-stringify.js";
8
9
  import { realpathSync } from "./realpath.js";
9
10
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
10
11
  import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
11
12
  import { assertSafePathSegment } from "./safe-path-segment.js";
13
+ import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
14
+ import { assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
12
15
  export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
13
16
  function assertSafeQueueEntryId(id) {
14
17
  assertSafePathSegment(id, { label: "queue entry id" });
15
18
  }
16
19
  export async function unlinkBestEffort(filePath) {
20
+ if (hasWindowsPathAlias(filePath, "filesystem"))
21
+ return;
17
22
  await fs.promises.unlink(filePath).catch(() => undefined);
18
23
  }
19
24
  export async function jsonDurableQueueEntryExists(filePath) {
25
+ assertNoWindowsPathAlias(filePath);
20
26
  try {
21
27
  const stat = fs.lstatSync(filePath);
22
28
  return stat.isFile();
@@ -43,6 +49,7 @@ async function unlinkStaleTmpBestEffort(filePath, now, maxAgeMs) {
43
49
  }
44
50
  export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
45
51
  assertSafeQueueEntryId(id);
52
+ assertNoWindowsPathAlias(queueDir);
46
53
  return {
47
54
  jsonPath: path.join(queueDir, `${id}.json`),
48
55
  deliveredPath: path.join(queueDir, `${id}.delivered`),
@@ -50,9 +57,13 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
50
57
  };
51
58
  }
52
59
  export async function ensureJsonDurableQueueDirs(params) {
53
- const roots = await queueValidationRoots(params.queueDir, params.failedDir);
54
- await ensureJsonDurableQueueDir(params.queueDir, roots.queueRoot);
55
- await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
60
+ const queueDir = params.queueDir;
61
+ const failedDir = params.failedDir;
62
+ assertNoWindowsPathAlias(queueDir);
63
+ assertNoWindowsPathAlias(failedDir);
64
+ const roots = await queueValidationRoots(queueDir, failedDir);
65
+ await ensureJsonDurableQueueDir(queueDir, roots.queueRoot);
66
+ await ensureJsonDurableQueueDir(failedDir, roots.failedRoot);
56
67
  }
57
68
  async function ensureJsonDurableQueueDir(dir, validationRoot) {
58
69
  const root = validationRoot
@@ -70,41 +81,6 @@ async function assertJsonDurableQueueDir(dir, validationRoot) {
70
81
  : queueValidationRoot(dir);
71
82
  await assertNoSymlinkDirectorySegments(root, dir, false);
72
83
  }
73
- function commonPathAncestor(paths) {
74
- const resolved = paths.map((entry) => path.resolve(entry));
75
- const root = path.parse(resolved[0] ?? process.cwd()).root;
76
- const parts = resolved.map((entry) => path.relative(root, entry).split(path.sep));
77
- const common = [];
78
- for (let index = 0; parts.every((part) => index < part.length); index++) {
79
- const segment = parts[0]?.[index];
80
- if (!segment || !parts.every((part) => part[index] === segment))
81
- break;
82
- common.push(segment);
83
- }
84
- return path.join(root, ...common);
85
- }
86
- function samePathRoot(paths) {
87
- const roots = paths.map((entry) => path.parse(path.resolve(entry)).root);
88
- const first = process.platform === "win32" ? roots[0]?.toLowerCase() : roots[0];
89
- return roots.every((root) => (process.platform === "win32" ? root.toLowerCase() : root) === first);
90
- }
91
- async function queueValidationRoots(queueDir, failedDir) {
92
- if (samePathRoot([queueDir, failedDir])) {
93
- const common = commonPathAncestor([queueDir, failedDir]);
94
- const root = queueValidationRoot(common);
95
- return { failedRoot: root, queueRoot: root };
96
- }
97
- return {
98
- failedRoot: queueValidationRoot(failedDir),
99
- queueRoot: queueValidationRoot(queueDir),
100
- };
101
- }
102
- function queueValidationRoot(dir) {
103
- return {
104
- path: path.parse(path.resolve(dir)).root,
105
- allowSymlinkBase: process.platform === "darwin",
106
- };
107
- }
108
84
  async function isDarwinSystemAlias(dir, stat) {
109
85
  if (process.platform !== "darwin" || !stat.isSymbolicLink()) {
110
86
  return false;
@@ -121,8 +97,8 @@ async function isDarwinSystemAlias(dir, stat) {
121
97
  }
122
98
  }
123
99
  async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissing) {
124
- let base = path.resolve(validationRoot.path);
125
- let target = path.resolve(dir);
100
+ let base = resolveQueueFilesystemPath(validationRoot.path);
101
+ let target = resolveQueueFilesystemPath(dir);
126
102
  let current = base;
127
103
  let baseStat = fs.lstatSync(base);
128
104
  if (baseStat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
@@ -213,11 +189,14 @@ export async function writeJsonDurableQueueEntry(params) {
213
189
  await writeQueueEntry(params);
214
190
  }
215
191
  async function writeQueueEntry(params, beforeRename) {
192
+ const filePath = admitStandalonePublicationPath(params.filePath);
193
+ const entry = params.entry;
194
+ const tempPrefix = params.tempPrefix;
216
195
  await replaceFileAtomicWithDirectorySync({
217
- filePath: params.filePath,
218
- content: stringifyJsonDocument(params.entry, null, 2),
196
+ filePath,
197
+ content: stringifyJsonDocument(entry, null, 2),
219
198
  mode: 0o600,
220
- tempPrefix: params.tempPrefix,
199
+ tempPrefix,
221
200
  syncTempFile: true,
222
201
  beforeRename,
223
202
  }, syncDirectory);
@@ -235,16 +214,19 @@ export async function ackJsonDurableQueueEntry(paths) {
235
214
  }
236
215
  export async function loadJsonDurableQueueEntry(params) {
237
216
  try {
238
- const claimedPath = await claimDurableQueueEntry(params.paths);
217
+ const pathsInput = params.paths;
218
+ const paths = validateDurableQueueEntryPaths(pathsInput);
219
+ const claimedPath = await claimDurableQueueEntry(paths);
239
220
  if (!claimedPath)
240
221
  return null;
241
222
  return await withJsonDurableQueueEntry(claimedPath, {
242
223
  maxBytes: params.maxBytes,
243
224
  }, async (raw, identity, releaseReadPin) => {
244
- const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
225
+ const read = params.read;
226
+ const result = read ? await Reflect.apply(read, params, [raw, paths.jsonPath]) : { entry: raw };
245
227
  if (result.migrated) {
246
228
  await migrateClaimedQueueEntry({
247
- paths: params.paths, identity, releaseReadPin, entry: result.entry, tempPrefix: params.tempPrefix,
229
+ paths, identity, releaseReadPin, entry: result.entry, tempPrefix: params.tempPrefix,
248
230
  });
249
231
  }
250
232
  return result.entry;
@@ -258,9 +240,11 @@ export async function loadJsonDurableQueueEntry(params) {
258
240
  }
259
241
  }
260
242
  export async function loadPendingJsonDurableQueueEntries(options) {
243
+ const queueDir = options.queueDir;
244
+ assertNoWindowsPathAlias(queueDir);
261
245
  let files;
262
246
  try {
263
- files = await fs.promises.readdir(options.queueDir);
247
+ files = await fs.promises.readdir(queueDir);
264
248
  }
265
249
  catch (error) {
266
250
  if (getErrorCode(error) === "ENOENT") {
@@ -278,10 +262,11 @@ export async function loadPendingJsonDurableQueueEntries(options) {
278
262
  catch {
279
263
  continue;
280
264
  }
281
- await completeDeliveredQueueEntry(resolveJsonDurableQueueEntryPaths(options.queueDir, id));
265
+ const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
266
+ await completeDeliveredQueueEntry(paths);
282
267
  }
283
268
  else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
284
- await unlinkStaleTmpBestEffort(path.join(options.queueDir, file), now, options.cleanupTmpMaxAgeMs);
269
+ await unlinkStaleTmpBestEffort(path.join(queueDir, file), now, options.cleanupTmpMaxAgeMs);
285
270
  }
286
271
  }
287
272
  const ids = [];
@@ -308,14 +293,15 @@ export async function loadPendingJsonDurableQueueEntries(options) {
308
293
  }
309
294
  const entries = [];
310
295
  for (const id of ids) {
311
- const paths = resolveJsonDurableQueueEntryPaths(options.queueDir, id);
296
+ const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
312
297
  const claimedPath = await claimDurableQueueEntry(paths, { skipUnowned: true });
313
298
  if (!claimedPath)
314
299
  continue;
315
300
  let migrationStarted = false;
316
301
  try {
317
302
  const entry = await withJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes }, async (raw, identity, releaseReadPin) => {
318
- const result = options.read ? await options.read(raw, paths.jsonPath) : { entry: raw };
303
+ const read = options.read;
304
+ const result = read ? await Reflect.apply(read, options, [raw, paths.jsonPath]) : { entry: raw };
319
305
  if (result.migrated) {
320
306
  migrationStarted = true;
321
307
  await migrateClaimedQueueEntry({
@@ -335,12 +321,18 @@ export async function loadPendingJsonDurableQueueEntries(options) {
335
321
  return entries;
336
322
  }
337
323
  export async function moveJsonDurableQueueEntryToFailed(params) {
338
- assertSafeQueueEntryId(params.id);
339
- const roots = await queueValidationRoots(params.queueDir, params.failedDir);
340
- await assertJsonDurableQueueDir(params.queueDir, roots.queueRoot);
341
- await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
324
+ const id = params.id;
325
+ const queueDir = params.queueDir;
326
+ const failedDir = params.failedDir;
327
+ assertSafeQueueEntryId(id);
328
+ assertNoWindowsPathAlias(queueDir);
329
+ assertNoWindowsPathAlias(failedDir);
330
+ const roots = await queueValidationRoots(queueDir, failedDir);
331
+ await assertJsonDurableQueueDir(queueDir, roots.queueRoot);
332
+ await ensureJsonDurableQueueDir(failedDir, roots.failedRoot);
333
+ const paths = validateDurableQueueEntryPaths(resolveJsonDurableQueueEntryPaths(queueDir, id));
342
334
  await moveDurableQueueEntryToFailed({
343
- paths: resolveJsonDurableQueueEntryPaths(params.queueDir, params.id),
344
- failedPath: path.join(params.failedDir, `${params.id}.json`),
335
+ paths,
336
+ failedPath: path.join(failedDir, `${id}.json`),
345
337
  });
346
338
  }
@@ -1 +1 @@
1
- {"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../src/json-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,oBAAoB,GAAG;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;AAEtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAQvE"}
1
+ {"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../src/json-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAIlC,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,oBAAoB,GAAG;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;AAEtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAUvE"}
@@ -1,8 +1,12 @@
1
1
  import path from "node:path";
2
2
  import { fileStore } from "./file-store.js";
3
3
  import {} from "./json-document-store.js";
4
+ import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
5
+ import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
4
6
  export function jsonStore(options) {
5
- const filePath = path.resolve(options.filePath);
7
+ const filePathInput = admitStandalonePublicationPath(options.filePath);
8
+ const filePath = path.resolve(filePathInput);
9
+ assertNoWindowsPathAlias(filePath);
6
10
  return fileStore({
7
11
  rootDir: path.dirname(filePath),
8
12
  private: true,
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
package/dist/json.js CHANGED
@@ -9,8 +9,10 @@ import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular
9
9
  import { openRootFileSync } from "./root-file.js";
10
10
  import { resolveReadOpenFlags } from "./read-open-flags.js";
11
11
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
12
+ import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
12
13
  import { writeTextAtomic } from "./text-atomic.js";
13
14
  import { sleep } from "./timing.js";
15
+ import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
14
16
  const READ_RETRY_MAX_ATTEMPTS = 5;
15
17
  const READ_RETRY_BASE_DELAY_MS = 50;
16
18
  function isRetryableReadError(err, options) {
@@ -167,16 +169,17 @@ export function tryReadJsonSync(pathname, options = {}) {
167
169
  }
168
170
  }
169
171
  export function writeJsonSync(pathname, data) {
172
+ const filePath = admitStandalonePublicationPath(pathname);
170
173
  // Keep literal parent segments so staging follows the same symlinks as the target.
171
- const tmpPath = path.format({ ...path.parse(pathname), base: `.fs-safe-${randomUUID()}.tmp` });
174
+ const tmpPath = path.format({ ...path.parse(filePath), base: `.fs-safe-${randomUUID()}.tmp` });
172
175
  const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
173
- fsSync.mkdirSync(recursiveMkdirPath(path.dirname(pathname)), { recursive: true, mode: JSON_DIR_MODE });
176
+ fsSync.mkdirSync(recursiveMkdirPath(path.dirname(filePath)), { recursive: true, mode: JSON_DIR_MODE });
174
177
  try {
175
178
  const tempIdentity = writeTempJsonFile(tmpPath, payload);
176
179
  trySetSecureMode(tmpPath, tempIdentity);
177
- renameJsonFileWithFallback(tmpPath, pathname);
178
- trySetSecureMode(pathname, tempIdentity);
179
- trySyncDirectory(pathname);
180
+ renameJsonFileWithFallback(tmpPath, filePath);
181
+ trySetSecureMode(filePath, tempIdentity);
182
+ trySyncDirectory(filePath);
180
183
  }
181
184
  finally {
182
185
  try {
@@ -207,29 +210,59 @@ function resolveInvalidMessage(invalidMessage, relativePath) {
207
210
  return invalidMessage ?? `${relativePath} has an unexpected shape`;
208
211
  }
209
212
  export function readRootStructuredFileSync(options) {
210
- const absolutePath = path.resolve(options.rootDir, options.relativePath);
213
+ return readRootStructuredFileSyncInternal(options, options);
214
+ }
215
+ function readRootStructuredFileSyncInternal(options, parser) {
216
+ let absolutePath;
217
+ let relativePath;
218
+ let rootDir;
219
+ let rootRealPath;
220
+ try {
221
+ rootDir = options.rootDir;
222
+ relativePath = options.relativePath;
223
+ rootRealPath = options.rootRealPath;
224
+ assertNoWindowsPathAlias(rootDir);
225
+ assertNoWindowsPathAlias(relativePath, "relative");
226
+ if (rootRealPath !== undefined) {
227
+ assertNoWindowsPathAlias(rootRealPath);
228
+ }
229
+ absolutePath = path.resolve(rootDir, relativePath);
230
+ assertNoWindowsPathAlias(absolutePath);
231
+ }
232
+ catch (error) {
233
+ return {
234
+ ok: false,
235
+ reason: "open",
236
+ failure: { ok: false, reason: "validation", error },
237
+ };
238
+ }
239
+ const boundaryLabel = options.boundaryLabel;
240
+ const rejectHardlinks = options.rejectHardlinks;
241
+ const maxBytes = options.maxBytes;
211
242
  const opened = openRootFileSync({
212
243
  absolutePath,
213
- rootPath: options.rootDir,
214
- ...(options.rootRealPath !== undefined ? { rootRealPath: options.rootRealPath } : {}),
215
- boundaryLabel: options.boundaryLabel,
216
- rejectHardlinks: options.rejectHardlinks,
217
- maxBytes: options.maxBytes,
244
+ rootPath: rootDir,
245
+ rootRealPath,
246
+ boundaryLabel,
247
+ rejectHardlinks,
248
+ maxBytes,
218
249
  allowedType: "file",
219
250
  });
220
251
  if (!opened.ok) {
221
252
  return { ok: false, reason: "open", failure: opened };
222
253
  }
223
254
  try {
224
- const raw = options.maxBytes === undefined
255
+ const raw = maxBytes === undefined
225
256
  ? fsSync.readFileSync(opened.fd, "utf8")
226
- : readFileDescriptorBoundedSync(opened.fd, options.maxBytes).toString("utf8");
227
- const parsed = options.parse(raw);
228
- if (options.validate && !options.validate(parsed)) {
257
+ : readFileDescriptorBoundedSync(opened.fd, maxBytes).toString("utf8");
258
+ const parse = parser.parse;
259
+ const parsed = Reflect.apply(parse, parser, [raw]);
260
+ const validate = parser.validate;
261
+ if (validate && !Reflect.apply(validate, parser, [parsed])) {
229
262
  return {
230
263
  ok: false,
231
264
  reason: "invalid",
232
- error: resolveInvalidMessage(options.invalidMessage, options.relativePath),
265
+ error: resolveInvalidMessage(parser.invalidMessage, relativePath),
233
266
  };
234
267
  }
235
268
  return {
@@ -244,7 +277,7 @@ export function readRootStructuredFileSync(options) {
244
277
  return {
245
278
  ok: false,
246
279
  reason: "parse",
247
- error: `failed to parse ${options.relativePath}: ${String(error)}`,
280
+ error: `failed to parse ${relativePath}: ${String(error)}`,
248
281
  };
249
282
  }
250
283
  finally {
@@ -252,14 +285,12 @@ export function readRootStructuredFileSync(options) {
252
285
  }
253
286
  }
254
287
  export function readRootJsonSync(options) {
255
- return readRootStructuredFileSync({
256
- ...options,
288
+ return readRootStructuredFileSyncInternal(options, {
257
289
  parse: (raw) => JSON.parse(raw),
258
290
  });
259
291
  }
260
292
  export function readRootJsonObjectSync(options) {
261
- return readRootStructuredFileSync({
262
- ...options,
293
+ return readRootStructuredFileSyncInternal(options, {
263
294
  parse: (raw) => JSON.parse(raw),
264
295
  validate: isRecord,
265
296
  invalidMessage: (relativePath) => `${relativePath} must contain a JSON object`,
@@ -334,8 +365,9 @@ export function readJsonSync(filePath, options = {}) {
334
365
  }
335
366
  }
336
367
  export async function writeJson(filePath, value, options) {
368
+ const admittedPath = admitStandalonePublicationPath(filePath);
337
369
  const text = stringifyJsonDocument(value, null, 2);
338
- await writeTextAtomic(filePath, text, {
370
+ await writeTextAtomic(admittedPath, text, {
339
371
  mode: options?.mode,
340
372
  dirMode: options?.dirMode,
341
373
  trailingNewline: options?.trailingNewline,
@@ -1 +1 @@
1
- {"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"AAOA,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhD;AAOD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAeT;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAIjF;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAqBR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
1
+ {"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"AAQA,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhD;AAOD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAeT;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAIjF;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAwBR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath, URL } from "node:url";
3
3
  import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
4
+ import { hasWindowsPathAlias } from "./windows-path-alias.js";
4
5
  const ENCODED_FILE_URL_SEPARATOR_RE = /%(?:2f|5c)/i;
5
6
  const FILE_URL_PREFIX_RE = /^file:\/\//i;
6
7
  export function isFileUrl(input) {
@@ -54,6 +55,9 @@ export function safeFileURLToPath(fileUrl, platform = process.platform) {
54
55
  throw new Error(`file:// URLs cannot encode path separators: ${fileUrl}`);
55
56
  }
56
57
  const filePath = fileURLToPath(parsed, { windows: platform === "win32" });
58
+ if (hasWindowsPathAlias(filePath, "filesystem", platform)) {
59
+ throw new Error(`Local file URL cannot use Windows filesystem namespace aliases: ${filePath}`);
60
+ }
57
61
  if (isWindowsNetworkPath(filePath, platform)) {
58
62
  throw new Error(`Local file URL cannot use Windows network paths: ${filePath}`);
59
63
  }
@@ -0,0 +1,17 @@
1
+ import type { BigIntStats, Stats } from "node:fs";
2
+ import type { FileHandle } from "node:fs/promises";
3
+ import type { HardlinkPolicy } from "./root-options.js";
4
+ import type { SymlinkPolicy } from "./root-symlink-policy.js";
5
+ type OwnedLocalFile = {
6
+ handle: FileHandle;
7
+ stat: Stats;
8
+ identity: BigIntStats;
9
+ preOpenStat: BigIntStats | undefined;
10
+ };
11
+ export declare function openLocalFileDescriptor(filePath: string, options?: {
12
+ hardlinks?: HardlinkPolicy;
13
+ symlinks?: SymlinkPolicy;
14
+ readWrite?: true;
15
+ }): Promise<OwnedLocalFile>;
16
+ export {};
17
+ //# sourceMappingURL=local-file-descriptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-file-descriptor.d.ts","sourceRoot":"","sources":["../src/local-file-descriptor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAYnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAO9D,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,KAAK,CAAC;IACZ,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;CACtC,CAAC;AAIF,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,GACA,OAAO,CAAC,cAAc,CAAC,CA0EzB"}
@@ -0,0 +1,84 @@
1
+ import fsSync from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { recordFileOpenFailure, recordPreOpenFileChange } from "./opened-file-failure.js";
5
+ import { assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
6
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
7
+ import { fileNotFoundError, hardlinkedPathNotAllowedError } from "./root-errors.js";
8
+ import { inspectFileIdentity } from "./strict-file-identity.js";
9
+ import { getFsSafeTestHooks } from "./test-hooks.js";
10
+ const OPEN_READ_FLAGS = resolveReadOpenFlags();
11
+ const OPEN_READ_FOLLOW_FLAGS = resolveReadOpenFlags({ followSymlinks: true });
12
+ // Ownership transfers only after descriptor verification. Each admission caller
13
+ // must close on rejection and construct an OpenResult only after admission.
14
+ export async function openLocalFileDescriptor(filePath, options) {
15
+ assertNoUnsafeDeviceReadPath(filePath);
16
+ const fsSafeTestHooks = getFsSafeTestHooks();
17
+ let preOpenStat;
18
+ let observedBeforeOpen = false;
19
+ // Reject directories before opening so we never surface EISDIR to callers (e.g. tool
20
+ // results that get sent to messaging channels). See openclaw/openclaw#31186.
21
+ try {
22
+ preOpenStat = await inspectFileIdentity(() => {
23
+ const stat = fsSync.lstatSync(filePath, { bigint: true });
24
+ observedBeforeOpen = true;
25
+ if (stat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
26
+ throw new FsSafeError("symlink", "symlink not allowed");
27
+ }
28
+ if (!stat.isFile() && !stat.isSymbolicLink()) {
29
+ throw new FsSafeError("not-file", "not a file");
30
+ }
31
+ return stat;
32
+ });
33
+ }
34
+ catch (err) {
35
+ if (err instanceof FsSafeError || observedBeforeOpen) {
36
+ throw err;
37
+ }
38
+ // Only an initial lookup failure falls through; a failed re-inspection aborts.
39
+ }
40
+ if (preOpenStat) {
41
+ await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
42
+ }
43
+ const openFlags = (options?.symlinks === "follow-within-root"
44
+ ? OPEN_READ_FOLLOW_FLAGS
45
+ : OPEN_READ_FLAGS) | (options?.readWrite ? fsSync.constants.O_RDWR : 0);
46
+ await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
47
+ let handle;
48
+ try {
49
+ handle = await fs.open(filePath, openFlags).catch((error) => recordFileOpenFailure(isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
50
+ }
51
+ catch (err) {
52
+ if (isSymlinkOpenError(err)) {
53
+ throw new FsSafeError("symlink", "symlink open blocked", { cause: err });
54
+ }
55
+ // Defensive: if open still throws EISDIR (e.g. race), sanitize so it never leaks.
56
+ if (hasNodeErrorCode(err, "EISDIR")) {
57
+ throw new FsSafeError("not-file", "not a file");
58
+ }
59
+ throw err;
60
+ }
61
+ try {
62
+ await fsSafeTestHooks?.afterOpen?.(filePath, handle);
63
+ const stat = fsSync.fstatSync(handle.fd);
64
+ if (!stat.isFile()) {
65
+ throw new FsSafeError("not-file", "not a file");
66
+ }
67
+ // Keep numeric Stats for the public receipt, never for identity verification.
68
+ let openedIdentity;
69
+ const identity = await inspectFileIdentity(() => (openedIdentity = fsSync.fstatSync(handle.fd, { bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
70
+ if ([0, 1].includes(stat.nlink)) {
71
+ await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
72
+ }
73
+ throw error;
74
+ });
75
+ if (options?.hardlinks === "reject" && stat.nlink > 1) {
76
+ throw hardlinkedPathNotAllowedError();
77
+ }
78
+ return { handle, stat, identity, preOpenStat };
79
+ }
80
+ catch (err) {
81
+ await handle.close().catch(() => { });
82
+ throw err;
83
+ }
84
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAsDF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CAqC7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAiCtC"}
1
+ {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAQ3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAiEF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA2C7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAuCtC"}
@@ -7,6 +7,7 @@ import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
7
7
  import { realpathSync } from "./realpath.js";
8
8
  import { ROOT_PATH_ALIAS_POLICIES, resolveRootPathSync } from "./root-path.js";
9
9
  import { root } from "./root.js";
10
+ import { assertNoWindowsPathAlias, isWindowsPathAliasError, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
10
11
  function resolveLocalPathInput(input, label) {
11
12
  if (isFileUrl(input)) {
12
13
  try {
@@ -20,14 +21,18 @@ function resolveLocalPathInput(input, label) {
20
21
  if (input.includes("\0")) {
21
22
  throw new FsSafeError("invalid-path", `${label} must not contain NUL bytes`);
22
23
  }
24
+ assertNoWindowsPathAlias(input, "filesystem", `${label} uses a Windows filesystem namespace alias`);
23
25
  const homePrefix = input === "~" || input.startsWith("~/") ||
24
26
  (path.sep === "\\" && input.startsWith("~\\"));
25
27
  const expanded = homePrefix ? `${resolveRequiredHomeDir()}${path.sep}${input.slice(2)}` : input;
28
+ assertNoWindowsPathAlias(expanded, "filesystem", `${label} uses a Windows filesystem namespace alias`);
26
29
  if (path.isAbsolute(expanded))
27
30
  return expanded;
28
31
  const drive = path.parse(expanded).root;
29
32
  const base = drive ? path.resolve(drive) : process.cwd();
30
- return `${base}${path.sep}${expanded.slice(drive.length)}`;
33
+ const resolved = `${base}${path.sep}${expanded.slice(drive.length)}`;
34
+ assertNoWindowsPathAlias(resolved, "filesystem", `${label} uses a Windows filesystem namespace alias`);
35
+ return resolved;
31
36
  }
32
37
  function resolveLocalRootInput(input, label) {
33
38
  const trimmed = input.trim();
@@ -40,29 +45,44 @@ function resolveLocalRootInput(input, label) {
40
45
  if (resolved.includes("\0")) {
41
46
  throw new FsSafeError("invalid-path", `${label} entry must not contain NUL bytes`);
42
47
  }
48
+ assertNoWindowsPathAlias(resolved, "filesystem", `${label} entry uses a Windows filesystem namespace alias`);
43
49
  if (!path.isAbsolute(resolved)) {
44
50
  throw new FsSafeError("invalid-path", `${label} entries must be absolute paths: ${input}`);
45
51
  }
46
- return path.resolve(resolved);
52
+ const absolute = resolvePathPreservingWindowsRoot(resolved);
53
+ assertNoWindowsPathAlias(absolute, "filesystem", `${label} entry uses a Windows filesystem namespace alias`);
54
+ return absolute;
47
55
  }
48
56
  function resolveRootRealSync(rootDir) {
57
+ let realPath;
49
58
  try {
50
59
  // Configured roots may themselves be symlinks. Follow only this trusted
51
60
  // root entry, then use its canonical directory for containment checks.
52
- const stat = fsSync.statSync(rootDir);
61
+ const operationPath = pathForWindowsFilesystem(rootDir);
62
+ const stat = fsSync.statSync(operationPath);
53
63
  if (!stat.isDirectory()) {
54
64
  return null;
55
65
  }
56
- return realpathSync(rootDir);
66
+ realPath = realpathSync(operationPath);
57
67
  }
58
68
  catch {
59
69
  return null;
60
70
  }
71
+ assertNoWindowsPathAlias(realPath, "filesystem", "local root uses a Windows filesystem namespace alias");
72
+ return realPath;
61
73
  }
62
74
  export function resolveLocalPathFromRootsSync(options) {
63
75
  const label = options.label ?? "local roots";
64
- const requestedPath = resolveLocalPathInput(options.filePath, "file path");
65
76
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
77
+ let requestedPath;
78
+ try {
79
+ requestedPath = resolveLocalPathInput(options.filePath, "file path");
80
+ }
81
+ catch (error) {
82
+ if (isWindowsPathAliasError(error))
83
+ return null;
84
+ throw error;
85
+ }
66
86
  for (const rootDir of rootDirs) {
67
87
  const rootReal = resolveRootRealSync(rootDir);
68
88
  if (!rootReal) {
@@ -89,7 +109,7 @@ export function resolveLocalPathFromRootsSync(options) {
89
109
  continue;
90
110
  if (candidate.exists && options.requireFile === true) {
91
111
  try {
92
- if (!fsSync.lstatSync(requestedPath).isFile())
112
+ if (!fsSync.lstatSync(pathForWindowsFilesystem(requestedPath)).isFile())
93
113
  continue;
94
114
  }
95
115
  catch {
@@ -103,8 +123,16 @@ export function resolveLocalPathFromRootsSync(options) {
103
123
  export async function readLocalFileFromRoots(options) {
104
124
  const maxBytes = normalizeMaxBytes(options.maxBytes);
105
125
  const label = options.label ?? "local roots";
106
- const requestedPath = resolveLocalPathInput(options.filePath, "file path");
107
126
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
127
+ let requestedPath;
128
+ try {
129
+ requestedPath = resolveLocalPathInput(options.filePath, "file path");
130
+ }
131
+ catch (error) {
132
+ if (isWindowsPathAliasError(error))
133
+ return null;
134
+ throw error;
135
+ }
108
136
  for (const rootDir of rootDirs) {
109
137
  let scopedRoot;
110
138
  try {
@@ -1,3 +1,4 @@
1
+ import fsSync from "node:fs";
1
2
  export type EntryIdentity = {
2
3
  ctimeMs: number;
3
4
  dev: number;
@@ -12,6 +13,7 @@ export type CopiedEntryManifest = (EntryIdentity & {
12
13
  name: string;
13
14
  manifest: CopiedEntryManifest;
14
15
  }>;
16
+ directoryIdentity: Readonly<Pick<fsSync.BigIntStats, "dev" | "ino">>;
15
17
  kind: "directory";
16
18
  }) | (EntryIdentity & {
17
19
  kind: "leaf";