@openclaw/fs-safe 0.15.0 → 0.17.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 (311) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +36 -7
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +2 -8
  5. package/dist/advanced.d.ts +3 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +3 -0
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +22 -23
  10. package/dist/archive-kind.d.ts +0 -1
  11. package/dist/archive-kind.d.ts.map +1 -1
  12. package/dist/archive-kind.js +5 -17
  13. package/dist/archive-merge.d.ts +1 -0
  14. package/dist/archive-merge.d.ts.map +1 -1
  15. package/dist/archive-merge.js +4 -4
  16. package/dist/archive-native.d.ts +1 -0
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +1 -0
  19. package/dist/archive-options.d.ts +2 -0
  20. package/dist/archive-options.d.ts.map +1 -1
  21. package/dist/archive-parser.wasm +0 -0
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +6 -7
  24. package/dist/archive-tar-stream.d.ts +3 -0
  25. package/dist/archive-tar-stream.d.ts.map +1 -1
  26. package/dist/archive-tar-stream.js +56 -37
  27. package/dist/archive-tar-wasm.d.ts +16 -4
  28. package/dist/archive-tar-wasm.d.ts.map +1 -1
  29. package/dist/archive-tar-wasm.js +134 -34
  30. package/dist/archive-zip-count.d.ts.map +1 -1
  31. package/dist/archive-zip-count.js +21 -1
  32. package/dist/archive-zip-directory.d.ts.map +1 -1
  33. package/dist/archive-zip-directory.js +23 -1
  34. package/dist/archive-zip-loader.d.ts +2 -0
  35. package/dist/archive-zip-loader.d.ts.map +1 -1
  36. package/dist/archive-zip-loader.js +7 -0
  37. package/dist/archive-zip-names.d.ts.map +1 -1
  38. package/dist/archive-zip-names.js +7 -2
  39. package/dist/archive.d.ts.map +1 -1
  40. package/dist/archive.js +14 -9
  41. package/dist/byte-view.d.ts +3 -0
  42. package/dist/byte-view.d.ts.map +1 -0
  43. package/dist/byte-view.js +13 -0
  44. package/dist/clone-metadata.d.ts +1 -0
  45. package/dist/clone-metadata.d.ts.map +1 -1
  46. package/dist/clone-metadata.js +6 -2
  47. package/dist/create-directory.d.ts +20 -0
  48. package/dist/create-directory.d.ts.map +1 -0
  49. package/dist/create-directory.js +130 -0
  50. package/dist/create-file-async.d.ts +7 -0
  51. package/dist/create-file-async.d.ts.map +1 -0
  52. package/dist/create-file-async.js +121 -0
  53. package/dist/create-file.d.ts +8 -0
  54. package/dist/create-file.d.ts.map +1 -0
  55. package/dist/create-file.js +190 -0
  56. package/dist/create-owned-file.d.ts +8 -0
  57. package/dist/create-owned-file.d.ts.map +1 -0
  58. package/dist/create-owned-file.js +16 -0
  59. package/dist/create.d.ts +4 -0
  60. package/dist/create.d.ts.map +1 -0
  61. package/dist/create.js +2 -0
  62. package/dist/creation-darwin.d.ts +6 -0
  63. package/dist/creation-darwin.d.ts.map +1 -0
  64. package/dist/creation-darwin.js +70 -0
  65. package/dist/creation-file-state.d.ts +19 -0
  66. package/dist/creation-file-state.d.ts.map +1 -0
  67. package/dist/creation-file-state.js +118 -0
  68. package/dist/creation-path.d.ts +21 -0
  69. package/dist/creation-path.d.ts.map +1 -0
  70. package/dist/creation-path.js +71 -0
  71. package/dist/creation-permissions.d.ts +19 -0
  72. package/dist/creation-permissions.d.ts.map +1 -0
  73. package/dist/creation-permissions.js +125 -0
  74. package/dist/directory-durability.d.ts +7 -7
  75. package/dist/directory-durability.d.ts.map +1 -1
  76. package/dist/directory-durability.js +22 -80
  77. package/dist/directory-guard.d.ts +3 -0
  78. package/dist/directory-guard.d.ts.map +1 -1
  79. package/dist/directory-mode-node.d.ts +2 -0
  80. package/dist/directory-mode-node.d.ts.map +1 -1
  81. package/dist/directory-mode-node.js +8 -0
  82. package/dist/directory-receipt.d.ts +24 -0
  83. package/dist/directory-receipt.d.ts.map +1 -0
  84. package/dist/directory-receipt.js +123 -0
  85. package/dist/file-cleanup.d.ts +20 -0
  86. package/dist/file-cleanup.d.ts.map +1 -0
  87. package/dist/file-cleanup.js +81 -0
  88. package/dist/file-contents.d.ts +6 -0
  89. package/dist/file-contents.d.ts.map +1 -0
  90. package/dist/file-contents.js +40 -0
  91. package/dist/file-hash.d.ts.map +1 -1
  92. package/dist/file-hash.js +16 -4
  93. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
  94. package/dist/file-lock-sync-root-acquire.js +3 -0
  95. package/dist/file-lock-sync-root-held.d.ts +1 -2
  96. package/dist/file-lock-sync-root-held.d.ts.map +1 -1
  97. package/dist/file-lock-sync-root-held.js +7 -5
  98. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
  99. package/dist/file-lock-sync-stale-admission.js +3 -0
  100. package/dist/file-lock-sync.d.ts.map +1 -1
  101. package/dist/file-lock-sync.js +8 -11
  102. package/dist/file-observation.d.ts +1 -1
  103. package/dist/file-observation.d.ts.map +1 -1
  104. package/dist/file-store-boundary.d.ts +2 -6
  105. package/dist/file-store-boundary.d.ts.map +1 -1
  106. package/dist/file-store-boundary.js +3 -9
  107. package/dist/file-store-sync-write.d.ts.map +1 -1
  108. package/dist/file-store-sync-write.js +2 -5
  109. package/dist/file-store.js +3 -3
  110. package/dist/guarded-mkdir.d.ts +1 -0
  111. package/dist/guarded-mkdir.d.ts.map +1 -1
  112. package/dist/guarded-mkdir.js +27 -19
  113. package/dist/install-path.d.ts.map +1 -1
  114. package/dist/install-path.js +2 -5
  115. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  116. package/dist/json-durable-queue-ownership.js +2 -6
  117. package/dist/json-durable-queue-paths.d.ts.map +1 -1
  118. package/dist/json-durable-queue-paths.js +2 -24
  119. package/dist/json-durable-queue.d.ts.map +1 -1
  120. package/dist/json-durable-queue.js +10 -9
  121. package/dist/json.d.ts.map +1 -1
  122. package/dist/json.js +32 -75
  123. package/dist/local-roots.d.ts.map +1 -1
  124. package/dist/local-roots.js +19 -21
  125. package/dist/move-path-cleanup.d.ts +5 -19
  126. package/dist/move-path-cleanup.d.ts.map +1 -1
  127. package/dist/move-path-cleanup.js +57 -21
  128. package/dist/move-path.d.ts.map +1 -1
  129. package/dist/move-path.js +63 -40
  130. package/dist/native-binding.d.ts +11 -1
  131. package/dist/native-binding.d.ts.map +1 -1
  132. package/dist/native-fallback-warning.d.ts +4 -0
  133. package/dist/native-fallback-warning.d.ts.map +1 -0
  134. package/dist/native-fallback-warning.js +11 -0
  135. package/dist/native-operations.d.ts +0 -2
  136. package/dist/native-operations.d.ts.map +1 -1
  137. package/dist/native-operations.js +0 -24
  138. package/dist/native-parent-admission.d.ts +2 -0
  139. package/dist/native-parent-admission.d.ts.map +1 -1
  140. package/dist/native-parent-admission.js +3 -2
  141. package/dist/native-pinned-write-windows.d.ts +1 -1
  142. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  143. package/dist/native-pinned-write-windows.js +173 -28
  144. package/dist/native-pinned-write.d.ts.map +1 -1
  145. package/dist/native-pinned-write.js +19 -3
  146. package/dist/native-policy-parent-windows.d.ts.map +1 -1
  147. package/dist/native-policy-parent-windows.js +15 -6
  148. package/dist/native-staged-file.d.ts +5 -3
  149. package/dist/native-staged-file.d.ts.map +1 -1
  150. package/dist/native-staged-file.js +90 -40
  151. package/dist/native.js +2 -2
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +11 -2
  154. package/dist/owner-dacl.d.ts.map +1 -1
  155. package/dist/owner-dacl.js +10 -4
  156. package/dist/path.d.ts.map +1 -1
  157. package/dist/path.js +2 -1
  158. package/dist/permissions.d.ts.map +1 -1
  159. package/dist/permissions.js +3 -17
  160. package/dist/pinned-write-input.d.ts +4 -0
  161. package/dist/pinned-write-input.d.ts.map +1 -0
  162. package/dist/pinned-write-input.js +35 -0
  163. package/dist/pinned-write-mode.d.ts +5 -0
  164. package/dist/pinned-write-mode.d.ts.map +1 -0
  165. package/dist/pinned-write-mode.js +31 -0
  166. package/dist/pinned-write-staged.d.ts +6 -0
  167. package/dist/pinned-write-staged.d.ts.map +1 -0
  168. package/dist/pinned-write-staged.js +186 -0
  169. package/dist/pinned-write-types.d.ts +3 -0
  170. package/dist/pinned-write-types.d.ts.map +1 -1
  171. package/dist/pinned-write.d.ts.map +1 -1
  172. package/dist/pinned-write.js +41 -147
  173. package/dist/private-directory.d.ts.map +1 -1
  174. package/dist/private-directory.js +18 -4
  175. package/dist/private-producer-handoff-sync.d.ts +14 -0
  176. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  177. package/dist/private-producer-handoff-sync.js +114 -0
  178. package/dist/private-producer-handoff.d.ts +22 -4
  179. package/dist/private-producer-handoff.d.ts.map +1 -1
  180. package/dist/private-producer-handoff.js +140 -77
  181. package/dist/publish-copy-stage.d.ts +2 -1
  182. package/dist/publish-copy-stage.d.ts.map +1 -1
  183. package/dist/publish-copy-stage.js +16 -7
  184. package/dist/publish-file.d.ts +2 -2
  185. package/dist/publish-file.d.ts.map +1 -1
  186. package/dist/publish-file.js +58 -98
  187. package/dist/regular-file.d.ts.map +1 -1
  188. package/dist/regular-file.js +35 -44
  189. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  190. package/dist/replace-file-copy-fallback.js +28 -26
  191. package/dist/replace-file-copy-source.d.ts.map +1 -1
  192. package/dist/replace-file-copy-source.js +13 -22
  193. package/dist/replace-file-descriptor.d.ts.map +1 -1
  194. package/dist/replace-file-descriptor.js +10 -16
  195. package/dist/replace-file-temp-owner.d.ts +0 -7
  196. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  197. package/dist/replace-file-temp-owner.js +9 -60
  198. package/dist/replace-file.d.ts.map +1 -1
  199. package/dist/replace-file.js +9 -13
  200. package/dist/root-create-input.d.ts +2 -1
  201. package/dist/root-create-input.d.ts.map +1 -1
  202. package/dist/root-create-input.js +13 -4
  203. package/dist/root-directory-creation.d.ts +3 -3
  204. package/dist/root-directory-creation.d.ts.map +1 -1
  205. package/dist/root-directory-creation.js +15 -3
  206. package/dist/root-directory-list.d.ts.map +1 -1
  207. package/dist/root-directory-list.js +20 -3
  208. package/dist/root-file-final-admission.d.ts +1 -1
  209. package/dist/root-file-final-admission.d.ts.map +1 -1
  210. package/dist/root-file-final-admission.js +5 -2
  211. package/dist/root-file.d.ts.map +1 -1
  212. package/dist/root-file.js +3 -2
  213. package/dist/root-impl.d.ts.map +1 -1
  214. package/dist/root-impl.js +78 -27
  215. package/dist/root-move-noreplace.d.ts +2 -0
  216. package/dist/root-move-noreplace.d.ts.map +1 -1
  217. package/dist/root-move-noreplace.js +22 -13
  218. package/dist/root-options.d.ts +12 -4
  219. package/dist/root-options.d.ts.map +1 -1
  220. package/dist/root-path-stat.d.ts.map +1 -1
  221. package/dist/root-path-stat.js +59 -7
  222. package/dist/root-read-admission.d.ts.map +1 -1
  223. package/dist/root-read-admission.js +7 -2
  224. package/dist/root-remove.d.ts.map +1 -1
  225. package/dist/root-remove.js +15 -1
  226. package/dist/root-write-publication.js +1 -1
  227. package/dist/secret-file.d.ts.map +1 -1
  228. package/dist/secret-file.js +1 -0
  229. package/dist/secure-file-windows.d.ts +6 -0
  230. package/dist/secure-file-windows.d.ts.map +1 -1
  231. package/dist/secure-file-windows.js +34 -117
  232. package/dist/secure-file.js +2 -2
  233. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  234. package/dist/sidecar-lock-acquire.js +4 -6
  235. package/dist/sidecar-lock-handle.d.ts +3 -0
  236. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  237. package/dist/sidecar-lock-handle.js +6 -0
  238. package/dist/sidecar-lock-reclaim.d.ts +1 -1
  239. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  240. package/dist/sidecar-lock-reclaim.js +11 -8
  241. package/dist/sidecar-lock-root.d.ts.map +1 -1
  242. package/dist/sidecar-lock-root.js +2 -1
  243. package/dist/sidecar-lock.d.ts.map +1 -1
  244. package/dist/sidecar-lock.js +3 -5
  245. package/dist/staged-directory.d.ts +2 -2
  246. package/dist/staged-directory.d.ts.map +1 -1
  247. package/dist/staged-directory.js +6 -6
  248. package/dist/staged-file-settlement.d.ts +17 -0
  249. package/dist/staged-file-settlement.d.ts.map +1 -0
  250. package/dist/staged-file-settlement.js +57 -0
  251. package/dist/strict-file-identity.d.ts +1 -1
  252. package/dist/strict-file-identity.d.ts.map +1 -1
  253. package/dist/strict-file-identity.js +9 -9
  254. package/dist/symlink-parents.d.ts.map +1 -1
  255. package/dist/symlink-parents.js +2 -27
  256. package/dist/temp-workspace-owner.js +4 -4
  257. package/dist/unicode-path.d.ts.map +1 -1
  258. package/dist/unicode-path.js +3 -0
  259. package/dist/walk.d.ts.map +1 -1
  260. package/dist/walk.js +4 -2
  261. package/dist/windows-owner.d.ts.map +1 -1
  262. package/dist/windows-owner.js +2 -1
  263. package/dist/windows-security-bridge.cs +336 -0
  264. package/dist/windows-security-bridge.ps1 +15 -0
  265. package/dist/windows-security-command.d.ts +26 -0
  266. package/dist/windows-security-command.d.ts.map +1 -0
  267. package/dist/windows-security-command.js +363 -0
  268. package/dist/windows-security-facts.d.ts +6 -0
  269. package/dist/windows-security-facts.d.ts.map +1 -0
  270. package/dist/windows-security-facts.js +108 -0
  271. package/dist/write-file-handle.d.ts +7 -0
  272. package/dist/write-file-handle.d.ts.map +1 -1
  273. package/dist/write-file-handle.js +23 -0
  274. package/dist/write-open-flags.d.ts.map +1 -1
  275. package/dist/write-open-flags.js +1 -8
  276. package/dist/write-queue.d.ts.map +1 -1
  277. package/dist/write-queue.js +1 -4
  278. package/docs/advanced.md +71 -2
  279. package/docs/archive.md +102 -39
  280. package/docs/atomic.md +29 -5
  281. package/docs/config.md +6 -2
  282. package/docs/contributing.md +48 -4
  283. package/docs/copy.md +2 -0
  284. package/docs/creation.md +132 -0
  285. package/docs/durability.md +59 -0
  286. package/docs/file-contents.md +68 -0
  287. package/docs/install.md +31 -7
  288. package/docs/json.md +5 -4
  289. package/docs/local-roots.md +2 -0
  290. package/docs/migrating-to-0.5.md +15 -6
  291. package/docs/migrating-to-0.6.md +9 -4
  292. package/docs/mutation-policy-proof.md +5 -3
  293. package/docs/native-helper.md +22 -9
  294. package/docs/native.md +47 -15
  295. package/docs/path.md +4 -4
  296. package/docs/permissions.md +37 -14
  297. package/docs/public-api.md +5 -0
  298. package/docs/quickstart.md +1 -1
  299. package/docs/reading.md +2 -2
  300. package/docs/regular-file.md +3 -0
  301. package/docs/root.md +43 -0
  302. package/docs/secret-file.md +11 -2
  303. package/docs/secure-file.md +9 -4
  304. package/docs/sidecar-lock.md +14 -5
  305. package/docs/staged-file.md +9 -3
  306. package/docs/store.md +3 -1
  307. package/docs/temp.md +4 -1
  308. package/docs/types.md +18 -2
  309. package/docs/walk.md +7 -0
  310. package/docs/writing.md +80 -7
  311. package/package.json +18 -15
@@ -50,19 +50,22 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
+ import { assertDarwinCreationAcl } from "./creation-darwin.js";
54
+ import { assertPrivateCreationFile } from "./creation-file-state.js";
53
55
  import { requireNativeBinding } from "./native.js";
54
56
  import { syncFileBestEffortSync } from "./file-sync.js";
55
57
  import { randomUUID } from "node:crypto";
56
- import fs from "node:fs";
58
+ import fs, {} from "node:fs";
57
59
  import path from "node:path";
58
60
  import { FsSafeError } from "./errors.js";
59
- import { MutationAuthorityError } from "./mutation-authority.js";
61
+ import { assertSynchronousCallbackResult, MutationAuthorityError } from "./mutation-authority.js";
60
62
  import { captureNativeFdClose } from "./native-binding.js";
61
- import { writeNativeInput } from "./native-operations.js";
63
+ import { writePinnedInput } from "./pinned-write-input.js";
62
64
  import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
63
65
  import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
64
66
  import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
65
67
  import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
68
+ import { createStagedFileReceipt, stagedFileFailure as failure } from "./staged-file-settlement.js";
66
69
  export function assertNativeStaging(binding) {
67
70
  if ([
68
71
  binding.closeOwnedFd,
@@ -82,12 +85,6 @@ function assertBasename(name, portable) {
82
85
  }
83
86
  }
84
87
  const NOT_PUBLISHED = Object.freeze({ status: "not-published" });
85
- function failure(error, details) {
86
- const code = error instanceof FsSafeError
87
- ? error.code
88
- : error?.code === "EEXIST" ? "already-exists" : "helper-failed";
89
- return new FsSafeError(code, `staged file ${details.phase} failed`, { cause: error, details });
90
- }
91
88
  class NativeStagedFile {
92
89
  #binding;
93
90
  #closeFd;
@@ -97,12 +94,15 @@ class NativeStagedFile {
97
94
  #portableNames;
98
95
  #publishedMode;
99
96
  #sync;
97
+ #strictFileSync;
98
+ #private;
99
+ #verifyMode;
100
100
  #assertBeforeMutation;
101
101
  #name;
102
102
  #state = { status: "open", publication: NOT_PUBLISHED };
103
103
  #receipt;
104
104
  #rejectFinalSymlink = false;
105
- constructor(binding, parentFd, closeParentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation, name = `.fs-safe-${randomUUID()}.tmp`) {
105
+ constructor(binding, parentFd, closeParentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation, name = `.fs-safe-${randomUUID()}.tmp`, strictFileSync = false, permissionPolicy) {
106
106
  assertBasename(name, portableNames);
107
107
  this.#name = name;
108
108
  this.#closeFd = captureNativeFdClose(binding);
@@ -113,16 +113,19 @@ class NativeStagedFile {
113
113
  this.#portableNames = portableNames;
114
114
  this.#publishedMode = publishedMode;
115
115
  this.#sync = sync;
116
+ this.#strictFileSync = strictFileSync;
117
+ this.#private = permissionPolicy === "private-creation";
118
+ this.#verifyMode = permissionPolicy !== undefined;
116
119
  this.#assertBeforeMutation = assertBeforeMutation;
117
120
  }
118
121
  // Public staging supplies a Node parent; pinned writes supply a native parent.
119
122
  // The supplied closer owns parentFd even on construction or preparation failure.
120
123
  static async create(binding, parentFd, closeParentFd, directory, input, mode, maxBytes,
121
124
  // Public staging uses portable names; existing POSIX writes accept literal names.
122
- portableNames = true, sync = true, assertBeforeMutation, exclusiveBasename) {
125
+ portableNames = true, sync = true, assertBeforeMutation, exclusiveBasename, strictFileSync = false, permissionPolicy) {
123
126
  let staged;
124
127
  try {
125
- staged = new NativeStagedFile(binding, parentFd, closeParentFd, directory, portableNames, mode, sync, assertBeforeMutation, exclusiveBasename);
128
+ staged = new NativeStagedFile(binding, parentFd, closeParentFd, directory, portableNames, mode, sync, assertBeforeMutation, exclusiveBasename, strictFileSync, permissionPolicy);
126
129
  }
127
130
  catch (error) {
128
131
  try {
@@ -142,19 +145,29 @@ class NativeStagedFile {
142
145
  const exclusive = params.overwrite === false && params.input.kind === "buffer" && params.input.stageBeforePublish === false;
143
146
  // This owner never escapes. Only the internal verifier borrows its fd;
144
147
  // public descriptor methods remain await-free and cannot race disposal.
145
- const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, closeParentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync, params.assertBeforeMutation, exclusive ? params.basename : undefined), true);
148
+ const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, closeParentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync, params.assertBeforeMutation, exclusive ? params.basename : undefined, params.strictFileSync, params.private ? "private-creation" : params.verifyPosixMode === true ? "mode-only" : undefined), true);
146
149
  staged.#rejectFinalSymlink = params.rejectFinalSymlink === true;
147
150
  if (params.input.kind === "file")
148
151
  await params.input.verifySource();
149
152
  if (exclusive) {
150
153
  staged.#assertCurrent();
151
154
  params.assertBeforeMutation?.();
155
+ if (staged.#verifyMode)
156
+ staged.#assertCurrent();
152
157
  }
153
158
  const published = exclusive
154
159
  ? staged.#completePublication(params.basename, false, params.onPublished)
155
160
  : await staged.publish(params.basename, { overwrite: params.overwrite !== false }, params.onPublished);
156
161
  const identity = published.staged.identity;
157
- await params.verifyPublished?.(staged.#file(), identity, parentGuard);
162
+ try {
163
+ await params.verifyPublished?.(staged.#file(), identity, parentGuard);
164
+ }
165
+ catch (error) {
166
+ if (params.overwrite === false && params.input.kind !== "file" && params.input.stageBeforePublish === true) {
167
+ throw failure(error, { phase: "publish", publication: published });
168
+ }
169
+ throw error;
170
+ }
158
171
  return { dev: identity.dev, ino: identity.ino };
159
172
  }
160
173
  catch (e_1) {
@@ -186,13 +199,14 @@ class NativeStagedFile {
186
199
  }
187
200
  return state.fileFd;
188
201
  }
189
- #assertNamed(name) {
202
+ #assertNamed(name, expectedMode) {
190
203
  const fd = this.#file();
191
204
  const stat = this.#binding.stagedFileMatches(this.#parentFd, name, fd)
192
205
  ? fs.fstatSync(fd, { bigint: true }) : undefined;
193
206
  if (!stat || stat.nlink !== 1n) {
194
207
  throw new FsSafeError("path-mismatch", "staged entry no longer names the exclusive created file");
195
208
  }
209
+ this.#assertPermissions(fd, stat, expectedMode);
196
210
  return Number(stat.mode & 4095n);
197
211
  }
198
212
  #assertCurrent() {
@@ -200,7 +214,17 @@ class NativeStagedFile {
200
214
  throw new FsSafeError("helper-failed", "staged file publication has already been attempted");
201
215
  }
202
216
  assertStagedDirectoryCurrent(this.#directory);
203
- this.#assertNamed(this.#name);
217
+ this.#assertNamed(this.#name, 0o600);
218
+ }
219
+ #assertPermissions(fd, stat, expectedMode) {
220
+ if (this.#private)
221
+ assertPrivateCreationFile(stat, fd);
222
+ if (this.#verifyMode && Number(stat.mode & 4095n) !== expectedMode) {
223
+ throw new FsSafeError("insecure-permissions", "filesystem did not enforce the staged file mode");
224
+ }
225
+ }
226
+ #assertStagePermissions(fd) {
227
+ this.#assertPermissions(fd, fs.fstatSync(fd, { bigint: true }), 0o600);
204
228
  }
205
229
  async #prepare(input, maxBytes) {
206
230
  try {
@@ -208,44 +232,63 @@ class NativeStagedFile {
208
232
  if (!Number.isInteger(mode) || mode < 0 || mode > 0o7777) {
209
233
  throw new FsSafeError("invalid-path", "invalid staged file mode");
210
234
  }
235
+ const fileSignal = input.kind === "file" && this.#verifyMode ? input.signal : undefined;
236
+ if (input.kind === "file" && this.#verifyMode) {
237
+ fileSignal?.throwIfAborted();
238
+ if (input.clone === "always") {
239
+ throw new FsSafeError("helper-unavailable", "required cloning cannot verify staged permissions before copying");
240
+ }
241
+ }
211
242
  assertStagedDirectoryCurrent(this.#directory);
212
243
  // The exclusive open performs no fallible post-open checks. Store its fd
213
244
  // before every subsequent operation, including the first metadata read.
214
245
  const state = this.#open();
246
+ if (this.#private)
247
+ assertDarwinCreationAcl(this.#parentFd, "file");
215
248
  this.#assertBeforeMutation?.();
216
- const copied = input.kind === "file"
249
+ if (this.#private)
250
+ assertDarwinCreationAcl(this.#parentFd, "file");
251
+ // Native copying populates its new file before returning its descriptor.
252
+ const copied = input.kind === "file" && !this.#verifyMode
217
253
  ? await createNativeCopyFile(this.#binding, input, this.#parentFd, this.#name, maxBytes, false)
218
254
  : undefined;
219
255
  state.fileFd = copied?.fd;
220
256
  if (state.fileFd === undefined) {
221
257
  this.#assertBeforeMutation?.();
258
+ if (this.#private)
259
+ assertDarwinCreationAcl(this.#parentFd, "file");
222
260
  state.fileFd = this.#binding.createStagedFile(this.#parentFd, this.#name);
223
261
  }
224
262
  const fd = state.fileFd;
225
263
  if (input.kind === "file")
226
264
  assertNativeCopyCompleted(input, copied);
227
- this.#assertBeforeMutation?.();
265
+ const assertBeforeChmod = this.#assertBeforeMutation;
266
+ const beforeChmodResult = assertBeforeChmod?.();
267
+ if (this.#private) {
268
+ assertSynchronousCallbackResult(beforeChmodResult, "assertBeforeMutation");
269
+ assertPrivateCreationFile(fs.fstatSync(fd, { bigint: true }), fd);
270
+ }
228
271
  fs.fchmodSync(fd, 0o600);
272
+ // Some filesystems report successful chmod without enforcing its mode.
273
+ if (this.#verifyMode)
274
+ this.#assertStagePermissions(fd);
275
+ const assertBeforeMutation = this.#verifyMode ? () => {
276
+ assertSynchronousCallbackResult(this.#assertBeforeMutation?.(), "assertBeforeMutation");
277
+ fileSignal?.throwIfAborted();
278
+ this.#assertStagePermissions(fd);
279
+ } : this.#assertBeforeMutation;
229
280
  if (!copied)
230
- await writeNativeInput(fd, input, maxBytes, this.#assertBeforeMutation);
231
- if (this.#sync)
232
- syncFileBestEffortSync(fd);
281
+ await writePinnedInput(fd, input, maxBytes, assertBeforeMutation);
282
+ if (this.#verifyMode)
283
+ this.#assertStagePermissions(fd);
284
+ if (this.#sync) {
285
+ if (this.#strictFileSync)
286
+ fs.fsyncSync(fd);
287
+ else
288
+ syncFileBestEffortSync(fd);
289
+ }
233
290
  const stat = fs.fstatSync(fd, { bigint: true });
234
- this.#receipt = Object.freeze({
235
- directory: this.#directory,
236
- temporaryBasename: this.#name,
237
- identity: Object.freeze({
238
- dev: stat.dev,
239
- ino: stat.ino,
240
- mode: Number(stat.mode & 4095n),
241
- nlink: stat.nlink,
242
- size: stat.size,
243
- uid: Number(stat.uid),
244
- gid: Number(stat.gid),
245
- mtimeNs: stat.mtimeNs,
246
- ctimeNs: stat.ctimeNs,
247
- }),
248
- });
291
+ this.#receipt = createStagedFileReceipt(this.#directory, this.#name, stat);
249
292
  this.#assertCurrent();
250
293
  }
251
294
  catch (error) {
@@ -275,6 +318,10 @@ class NativeStagedFile {
275
318
  this.#assertCurrent();
276
319
  assertFinalSymlinkRejected(path.join(this.#directory.realPath, basename), this.#rejectFinalSymlink);
277
320
  this.#assertBeforeMutation?.();
321
+ if (this.#assertBeforeMutation) {
322
+ this.#assertCurrent();
323
+ assertFinalSymlinkRejected(path.join(this.#directory.realPath, basename), this.#rejectFinalSymlink);
324
+ }
278
325
  try {
279
326
  if (overwrite) {
280
327
  this.#binding.renameReplace(this.#parentFd, this.#name, this.#parentFd, basename);
@@ -307,7 +354,7 @@ class NativeStagedFile {
307
354
  const receipt = Object.freeze({ status: "published", staged: this.receipt, basename, overwrite });
308
355
  this.#open().publication = receipt;
309
356
  onPublished?.(receipt.staged.identity);
310
- const stagedMode = this.#assertNamed(basename);
357
+ const stagedMode = this.#assertNamed(basename, 0o600);
311
358
  assertStagedDirectoryCurrent(this.#directory);
312
359
  // Keep contents private until the name passes its identity fence. Mode
313
360
  // changes use the owned fd, including for final mode 000.
@@ -317,11 +364,14 @@ class NativeStagedFile {
317
364
  // Content was synced during preparation. A lost chmod leaves 0600, no wider
318
365
  // than modes retaining owner rw; restrictive modes still need a durable correction.
319
366
  if (this.#sync && ((this.#publishedMode & 0o600) !== 0o600 || (stagedMode & ~this.#publishedMode) !== 0)) {
320
- syncFileBestEffortSync(fd);
367
+ if (this.#strictFileSync)
368
+ fs.fsyncSync(fd);
369
+ else
370
+ syncFileBestEffortSync(fd);
321
371
  }
322
372
  if (this.#sync)
323
373
  syncFileBestEffortSync(this.#parentFd);
324
- this.#assertNamed(basename);
374
+ this.#assertNamed(basename, this.#publishedMode);
325
375
  assertStagedDirectoryCurrent(this.#directory);
326
376
  return receipt;
327
377
  }
@@ -397,5 +447,5 @@ export async function stageFileInDirectory(options) {
397
447
  const input = { kind: "buffer", data: Buffer.from(options.content) };
398
448
  const mode = options.mode ?? 0o600;
399
449
  const parent = openStagedDirectory(options.directory);
400
- return await createNativeStage(binding, parent.fd, fs.closeSync, parent.receipt, input, mode);
450
+ return await createNativeStage(binding, parent.fd, fs.closeSync, parent.receipt, input, mode, undefined, true, true, undefined, undefined, false, "mode-only");
401
451
  }
package/dist/native.js CHANGED
@@ -22,7 +22,7 @@ function isMuslFromReport() {
22
22
  return true;
23
23
  }
24
24
  catch {
25
- // Continue with filesystem and ELF inspection.
25
+ // Continue with ELF and filesystem inspection.
26
26
  }
27
27
  return undefined;
28
28
  }
@@ -114,7 +114,7 @@ function isMuslFromElfInterpreter() {
114
114
  function isMusl() {
115
115
  if (process.platform !== "linux")
116
116
  return false;
117
- for (const detector of [isMuslFromReport, isMuslFromFilesystem, isMuslFromElfInterpreter]) {
117
+ for (const detector of [isMuslFromReport, isMuslFromElfInterpreter, isMuslFromFilesystem]) {
118
118
  const result = detector();
119
119
  if (result !== undefined)
120
120
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AACpD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAA;CAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AACpD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAA;CAAE,CAAC,CAAC"}
@@ -5,9 +5,18 @@ import { openedPathResolutionError } from "./opened-file-failure.js";
5
5
  import { sameFileIdentity } from "./file-identity.js";
6
6
  import { isNotFoundPathError } from "./path.js";
7
7
  import { realpathSync } from "./realpath.js";
8
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
8
9
  export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
9
- const handleStat = fsSync.fstatSync(handle.fd);
10
- return (await resolveOpenedFileRealPathForFd(handle.fd, handleStat, ioPath)).realPath;
10
+ const fd = handle.fd;
11
+ const identity = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
12
+ const resolved = await resolveOpenedFileRealPathForFd(fd, identity, ioPath);
13
+ let first = resolved.stat;
14
+ inspectFileIdentitySync(() => {
15
+ const current = first ?? fsSync.statSync(resolved.realPath, { bigint: true });
16
+ first = undefined;
17
+ return current;
18
+ }, identity);
19
+ return resolved.realPath;
11
20
  }
12
21
  export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
13
22
  const statOptions = typeof handleStat.dev === "bigint" || typeof handleStat.ino === "bigint"
@@ -1 +1 @@
1
- {"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACnC,GACD;IACE,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,CAAC;AAEN,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAmCvE"}
1
+ {"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACnC,GACD;IACE,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,CAAC;AAEN,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAmCvE"}
@@ -1,17 +1,23 @@
1
1
  import { FsSafeError } from "./errors.js";
2
2
  import { getNativeBinding } from "./native.js";
3
+ import { getFsSafeNativeConfig } from "./native-config.js";
4
+ import { warnNativeFallback } from "./native-fallback-warning.js";
3
5
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
6
+ import { readWindowsSecurityFactsCommand } from "./windows-security-command.js";
4
7
  export function readOwnerAndDacl(targetPath) {
5
8
  if (process.platform !== "win32") {
6
9
  return { status: "unsupported-platform", platform: process.platform };
7
10
  }
8
11
  assertNoWindowsPathAlias(targetPath, "filesystem", "owner and DACL path uses a Windows filesystem namespace alias");
9
12
  const native = getNativeBinding();
10
- if (!native) {
11
- throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require the matching optional native platform package; " +
12
- "install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
13
+ const inspect = native?.readOwnerAndDacl;
14
+ if (typeof inspect !== "function") {
15
+ if (getFsSafeNativeConfig().mode === "require") {
16
+ throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require an up-to-date native helper");
17
+ }
18
+ warnNativeFallback("windows-owner-dacl", "Windows owner and DACL inspection uses a slower built-in system command.");
13
19
  }
14
- const facts = native.readOwnerAndDacl(targetPath);
20
+ const facts = typeof inspect === "function" ? inspect.call(native, targetPath) : readWindowsSecurityFactsCommand(targetPath);
15
21
  return {
16
22
  status: "supported",
17
23
  ownerSid: facts.ownerSid,
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAczB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWvE;AAaD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAI1E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAA6B,GAAG,IAAI,CAIjG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA0ClE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAa/F;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChE,OAAO,CAsBT;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAMhE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWrF"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAczB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWvE;AAaD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAI1E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAA6B,GAAG,IAAI,CAIjG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA0ClE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAa/F;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChE,OAAO,CAsBT;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAMhE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CASrF"}
package/dist/path.js CHANGED
@@ -160,7 +160,8 @@ export function splitSafeRelativePath(relativePath) {
160
160
  export function resolveSafeRelativePath(rootDir, relativePath) {
161
161
  assertNoWindowsPathAlias(rootDir, "filesystem", "root dir uses a Windows filesystem namespace alias");
162
162
  const root = resolvePathPreservingWindowsRoot(rootDir);
163
- const target = resolvePathFromBasePreservingWindowsRoot(root, ...splitSafeRelativePath(relativePath));
163
+ splitSafeRelativePath(relativePath);
164
+ const target = resolvePathFromBasePreservingWindowsRoot(root, relativePath);
164
165
  if (!isPathInside(root, target)) {
165
166
  throw new FsSafeError("outside-workspace", "relative path escapes root");
166
167
  }
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
@@ -39,23 +39,9 @@ export async function inspectPathPermissions(targetPath, opts) {
39
39
  const admissionPlatform = process.platform === "win32"
40
40
  ? process.platform
41
41
  : (opts?.platform ?? process.platform);
42
- if (hasWindowsPathAlias(targetPath, "filesystem", admissionPlatform)) {
43
- const st = failedSafeStat("Path uses a Windows filesystem namespace alias");
44
- return {
45
- ok: false,
46
- isSymlink: false,
47
- isDir: false,
48
- mode: null,
49
- bits: null,
50
- source: "unknown",
51
- worldWritable: false,
52
- groupWritable: false,
53
- worldReadable: false,
54
- groupReadable: false,
55
- error: st.error,
56
- };
57
- }
58
- const st = await safeStatAdmitted(targetPath);
42
+ const st = hasWindowsPathAlias(targetPath, "filesystem", admissionPlatform)
43
+ ? failedSafeStat("Path uses a Windows filesystem namespace alias")
44
+ : await safeStatAdmitted(targetPath);
59
45
  if (!st.ok) {
60
46
  return {
61
47
  ok: false,
@@ -0,0 +1,4 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ import type { PinnedWriteInput } from "./pinned-write-types.js";
3
+ export declare function writePinnedInput(target: FileHandle | number, input: PinnedWriteInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
4
+ //# sourceMappingURL=pinned-write-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pinned-write-input.d.ts","sourceRoot":"","sources":["../src/pinned-write-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAWhE,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
@@ -0,0 +1,35 @@
1
+ import { types } from "node:util";
2
+ import { snapshotByteView } from "./byte-view.js";
3
+ import { writeCopyFileToFd } from "./copy-file-input.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { writeAllToFile } from "./write-file-handle.js";
6
+ const isUint8Array = types.isUint8Array;
7
+ function streamChunkBytes(chunk) {
8
+ if (Buffer.isBuffer(chunk))
9
+ return chunk;
10
+ if (!isUint8Array(chunk))
11
+ return Buffer.from(chunk);
12
+ return snapshotByteView(chunk);
13
+ }
14
+ export async function writePinnedInput(target, input, maxBytes, assertBeforeMutation) {
15
+ if (input.kind === "file") {
16
+ await writeCopyFileToFd(typeof target === "number" ? target : target.fd, input, maxBytes, assertBeforeMutation);
17
+ return;
18
+ }
19
+ let bytes = 0;
20
+ const write = async (data) => {
21
+ bytes += data.byteLength;
22
+ if (maxBytes !== undefined && bytes > maxBytes) {
23
+ throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
24
+ }
25
+ await writeAllToFile(target, data, { assertBeforeMutation });
26
+ };
27
+ if (input.kind === "buffer") {
28
+ await write(typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data);
29
+ }
30
+ else {
31
+ for await (const chunk of input.stream) {
32
+ await write(streamChunkBytes(chunk));
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,5 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ export declare function assertPinnedWriteMode(fd: number, mode: number, privateCreation?: boolean): void;
3
+ export declare function preparePinnedWriteMode(handle: FileHandle, createdMode: number, assertBeforeMutation?: () => void, privateCreation?: boolean): Promise<(() => void) | undefined>;
4
+ export declare function pinnedWriteModeAssertion(fd: number, mode: number, assertBeforeMutation?: () => void, privateCreation?: boolean): () => void;
5
+ //# sourceMappingURL=pinned-write-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pinned-write-mode.d.ts","sourceRoot":"","sources":["../src/pinned-write-mode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,UAAQ,GAAG,IAAI,CAO7F;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,eAAe,UAAQ,GACtB,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CAQnC;AAED,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,eAAe,UAAQ,GACtB,MAAM,IAAI,CAKZ"}
@@ -0,0 +1,31 @@
1
+ import fs from "node:fs";
2
+ import { FsSafeError } from "./errors.js";
3
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
4
+ import { assertPrivateCreationFile } from "./creation-file-state.js";
5
+ export function assertPinnedWriteMode(fd, mode, privateCreation = false) {
6
+ if (process.platform === "win32")
7
+ return;
8
+ const stat = fs.fstatSync(fd, { bigint: true });
9
+ if (privateCreation)
10
+ assertPrivateCreationFile(stat, fd);
11
+ if (Number(stat.mode & 4095n) !== mode) {
12
+ throw new FsSafeError("insecure-permissions", "filesystem did not enforce the requested file mode");
13
+ }
14
+ }
15
+ export async function preparePinnedWriteMode(handle, createdMode, assertBeforeMutation, privateCreation = false) {
16
+ if (process.platform === "win32")
17
+ return assertBeforeMutation;
18
+ assertSynchronousCallbackResult(assertBeforeMutation?.(), "assertBeforeMutation");
19
+ if (createdMode !== 0o600) {
20
+ if (privateCreation)
21
+ assertPrivateCreationFile(fs.fstatSync(handle.fd, { bigint: true }), handle.fd);
22
+ await handle.chmod(0o600);
23
+ }
24
+ return pinnedWriteModeAssertion(handle.fd, 0o600, assertBeforeMutation, privateCreation);
25
+ }
26
+ export function pinnedWriteModeAssertion(fd, mode, assertBeforeMutation, privateCreation = false) {
27
+ return () => {
28
+ assertSynchronousCallbackResult(assertBeforeMutation?.(), "assertBeforeMutation");
29
+ assertPinnedWriteMode(fd, mode, privateCreation);
30
+ };
31
+ }
@@ -0,0 +1,6 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import type { AsyncDirectoryGuard } from "./directory-guard.js";
3
+ import { type FileIdentityStat } from "./file-identity.js";
4
+ import type { PinnedWriteParams } from "./pinned-write-types.js";
5
+ export declare function runPinnedStagedWrite(params: PinnedWriteParams, parentPath: string, parentGuard: AsyncDirectoryGuard<BigIntStats>): Promise<FileIdentityStat>;
6
+ //# sourceMappingURL=pinned-write-staged.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pinned-write-staged.d.ts","sourceRoot":"","sources":["../src/pinned-write-staged.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAKxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AASjE,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,GAC5C,OAAO,CAAC,gBAAgB,CAAC,CAqJ3B"}