@openclaw/fs-safe 0.10.0 → 0.11.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 (259) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +1 -0
  3. package/README.md +36 -5
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +4 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +4 -0
  9. package/dist/archive-durability.d.ts +6 -6
  10. package/dist/archive-durability.d.ts.map +1 -1
  11. package/dist/archive-durability.js +1 -1
  12. package/dist/archive-entry.d.ts.map +1 -1
  13. package/dist/archive-entry.js +4 -5
  14. package/dist/archive-gzip-tail.d.ts +1 -0
  15. package/dist/archive-gzip-tail.d.ts.map +1 -1
  16. package/dist/archive-gzip-tail.js +3 -0
  17. package/dist/archive-input.d.ts.map +1 -1
  18. package/dist/archive-input.js +4 -2
  19. package/dist/archive-merge.d.ts +5 -1
  20. package/dist/archive-merge.d.ts.map +1 -1
  21. package/dist/archive-merge.js +15 -12
  22. package/dist/archive-native.js +4 -4
  23. package/dist/archive-parser.wasm +0 -0
  24. package/dist/archive-read.d.ts.map +1 -1
  25. package/dist/archive-read.js +17 -9
  26. package/dist/archive-staging.d.ts +6 -3
  27. package/dist/archive-staging.d.ts.map +1 -1
  28. package/dist/archive-staging.js +42 -22
  29. package/dist/archive-tar-stream.d.ts.map +1 -1
  30. package/dist/archive-tar-stream.js +7 -6
  31. package/dist/archive-tar-wasm.d.ts.map +1 -1
  32. package/dist/archive-tar-wasm.js +16 -13
  33. package/dist/archive-zip-admission.d.ts +1 -1
  34. package/dist/archive-zip-admission.d.ts.map +1 -1
  35. package/dist/archive-zip-admission.js +48 -12
  36. package/dist/archive-zip-loader.d.ts +6 -0
  37. package/dist/archive-zip-loader.d.ts.map +1 -0
  38. package/dist/archive-zip-loader.js +38 -0
  39. package/dist/archive-zip-names.d.ts.map +1 -1
  40. package/dist/archive-zip-names.js +13 -8
  41. package/dist/archive-zip-preflight.d.ts +2 -3
  42. package/dist/archive-zip-preflight.d.ts.map +1 -1
  43. package/dist/archive-zip-preflight.js +2 -34
  44. package/dist/archive.d.ts.map +1 -1
  45. package/dist/archive.js +12 -10
  46. package/dist/bounded-read.d.ts +5 -0
  47. package/dist/bounded-read.d.ts.map +1 -1
  48. package/dist/bounded-read.js +16 -9
  49. package/dist/copy-file-input.d.ts +0 -1
  50. package/dist/copy-file-input.d.ts.map +1 -1
  51. package/dist/copy-file-input.js +4 -26
  52. package/dist/copy-tree-portable.d.ts.map +1 -1
  53. package/dist/copy-tree-portable.js +57 -26
  54. package/dist/copy.d.ts +1 -1
  55. package/dist/copy.d.ts.map +1 -1
  56. package/dist/copy.js +3 -1
  57. package/dist/directory-durability.d.ts.map +1 -1
  58. package/dist/directory-durability.js +5 -4
  59. package/dist/directory-guard.d.ts +11 -1
  60. package/dist/directory-guard.d.ts.map +1 -1
  61. package/dist/directory-guard.js +53 -11
  62. package/dist/durability.d.ts +1 -1
  63. package/dist/durability.d.ts.map +1 -1
  64. package/dist/durability.js +1 -1
  65. package/dist/file-handle-transfer.d.ts +14 -0
  66. package/dist/file-handle-transfer.d.ts.map +1 -0
  67. package/dist/file-handle-transfer.js +64 -0
  68. package/dist/file-hash.d.ts +3 -0
  69. package/dist/file-hash.d.ts.map +1 -1
  70. package/dist/file-hash.js +91 -31
  71. package/dist/file-lock-sync.d.ts.map +1 -1
  72. package/dist/file-lock-sync.js +8 -4
  73. package/dist/file-store-boundary.d.ts.map +1 -1
  74. package/dist/file-store-boundary.js +7 -5
  75. package/dist/file-store-path.d.ts +3 -0
  76. package/dist/file-store-path.d.ts.map +1 -0
  77. package/dist/file-store-path.js +27 -0
  78. package/dist/file-store-prune.d.ts.map +1 -1
  79. package/dist/file-store-prune.js +6 -4
  80. package/dist/file-store-sync-write.d.ts.map +1 -1
  81. package/dist/file-store-sync-write.js +56 -44
  82. package/dist/file-store.d.ts.map +1 -1
  83. package/dist/file-store.js +2 -18
  84. package/dist/filename.d.ts.map +1 -1
  85. package/dist/filename.js +2 -1
  86. package/dist/guarded-mkdir.d.ts.map +1 -1
  87. package/dist/guarded-mkdir.js +3 -2
  88. package/dist/guest-dispatch-python.d.ts +2 -0
  89. package/dist/guest-dispatch-python.d.ts.map +1 -0
  90. package/dist/guest-dispatch-python.js +117 -0
  91. package/dist/guest-native-python.d.ts +4 -0
  92. package/dist/guest-native-python.d.ts.map +1 -0
  93. package/dist/guest-native-python.js +135 -0
  94. package/dist/guest.d.ts +9 -0
  95. package/dist/guest.d.ts.map +1 -0
  96. package/dist/guest.js +413 -0
  97. package/dist/index.d.ts +1 -1
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/install-path.d.ts.map +1 -1
  100. package/dist/install-path.js +3 -2
  101. package/dist/json-durable-queue-directory.js +3 -3
  102. package/dist/json-durable-queue.d.ts.map +1 -1
  103. package/dist/json-durable-queue.js +6 -4
  104. package/dist/json.d.ts.map +1 -1
  105. package/dist/json.js +2 -1
  106. package/dist/local-roots.d.ts.map +1 -1
  107. package/dist/local-roots.js +2 -1
  108. package/dist/move-path-stage.d.ts.map +1 -1
  109. package/dist/move-path-stage.js +2 -1
  110. package/dist/move-path.d.ts.map +1 -1
  111. package/dist/move-path.js +4 -3
  112. package/dist/mutation-authority.d.ts +1 -0
  113. package/dist/mutation-authority.d.ts.map +1 -1
  114. package/dist/mutation-authority.js +4 -4
  115. package/dist/native-binding.d.ts +7 -2
  116. package/dist/native-binding.d.ts.map +1 -1
  117. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  118. package/dist/native-pinned-write-windows.js +3 -2
  119. package/dist/native-pinned-write.d.ts.map +1 -1
  120. package/dist/native-pinned-write.js +2 -1
  121. package/dist/opened-realpath.d.ts.map +1 -1
  122. package/dist/opened-realpath.js +5 -4
  123. package/dist/output.d.ts +2 -0
  124. package/dist/output.d.ts.map +1 -1
  125. package/dist/output.js +2 -0
  126. package/dist/overwrite-file-handle.d.ts +8 -0
  127. package/dist/overwrite-file-handle.d.ts.map +1 -0
  128. package/dist/overwrite-file-handle.js +42 -0
  129. package/dist/path-case.d.ts +7 -0
  130. package/dist/path-case.d.ts.map +1 -0
  131. package/dist/path-case.js +136 -0
  132. package/dist/path.d.ts.map +1 -1
  133. package/dist/path.js +2 -1
  134. package/dist/permissions-windows.d.ts +1 -1
  135. package/dist/permissions-windows.d.ts.map +1 -1
  136. package/dist/permissions-windows.js +48 -6
  137. package/dist/pinned-open.d.ts.map +1 -1
  138. package/dist/pinned-open.js +3 -1
  139. package/dist/pinned-write.d.ts +2 -2
  140. package/dist/pinned-write.d.ts.map +1 -1
  141. package/dist/pinned-write.js +2 -1
  142. package/dist/private-temp-workspace.d.ts.map +1 -1
  143. package/dist/private-temp-workspace.js +6 -4
  144. package/dist/realpath.d.ts +4 -0
  145. package/dist/realpath.d.ts.map +1 -0
  146. package/dist/realpath.js +43 -0
  147. package/dist/recursive-mkdir-path.d.ts +3 -0
  148. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  149. package/dist/recursive-mkdir-path.js +8 -0
  150. package/dist/replace-directory.d.ts.map +1 -1
  151. package/dist/replace-directory.js +2 -1
  152. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  153. package/dist/replace-file-copy-fallback.js +23 -31
  154. package/dist/replace-file-copy-source.d.ts.map +1 -1
  155. package/dist/replace-file-copy-source.js +7 -12
  156. package/dist/replace-file-mode.d.ts +3 -0
  157. package/dist/replace-file-mode.d.ts.map +1 -0
  158. package/dist/replace-file-mode.js +10 -0
  159. package/dist/replace-file.d.ts +1 -0
  160. package/dist/replace-file.d.ts.map +1 -1
  161. package/dist/replace-file.js +14 -8
  162. package/dist/root-context.d.ts.map +1 -1
  163. package/dist/root-context.js +8 -8
  164. package/dist/root-create-input.d.ts +10 -0
  165. package/dist/root-create-input.d.ts.map +1 -0
  166. package/dist/root-create-input.js +80 -0
  167. package/dist/root-directory-list.d.ts +3 -1
  168. package/dist/root-directory-list.d.ts.map +1 -1
  169. package/dist/root-directory-list.js +21 -3
  170. package/dist/root-entries.d.ts +11 -0
  171. package/dist/root-entries.d.ts.map +1 -0
  172. package/dist/root-entries.js +61 -0
  173. package/dist/root-errors.d.ts +5 -5
  174. package/dist/root-errors.d.ts.map +1 -1
  175. package/dist/root-errors.js +13 -12
  176. package/dist/root-impl.d.ts +13 -3
  177. package/dist/root-impl.d.ts.map +1 -1
  178. package/dist/root-impl.js +40 -36
  179. package/dist/root-options.d.ts +12 -1
  180. package/dist/root-options.d.ts.map +1 -1
  181. package/dist/root-path-existing.d.ts.map +1 -1
  182. package/dist/root-path-existing.js +4 -3
  183. package/dist/root-path-symlink.d.ts.map +1 -1
  184. package/dist/root-path-symlink.js +3 -2
  185. package/dist/root-paths.d.ts.map +1 -1
  186. package/dist/root-paths.js +13 -9
  187. package/dist/root-remove.d.ts +5 -0
  188. package/dist/root-remove.d.ts.map +1 -0
  189. package/dist/root-remove.js +286 -0
  190. package/dist/root-symlink-policy.d.ts +2 -1
  191. package/dist/root-symlink-policy.d.ts.map +1 -1
  192. package/dist/root-symlink-policy.js +2 -2
  193. package/dist/root-write-mode.d.ts.map +1 -1
  194. package/dist/root-write-mode.js +2 -1
  195. package/dist/root.d.ts +2 -1
  196. package/dist/root.d.ts.map +1 -1
  197. package/dist/secret-file.d.ts.map +1 -1
  198. package/dist/secret-file.js +2 -1
  199. package/dist/secret-read-async.d.ts.map +1 -1
  200. package/dist/secret-read-async.js +2 -1
  201. package/dist/secure-file.d.ts.map +1 -1
  202. package/dist/secure-file.js +21 -4
  203. package/dist/secure-temp-dir.d.ts.map +1 -1
  204. package/dist/secure-temp-dir.js +2 -1
  205. package/dist/sibling-staged-file.d.ts +1 -0
  206. package/dist/sibling-staged-file.d.ts.map +1 -1
  207. package/dist/sibling-staged-file.js +42 -8
  208. package/dist/sibling-temp.d.ts +2 -0
  209. package/dist/sibling-temp.d.ts.map +1 -1
  210. package/dist/sibling-temp.js +6 -4
  211. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  212. package/dist/sidecar-lock-acquire.js +15 -4
  213. package/dist/sidecar-lock-policy.d.ts +2 -0
  214. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  215. package/dist/sidecar-lock-policy.js +17 -0
  216. package/dist/staged-directory.d.ts.map +1 -1
  217. package/dist/staged-directory.js +4 -3
  218. package/dist/temp-target.d.ts +14 -12
  219. package/dist/temp-target.d.ts.map +1 -1
  220. package/dist/temp-target.js +12 -6
  221. package/dist/trash.d.ts.map +1 -1
  222. package/dist/trash.js +7 -5
  223. package/dist/unicode-path.d.ts +3 -0
  224. package/dist/unicode-path.d.ts.map +1 -0
  225. package/dist/unicode-path.js +13 -0
  226. package/dist/walk.d.ts.map +1 -1
  227. package/dist/walk.js +3 -2
  228. package/dist/write-file-handle.d.ts +1 -0
  229. package/dist/write-file-handle.d.ts.map +1 -1
  230. package/dist/write-file-handle.js +3 -2
  231. package/docs/advanced.md +4 -0
  232. package/docs/archive.md +25 -5
  233. package/docs/atomic.md +17 -1
  234. package/docs/config.md +1 -0
  235. package/docs/contributing.md +29 -1
  236. package/docs/copy.md +75 -6
  237. package/docs/directory-identity.md +85 -0
  238. package/docs/durability.md +36 -1
  239. package/docs/entries.md +109 -0
  240. package/docs/errors.md +3 -3
  241. package/docs/file-store.md +15 -0
  242. package/docs/guest.md +141 -0
  243. package/docs/in-place-write.md +81 -0
  244. package/docs/index.md +2 -0
  245. package/docs/install.md +31 -0
  246. package/docs/native-helper.md +10 -3
  247. package/docs/native.md +4 -0
  248. package/docs/output.md +32 -6
  249. package/docs/path-case.md +64 -0
  250. package/docs/path-scope.md +1 -1
  251. package/docs/permissions.md +11 -2
  252. package/docs/public-api.md +31 -2
  253. package/docs/root.md +30 -2
  254. package/docs/secure-file.md +2 -0
  255. package/docs/sidecar-lock.md +12 -3
  256. package/docs/temp.md +35 -6
  257. package/docs/types.md +1 -1
  258. package/docs/writing.md +153 -3
  259. package/package.json +14 -8
@@ -1 +1 @@
1
- {"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { formatErrorDetail } from "./error-detail.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { assertNoNulPathInput, hasNodeErrorCode, isNodeError, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
7
7
  import { root as openRoot } from "./root.js";
8
+ import { realpathSync } from "./realpath.js";
8
9
  function invalidPath(scopeLabel) {
9
10
  return {
10
11
  ok: false,
@@ -12,12 +13,15 @@ function invalidPath(scopeLabel) {
12
13
  };
13
14
  }
14
15
  function pathStaysWithinRoot(rootDir, candidatePath) {
16
+ if (process.platform !== "win32") {
17
+ return candidatePath !== rootDir && isPathInside(rootDir, candidatePath);
18
+ }
15
19
  const relative = path.relative(rootDir, candidatePath);
16
20
  return Boolean(relative) && !isPathRelativeEscape(relative);
17
21
  }
18
22
  async function resolveRealPathIfExists(targetPath) {
19
23
  try {
20
- return fsSync.realpathSync.native(targetPath);
24
+ return realpathSync.native(targetPath);
21
25
  }
22
26
  catch {
23
27
  return undefined;
@@ -29,7 +33,7 @@ async function resolveTrustedRootRealPath(rootDir) {
29
33
  if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
30
34
  return undefined;
31
35
  }
32
- return fsSync.realpathSync.native(rootDir);
36
+ return realpathSync.native(rootDir);
33
37
  }
34
38
  catch {
35
39
  return undefined;
@@ -50,7 +54,7 @@ async function validateCanonicalPathWithinRoot(params) {
50
54
  if (params.expect === "file" && candidateLstat.nlink > 1) {
51
55
  return "invalid";
52
56
  }
53
- const candidateRealPath = fsSync.realpathSync.native(params.candidatePath);
57
+ const candidateRealPath = realpathSync.native(params.candidatePath);
54
58
  return isPathInside(params.rootRealPath, candidateRealPath) ? "ok" : "invalid";
55
59
  }
56
60
  catch (err) {
@@ -169,9 +173,9 @@ export async function ensureDirectoryWithinRoot(params) {
169
173
  return invalidPath(scopeLabel);
170
174
  }
171
175
  await assertNoSymlinkSegments({ rootDir, targetPath, scopeLabel });
172
- const rootReal = fsSync.realpathSync.native(rootDir);
176
+ const rootReal = realpathSync.native(rootDir);
173
177
  const nearestExistingPath = await resolveNearestExistingPath(targetPath);
174
- const nearestExistingReal = fsSync.realpathSync.native(nearestExistingPath);
178
+ const nearestExistingReal = realpathSync.native(nearestExistingPath);
175
179
  if (!isPathInside(rootReal, nearestExistingReal)) {
176
180
  return invalidPath(scopeLabel);
177
181
  }
@@ -202,12 +206,12 @@ export async function ensureDirectoryWithinRoot(params) {
202
206
  }
203
207
  }
204
208
  }
205
- const currentReal = fsSync.realpathSync.native(current);
209
+ const currentReal = realpathSync.native(current);
206
210
  if (!isPathInside(rootReal, currentReal)) {
207
211
  return invalidPath(scopeLabel);
208
212
  }
209
213
  }
210
- const targetReal = fsSync.realpathSync.native(targetPath);
214
+ const targetReal = realpathSync.native(targetPath);
211
215
  if (!isPathInside(rootReal, targetReal)) {
212
216
  return invalidPath(scopeLabel);
213
217
  }
@@ -309,7 +313,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
309
313
  return lexicalPathResult;
310
314
  }
311
315
  try {
312
- const resolvedExistingPath = fsSync.realpathSync.native(raw);
316
+ const resolvedExistingPath = realpathSync.native(raw);
313
317
  const relativePath = path.relative(rootRealPath, resolvedExistingPath);
314
318
  if (!isInRoot(relativePath)) {
315
319
  return lexicalPathResult;
@@ -351,7 +355,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
351
355
  scopeLabel: params.scopeLabel,
352
356
  });
353
357
  const existingPath = await resolveNearestExistingPath(pathResult.fallbackPath);
354
- const existingRealPath = fsSync.realpathSync.native(existingPath);
358
+ const existingRealPath = realpathSync.native(existingPath);
355
359
  if (!isPathInside(rootRealPath, existingRealPath)) {
356
360
  return invalidPath(params.scopeLabel);
357
361
  }
@@ -0,0 +1,5 @@
1
+ import { type RootContext } from "./root-context.js";
2
+ import type { RootRemoveOptions } from "./root-options.js";
3
+ export declare function validateRemoveOptions(options: RootRemoveOptions): void;
4
+ export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions): Promise<void>;
5
+ //# sourceMappingURL=root-remove.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAS3D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AA0CD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA6Lf"}
@@ -0,0 +1,286 @@
1
+ import fsSync from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
5
+ import { assertMutationNotDenied } from "./deny-mutations.js";
6
+ import { FsSafeError } from "./errors.js";
7
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
8
+ import { MutationAuthorityError } from "./mutation-authority.js";
9
+ import { isNotFoundPathError, isPathInside } from "./path.js";
10
+ import { assertRootIdentityCurrent } from "./root-context.js";
11
+ import { normalizeRemoveGuardError, normalizeRemovePathError, rootPathChangedError } from "./root-errors.js";
12
+ import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
13
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
14
+ import { createSuppressedError } from "./suppressed-error.js";
15
+ import { getFsSafeTestHooks } from "./test-hooks.js";
16
+ const DEFAULT_MAX_ENTRIES = 100_000;
17
+ const DEFAULT_MAX_DEPTH = 64;
18
+ export function validateRemoveOptions(options) {
19
+ if (options.order !== undefined && options.order !== "filesystem" && options.order !== "sorted") {
20
+ throw new TypeError("remove order must be filesystem or sorted");
21
+ }
22
+ for (const key of ["maxEntries", "maxDepth"]) {
23
+ const value = options[key];
24
+ if (value !== undefined && value !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(value) || value < 0)) {
25
+ throw new RangeError(`${key} must be a non-negative safe integer`);
26
+ }
27
+ }
28
+ if (!options.recursive && (options.maxEntries !== undefined || options.maxDepth !== undefined)) {
29
+ throw new TypeError("remove budgets require recursive: true");
30
+ }
31
+ if (!options.recursive && options.order !== undefined) {
32
+ throw new TypeError("remove order requires recursive: true");
33
+ }
34
+ }
35
+ function assertNotAborted(signal) {
36
+ try {
37
+ signal?.throwIfAborted();
38
+ }
39
+ catch (error) {
40
+ // Preserve arbitrary abort reasons through Root's filesystem error mapping.
41
+ throw new MutationAuthorityError(error);
42
+ }
43
+ }
44
+ async function removeOne(root, targetPath, options) {
45
+ let guard;
46
+ try {
47
+ guard = await createAsyncDirectoryGuard(path.dirname(targetPath));
48
+ }
49
+ catch (error) {
50
+ if (options.force && isNotFoundPathError(error)) {
51
+ await assertRootIdentityCurrent(root);
52
+ assertNotAborted(options.signal);
53
+ return;
54
+ }
55
+ throw normalizeRemoveGuardError(error);
56
+ }
57
+ try {
58
+ await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", targetPath);
59
+ await assertAsyncDirectoryGuard(guard);
60
+ }
61
+ catch (error) {
62
+ throw normalizeRemoveGuardError(error);
63
+ }
64
+ try {
65
+ const isDirectory = fsSync.lstatSync(targetPath).isDirectory();
66
+ assertFinalSymlinkRejected(targetPath, options.mutationSymlinks !== undefined);
67
+ assertNotAborted(options.signal);
68
+ options.assertBeforeMutation?.();
69
+ await (isDirectory ? fs.rmdir(targetPath) : fs.unlink(targetPath));
70
+ }
71
+ catch (error) {
72
+ if (!(options.force && isNotFoundPathError(error)))
73
+ throw normalizeRemovePathError(error);
74
+ }
75
+ await assertAsyncDirectoryGuard(guard).catch(error => { throw normalizeRemoveGuardError(error); });
76
+ assertNotAborted(options.signal);
77
+ }
78
+ export async function removePathInRootFallback(root, targetPath, options) {
79
+ assertNotAborted(options.signal);
80
+ if (!options.recursive) {
81
+ await removeOne(root, targetPath, options);
82
+ return;
83
+ }
84
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
85
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
86
+ const details = (target, phase) => ({
87
+ operation: "remove",
88
+ phase,
89
+ relativePath: path.relative(targetPath, target),
90
+ });
91
+ let examined = 0;
92
+ const rootGuard = await createAsyncDirectoryGuard(root.rootReal, { bigint: true });
93
+ if (!sameFileIdentityForCleanup(rootGuard.stat, root.rootIdentity))
94
+ throw rootPathChangedError();
95
+ const guards = [rootGuard];
96
+ let ancestor = root.rootReal;
97
+ const parentRelative = path.relative(root.rootReal, path.dirname(targetPath));
98
+ for (const segment of parentRelative.split(path.sep).filter(Boolean)) {
99
+ ancestor = path.join(ancestor, segment);
100
+ let guard;
101
+ try {
102
+ guard = await createAsyncDirectoryGuard(ancestor, { bigint: true });
103
+ }
104
+ catch (error) {
105
+ if (isNotFoundPathError(error)) {
106
+ assertCurrent();
107
+ assertNotAborted(options.signal);
108
+ if (options.force)
109
+ return;
110
+ }
111
+ throw normalizeRemoveGuardError(error);
112
+ }
113
+ if (!isPathInside(guard.realPath, ancestor) || !isPathInside(ancestor, guard.realPath)) {
114
+ throw new FsSafeError("path-mismatch", "removal ancestor changed during operation");
115
+ }
116
+ guards.push(guard);
117
+ }
118
+ function assertCurrent() {
119
+ for (const guard of guards) {
120
+ try {
121
+ assertSyncDirectoryGuard(guard);
122
+ }
123
+ catch (error) {
124
+ // force tolerates a vanished leaf, never a lost traversal boundary.
125
+ throw new FsSafeError("path-mismatch", "removal ancestor changed during operation", { cause: error });
126
+ }
127
+ }
128
+ }
129
+ const inspect = (target, expected) => {
130
+ assertCurrent();
131
+ try {
132
+ return inspectFileIdentitySync(() => fsSync.lstatSync(target, { bigint: true }), expected);
133
+ }
134
+ catch (error) {
135
+ if (options.force && isNotFoundPathError(error))
136
+ return;
137
+ throw normalizeRemovePathError(error, details(target, expected ? "remove" : "inspect"));
138
+ }
139
+ };
140
+ async function visit(target, depth, entryCounted = false) {
141
+ assertNotAborted(options.signal);
142
+ if ((!entryCounted && examined >= maxEntries) || depth > maxDepth) {
143
+ throw new FsSafeError("too-large", "recursive removal budget exceeded", {
144
+ details: details(target, "inspect"),
145
+ });
146
+ }
147
+ if (!entryCounted)
148
+ examined += 1;
149
+ await assertMutationNotDenied(target, options.denyMutations, { protectAncestors: true });
150
+ assertNotAborted(options.signal);
151
+ const initial = inspect(target);
152
+ if (!initial)
153
+ return;
154
+ assertFinalSymlinkRejected(target, options.mutationSymlinks !== undefined, details(target, "inspect"));
155
+ if (initial.isDirectory()) {
156
+ let directoryGuard;
157
+ try {
158
+ directoryGuard = await createAsyncDirectoryGuard(target, { bigint: true });
159
+ }
160
+ catch (error) {
161
+ assertCurrent();
162
+ assertNotAborted(options.signal);
163
+ if (options.force && isNotFoundPathError(error))
164
+ return;
165
+ throw normalizeRemovePathError(error, details(target, "inspect"));
166
+ }
167
+ if (!isPathInside(directoryGuard.realPath, target) || !isPathInside(target, directoryGuard.realPath) ||
168
+ !sameFileIdentityForCleanup(directoryGuard.stat, initial)) {
169
+ throw new FsSafeError("path-mismatch", "removal directory changed during operation");
170
+ }
171
+ guards.push(directoryGuard);
172
+ try {
173
+ assertCurrent();
174
+ assertNotAborted(options.signal);
175
+ const handle = await fs.opendir(target, { bufferSize: 1 }).catch(error => {
176
+ if (options.force && isNotFoundPathError(error)) {
177
+ assertNotAborted(options.signal);
178
+ return undefined;
179
+ }
180
+ throw normalizeRemovePathError(error, details(target, "enumerate"));
181
+ });
182
+ if (handle) {
183
+ let failed = false;
184
+ let operationError;
185
+ try {
186
+ let names = options.order === "sorted" ? [] : undefined;
187
+ if (names && maxEntries === Infinity) {
188
+ assertCurrent();
189
+ assertNotAborted(options.signal);
190
+ const snapshot = await fs.readdir(target).catch(error => {
191
+ if (options.force && isNotFoundPathError(error))
192
+ return undefined;
193
+ throw normalizeRemovePathError(error, details(target, "enumerate"));
194
+ });
195
+ assertNotAborted(options.signal);
196
+ if (snapshot) {
197
+ assertCurrent();
198
+ names = snapshot;
199
+ examined += names.length;
200
+ }
201
+ }
202
+ else {
203
+ while (true) {
204
+ assertCurrent();
205
+ assertNotAborted(options.signal);
206
+ const entry = await handle.read().catch(error => {
207
+ if (options.force && isNotFoundPathError(error))
208
+ return undefined;
209
+ throw normalizeRemovePathError(error, details(target, "enumerate"));
210
+ });
211
+ if (entry === undefined) {
212
+ assertNotAborted(options.signal);
213
+ if (names)
214
+ names.length = 0;
215
+ break;
216
+ }
217
+ assertCurrent();
218
+ assertNotAborted(options.signal);
219
+ if (!entry)
220
+ break;
221
+ if (names) {
222
+ if (examined >= maxEntries) {
223
+ throw new FsSafeError("too-large", "recursive removal budget exceeded", {
224
+ details: details(path.join(target, entry.name), "enumerate"),
225
+ });
226
+ }
227
+ examined += 1;
228
+ names.push(entry.name);
229
+ }
230
+ else {
231
+ await visit(path.join(target, entry.name), depth + 1);
232
+ }
233
+ }
234
+ }
235
+ if (names) {
236
+ for (const name of names.sort()) {
237
+ await visit(path.join(target, name), depth + 1, true);
238
+ }
239
+ }
240
+ }
241
+ catch (error) {
242
+ failed = true;
243
+ operationError = error instanceof MutationAuthorityError ? error.rejection : error;
244
+ throw error;
245
+ }
246
+ finally {
247
+ try {
248
+ // Close before rmdir, including on Windows and after revocation.
249
+ await handle.close();
250
+ }
251
+ catch (error) {
252
+ const closeError = normalizeRemovePathError(error, details(target, "enumerate"));
253
+ if (failed)
254
+ throw createSuppressedError(closeError, operationError, "recursive removal and close both failed");
255
+ throw closeError;
256
+ }
257
+ }
258
+ }
259
+ }
260
+ finally {
261
+ guards.pop();
262
+ }
263
+ }
264
+ // A tolerated missing-directory observation still rechecks its target and ancestors.
265
+ await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("remove", target);
266
+ await assertMutationNotDenied(target, options.denyMutations, { protectAncestors: true });
267
+ assertNotAborted(options.signal);
268
+ if (!inspect(target, initial))
269
+ return;
270
+ assertFinalSymlinkRejected(target, options.mutationSymlinks !== undefined, details(target, "remove"));
271
+ assertNotAborted(options.signal);
272
+ options.assertBeforeMutation?.();
273
+ try {
274
+ await (initial.isDirectory() ? fs.rmdir(target) : fs.unlink(target));
275
+ }
276
+ catch (error) {
277
+ if (!(options.force && isNotFoundPathError(error))) {
278
+ throw normalizeRemovePathError(error, details(target, "remove"));
279
+ }
280
+ }
281
+ assertCurrent();
282
+ assertNotAborted(options.signal);
283
+ }
284
+ await assertRootIdentityCurrent(root);
285
+ await visit(targetPath, 0);
286
+ }
@@ -1,3 +1,4 @@
1
+ import { type FsSafeErrorDetails } from "./errors.js";
1
2
  export type SymlinkPolicy = "reject" | "follow-within-root" | "follow-parents-within-root";
2
3
  export type MutationSymlinkPolicy = "reject" | "follow-parents-within-root";
3
4
  export declare function readSymlinkResolution(symlinks: SymlinkPolicy | undefined): {
@@ -9,5 +10,5 @@ export declare function mutationSymlinkResolution(symlinks: MutationSymlinkPolic
9
10
  rejectFinalSymlink: boolean;
10
11
  resolveCanonical: boolean;
11
12
  };
12
- export declare function assertFinalSymlinkRejected(targetPath: string, reject: boolean | undefined): void;
13
+ export declare function assertFinalSymlinkRejected(targetPath: string, reject: boolean | undefined, details?: FsSafeErrorDetails): void;
13
14
  //# sourceMappingURL=root-symlink-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-symlink-policy.d.ts","sourceRoot":"","sources":["../src/root-symlink-policy.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,oBAAoB,GAAG,4BAA4B,CAAC;AAC3F,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,4BAA4B,CAAC;AAE5E,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS;IAErE,cAAc;IACd,kBAAkB;EAErB;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,SAAS;IAKjF,cAAc;IACd,kBAAkB;IAClB,gBAAgB;EAEnB;AAID,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAShG"}
1
+ {"version":3,"file":"root-symlink-policy.d.ts","sourceRoot":"","sources":["../src/root-symlink-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGnE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,oBAAoB,GAAG,4BAA4B,CAAC;AAC3F,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,4BAA4B,CAAC;AAE5E,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS;IAErE,cAAc;IACd,kBAAkB;EAErB;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,SAAS;IAKjF,cAAc;IACd,kBAAkB;IAClB,gBAAgB;EAEnB;AAID,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,OAAO,GAAG,SAAS,EAC3B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,IAAI,CASN"}
@@ -19,12 +19,12 @@ export function mutationSymlinkResolution(symlinks) {
19
19
  }
20
20
  // Call after parent fences and immediately before the mutation. This is a
21
21
  // final-component check, not an atomic conditional rename against another process.
22
- export function assertFinalSymlinkRejected(targetPath, reject) {
22
+ export function assertFinalSymlinkRejected(targetPath, reject, details) {
23
23
  if (!reject)
24
24
  return;
25
25
  try {
26
26
  if (fs.lstatSync(targetPath).isSymbolicLink()) {
27
- throw new FsSafeError("symlink", "final symlink not allowed");
27
+ throw new FsSafeError("symlink", "final symlink not allowed", { details });
28
28
  }
29
29
  }
30
30
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AASA,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsClB"}
1
+ {"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AAUA,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsClB"}
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { isNotFoundPathError, isPathInside } from "./path.js";
5
5
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
+ import { realpathSync } from "./realpath.js";
6
7
  import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
7
8
  import { inspectFileIdentity } from "./strict-file-identity.js";
8
9
  // The caller has already resolved and guarded this write target.
@@ -30,7 +31,7 @@ export async function inheritWriteTargetMode(params) {
30
31
  try {
31
32
  // A parent can change after guarded resolution. Do not inherit metadata
32
33
  // from an outside inode, even if the parent is restored before publication.
33
- const realPath = fsSync.realpathSync.native(params.targetPath);
34
+ const realPath = realpathSync.native(params.targetPath);
34
35
  if (!isPathInside(params.rootWithSep, realPath))
35
36
  throw outsideWorkspaceError();
36
37
  await inspectFileIdentity(async () => {
package/dist/root.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type MutationSymlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
1
+ export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type MutationSymlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCreateJsonOptions, type RootCreateOptions, type RootCreateStreamOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
2
2
  export type { RootCopyPublicationReceipt } from "./copy-publication.js";
3
3
  export type { CopyCloneMode } from "./copy-policy.js";
4
+ export type { RootEntriesOptions } from "./root-entries.js";
4
5
  export type { ContainmentGuarantee } from "./containment.js";
5
6
  export type { RootWalkDataEntry, RootWalkDataEntryKind, RootWalkDirectoryErrorBehavior, RootWalkEntry, RootWalkEntryFilter, RootWalkEntryFilterResult, RootWalkEntryKind, RootWalkLimitBehavior, RootWalkOptions, RootWalkSymlinkPolicy, } from "./root-walk.js";
6
7
  //# sourceMappingURL=root.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,8BAA8B,EAC9B,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,8BAA8B,EAC9B,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAUhJ,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAYF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAuBD;AAwDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
1
+ {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAUhJ,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAYF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAuBD;AAwDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
@@ -1,6 +1,7 @@
1
1
  import fs, {} from "node:fs";
2
2
  import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
4
5
  import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
5
6
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
6
7
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
@@ -166,7 +167,7 @@ async function ensurePrivateDirectory(rootDir, targetDir, mode) {
166
167
  catch (error) {
167
168
  if (error.code !== "ENOENT")
168
169
  throw error;
169
- await fsp.mkdir(path.dirname(resolvedRoot), { recursive: true, mode });
170
+ await fsp.mkdir(recursiveMkdirPath(path.dirname(resolvedRoot)), { recursive: true, mode });
170
171
  createdRoot = await createPrivateDirectory(resolvedRoot, mode);
171
172
  }
172
173
  rootStat = await inspectPrivateDirectory(resolvedRoot, "root");
@@ -1 +1 @@
1
- {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAOA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA0DjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
1
+ {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAQA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA0DjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
@@ -4,6 +4,7 @@ import { readFileHandleBounded } from "./bounded-read.js";
4
4
  import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { resolveReadOpenFlags } from "./read-open-flags.js";
7
+ import { realpathSync } from "./realpath.js";
7
8
  import { inspectFileIdentity } from "./strict-file-identity.js";
8
9
  import { assertSecretFilePreview, resolveSecretReadPolicy, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
9
10
  export async function readSecretFile(filePath, label, options = {}) {
@@ -29,7 +30,7 @@ export async function readSecretFile(filePath, label, options = {}) {
29
30
  let handle;
30
31
  let raw;
31
32
  try {
32
- const realPath = fsSync.realpathSync.native(resolvedPath);
33
+ const realPath = realpathSync.native(resolvedPath);
33
34
  assertNoUnsafeDeviceReadPath(realPath);
34
35
  handle = await fs.open(realPath, resolveReadOpenFlags());
35
36
  const openedHandle = handle;
@@ -1 +1 @@
1
- {"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAa7C,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAI1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AAmMF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAe/B"}
1
+ {"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAc/D,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAI1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AAmNF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAuB/B"}
@@ -9,6 +9,7 @@ import { isWindowsDriveLetterPath, isWindowsNetworkPath } from "./local-file-acc
9
9
  import { isPathInside, isSymlinkOpenError } from "./path.js";
10
10
  import { formatPermissionErrorDetail } from "./permission-exec.js";
11
11
  import { resolveReadOpenFlags } from "./read-open-flags.js";
12
+ import { realpathSync } from "./realpath.js";
12
13
  import { inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, } from "./permissions.js";
13
14
  import { inspectFileIdentity } from "./strict-file-identity.js";
14
15
  import { scheduleTimeout } from "./timing.js";
@@ -20,6 +21,11 @@ function isAbsolutePathname(value) {
20
21
  function label(options) {
21
22
  return options.label ?? "Secure file";
22
23
  }
24
+ function assertNotHardlinked(options, stat) {
25
+ if (stat.nlink > 1n) {
26
+ throw new FsSafeError("hardlink", `${label(options)} must not be hardlinked: ${options.filePath}`);
27
+ }
28
+ }
23
29
  async function openSecureHandle(options, maxBytes) {
24
30
  assertNoUnsafeDeviceReadPath(options.filePath);
25
31
  if (isWindowsNetworkPath(options.filePath, "win32") && !options.trust?.allowNetworkPath) {
@@ -63,6 +69,7 @@ async function openSecureHandle(options, maxBytes) {
63
69
  throw new FsSafeError("not-file", `${label(options)} must be a file: ${options.filePath}`);
64
70
  }
65
71
  const openedIdentity = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }));
72
+ assertNotHardlinked(options, openedIdentity);
66
73
  await inspectFileIdentity(async () => {
67
74
  const pathStat = options.trust?.allowSymlink
68
75
  ? fsSync.statSync(options.filePath, { bigint: true })
@@ -70,14 +77,19 @@ async function openSecureHandle(options, maxBytes) {
70
77
  if (!options.trust?.allowSymlink && pathStat.isSymbolicLink()) {
71
78
  throw new FsSafeError("symlink", `${label(options)} must not be a symlink: ${options.filePath}`);
72
79
  }
80
+ assertNotHardlinked(options, pathStat);
73
81
  return pathStat;
74
82
  }, openedIdentity);
75
- const realPath = fsSync.realpathSync.native(options.filePath);
76
- await inspectFileIdentity(() => fsSync.statSync(realPath, { bigint: true }), openedIdentity);
83
+ const realPath = realpathSync.native(options.filePath);
84
+ await inspectFileIdentity(() => {
85
+ const realPathStat = fsSync.statSync(realPath, { bigint: true });
86
+ assertNotHardlinked(options, realPathStat);
87
+ return realPathStat;
88
+ }, openedIdentity);
77
89
  if (maxBytes !== undefined && openedStat.size > maxBytes) {
78
90
  throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${maxBytes}).`);
79
91
  }
80
- return { handle, pathStat: openedStat, realPath };
92
+ return { handle, identity: openedIdentity, pathStat: openedStat, realPath };
81
93
  }
82
94
  catch (err) {
83
95
  await handle.close().catch(() => undefined);
@@ -91,7 +103,7 @@ async function assertTrustedDirs(options, realPath) {
91
103
  const trusted = await Promise.all(options.trust.trustedDirs.map(async (dir) => {
92
104
  const resolved = path.resolve(dir);
93
105
  try {
94
- return fsSync.realpathSync.native(resolved);
106
+ return realpathSync.native(resolved);
95
107
  }
96
108
  catch {
97
109
  return resolved;
@@ -184,6 +196,11 @@ export async function readSecureFile(options) {
184
196
  await assertTrustedDirs(options, opened.realPath);
185
197
  const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath);
186
198
  const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs, maxBytes);
199
+ const finalIdentity = await inspectFileIdentity(() => fsSync.fstatSync(opened.handle.fd, { bigint: true }), opened.identity);
200
+ if (!finalIdentity.isFile()) {
201
+ throw new FsSafeError("not-file", `${label(options)} must remain a file: ${options.filePath}`);
202
+ }
203
+ assertNotHardlinked(options, finalIdentity);
187
204
  return { buffer, realPath: opened.realPath, stat: opened.pathStat, permissions };
188
205
  }
189
206
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"secure-temp-dir.d.ts","sourceRoot":"","sources":["../src/secure-temp-dir.ts"],"names":[],"mappings":"AAOA,KAAK,aAAa,GAAG;IACnB,WAAW,IAAI,OAAO,CAAC;IACvB,cAAc,IAAI,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAWF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,CA8JnF"}
1
+ {"version":3,"file":"secure-temp-dir.d.ts","sourceRoot":"","sources":["../src/secure-temp-dir.ts"],"names":[],"mappings":"AAQA,KAAK,aAAa,GAAG;IACnB,WAAW,IAAI,OAAO,CAAC;IACvB,cAAc,IAAI,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAWF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,CA+JnF"}
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import { tmpdir as getOsTmpDir } from "node:os";
3
3
  import path from "node:path";
4
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
4
5
  import { assertSafePathSegment } from "./safe-path-segment.js";
5
6
  function isNodeErrorWithCode(err, code) {
6
7
  return (typeof err === "object" &&
@@ -17,7 +18,7 @@ export function resolveSecureTempRoot(options) {
17
18
  const accessSync = options.accessSync ?? fs.accessSync;
18
19
  const chmodSync = options.chmodSync ?? fs.chmodSync;
19
20
  const lstatSync = options.lstatSync ?? fs.lstatSync;
20
- const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
21
+ const mkdirSync = options.mkdirSync ?? ((directory, mkdirOptions) => fs.mkdirSync(recursiveMkdirPath(directory), mkdirOptions));
21
22
  const warn = options.warn ?? ((message) => console.warn(message));
22
23
  const warningPrefix = options.warningPrefix ?? "[fs-safe]";
23
24
  const unsafeFallbackLabel = options.unsafeFallbackLabel ?? "secure temp dir";
@@ -1,6 +1,7 @@
1
1
  export declare function writeCallbackSibling<T>(params: {
2
2
  tempPath: string;
3
3
  write: (tempPath: string) => Promise<T>;
4
+ producerIsolation?: "private-directory";
4
5
  resolveFinalPath: (result: T) => string;
5
6
  mode?: number;
6
7
  /** Preserve the caller's historical best-effort mode behavior. */