@openclaw/fs-safe 0.10.0 → 0.12.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 (302) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/LICENSE +1 -0
  3. package/README.md +39 -6
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +5 -1
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +5 -1
  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 +8 -8
  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 +16 -9
  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 +54 -42
  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 +26 -9
  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 +18 -11
  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/device-path.d.ts.map +1 -1
  58. package/dist/device-path.js +5 -3
  59. package/dist/directory-durability.d.ts.map +1 -1
  60. package/dist/directory-durability.js +5 -4
  61. package/dist/directory-guard.d.ts +11 -1
  62. package/dist/directory-guard.d.ts.map +1 -1
  63. package/dist/directory-guard.js +53 -11
  64. package/dist/durability.d.ts +1 -1
  65. package/dist/durability.d.ts.map +1 -1
  66. package/dist/durability.js +1 -1
  67. package/dist/file-handle-transfer.d.ts +14 -0
  68. package/dist/file-handle-transfer.d.ts.map +1 -0
  69. package/dist/file-handle-transfer.js +64 -0
  70. package/dist/file-hash.d.ts +3 -0
  71. package/dist/file-hash.d.ts.map +1 -1
  72. package/dist/file-hash.js +99 -32
  73. package/dist/file-lock-sync.d.ts.map +1 -1
  74. package/dist/file-lock-sync.js +8 -4
  75. package/dist/file-store-boundary.d.ts.map +1 -1
  76. package/dist/file-store-boundary.js +7 -5
  77. package/dist/file-store-path.d.ts +3 -0
  78. package/dist/file-store-path.d.ts.map +1 -0
  79. package/dist/file-store-path.js +27 -0
  80. package/dist/file-store-prune.d.ts.map +1 -1
  81. package/dist/file-store-prune.js +15 -5
  82. package/dist/file-store-sync-write.d.ts.map +1 -1
  83. package/dist/file-store-sync-write.js +56 -44
  84. package/dist/file-store.d.ts.map +1 -1
  85. package/dist/file-store.js +2 -18
  86. package/dist/filename.d.ts +1 -0
  87. package/dist/filename.d.ts.map +1 -1
  88. package/dist/filename.js +32 -14
  89. package/dist/guarded-mkdir.d.ts +2 -0
  90. package/dist/guarded-mkdir.d.ts.map +1 -1
  91. package/dist/guarded-mkdir.js +52 -16
  92. package/dist/guest-dispatch-python.d.ts +2 -0
  93. package/dist/guest-dispatch-python.d.ts.map +1 -0
  94. package/dist/guest-dispatch-python.js +117 -0
  95. package/dist/guest-native-python.d.ts +4 -0
  96. package/dist/guest-native-python.d.ts.map +1 -0
  97. package/dist/guest-native-python.js +135 -0
  98. package/dist/guest.d.ts +9 -0
  99. package/dist/guest.d.ts.map +1 -0
  100. package/dist/guest.js +421 -0
  101. package/dist/index.d.ts +1 -1
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/install-path.d.ts +6 -0
  104. package/dist/install-path.d.ts.map +1 -1
  105. package/dist/install-path.js +16 -2
  106. package/dist/json-durable-queue-directory.js +3 -3
  107. package/dist/json-durable-queue-ownership.d.ts +2 -0
  108. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  109. package/dist/json-durable-queue-ownership.js +47 -3
  110. package/dist/json-durable-queue-read.d.ts +6 -0
  111. package/dist/json-durable-queue-read.d.ts.map +1 -0
  112. package/dist/json-durable-queue-read.js +59 -0
  113. package/dist/json-durable-queue.d.ts +1 -1
  114. package/dist/json-durable-queue.d.ts.map +1 -1
  115. package/dist/json-durable-queue.js +43 -84
  116. package/dist/json.d.ts.map +1 -1
  117. package/dist/json.js +2 -1
  118. package/dist/local-roots.d.ts.map +1 -1
  119. package/dist/local-roots.js +2 -1
  120. package/dist/move-path-stage.d.ts.map +1 -1
  121. package/dist/move-path-stage.js +2 -1
  122. package/dist/move-path.d.ts.map +1 -1
  123. package/dist/move-path.js +4 -3
  124. package/dist/mutation-authority.d.ts +1 -0
  125. package/dist/mutation-authority.d.ts.map +1 -1
  126. package/dist/mutation-authority.js +4 -4
  127. package/dist/native-binding.d.ts +14 -2
  128. package/dist/native-binding.d.ts.map +1 -1
  129. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  130. package/dist/native-pinned-write-windows.js +3 -2
  131. package/dist/native-pinned-write.d.ts.map +1 -1
  132. package/dist/native-pinned-write.js +2 -1
  133. package/dist/opened-realpath.d.ts.map +1 -1
  134. package/dist/opened-realpath.js +5 -4
  135. package/dist/output.d.ts +2 -0
  136. package/dist/output.d.ts.map +1 -1
  137. package/dist/output.js +2 -0
  138. package/dist/overwrite-file-handle.d.ts +8 -0
  139. package/dist/overwrite-file-handle.d.ts.map +1 -0
  140. package/dist/overwrite-file-handle.js +42 -0
  141. package/dist/path-case.d.ts +7 -0
  142. package/dist/path-case.d.ts.map +1 -0
  143. package/dist/path-case.js +136 -0
  144. package/dist/path-scope-lexical.d.ts +14 -0
  145. package/dist/path-scope-lexical.d.ts.map +1 -0
  146. package/dist/path-scope-lexical.js +27 -0
  147. package/dist/path.d.ts.map +1 -1
  148. package/dist/path.js +22 -3
  149. package/dist/permissions-windows.d.ts +1 -1
  150. package/dist/permissions-windows.d.ts.map +1 -1
  151. package/dist/permissions-windows.js +48 -6
  152. package/dist/pinned-open.d.ts.map +1 -1
  153. package/dist/pinned-open.js +3 -1
  154. package/dist/pinned-write.d.ts +2 -2
  155. package/dist/pinned-write.d.ts.map +1 -1
  156. package/dist/pinned-write.js +3 -1
  157. package/dist/private-temp-workspace.d.ts.map +1 -1
  158. package/dist/private-temp-workspace.js +6 -4
  159. package/dist/realpath.d.ts +4 -0
  160. package/dist/realpath.d.ts.map +1 -0
  161. package/dist/realpath.js +43 -0
  162. package/dist/recursive-mkdir-path.d.ts +3 -0
  163. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  164. package/dist/recursive-mkdir-path.js +8 -0
  165. package/dist/replace-directory.d.ts.map +1 -1
  166. package/dist/replace-directory.js +2 -1
  167. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  168. package/dist/replace-file-copy-fallback.js +23 -31
  169. package/dist/replace-file-copy-source.d.ts.map +1 -1
  170. package/dist/replace-file-copy-source.js +7 -12
  171. package/dist/replace-file-mode.d.ts +3 -0
  172. package/dist/replace-file-mode.d.ts.map +1 -0
  173. package/dist/replace-file-mode.js +10 -0
  174. package/dist/replace-file.d.ts +1 -0
  175. package/dist/replace-file.d.ts.map +1 -1
  176. package/dist/replace-file.js +14 -8
  177. package/dist/root-boundary.d.ts +25 -0
  178. package/dist/root-boundary.d.ts.map +1 -0
  179. package/dist/root-boundary.js +177 -0
  180. package/dist/root-context.d.ts.map +1 -1
  181. package/dist/root-context.js +40 -13
  182. package/dist/root-create-input.d.ts +10 -0
  183. package/dist/root-create-input.d.ts.map +1 -0
  184. package/dist/root-create-input.js +80 -0
  185. package/dist/root-directory-list.d.ts +3 -1
  186. package/dist/root-directory-list.d.ts.map +1 -1
  187. package/dist/root-directory-list.js +31 -5
  188. package/dist/root-entries.d.ts +11 -0
  189. package/dist/root-entries.d.ts.map +1 -0
  190. package/dist/root-entries.js +61 -0
  191. package/dist/root-errors.d.ts +5 -5
  192. package/dist/root-errors.d.ts.map +1 -1
  193. package/dist/root-errors.js +13 -12
  194. package/dist/root-impl.d.ts +13 -3
  195. package/dist/root-impl.d.ts.map +1 -1
  196. package/dist/root-impl.js +93 -70
  197. package/dist/root-move-preflight.d.ts +8 -0
  198. package/dist/root-move-preflight.d.ts.map +1 -0
  199. package/dist/root-move-preflight.js +16 -0
  200. package/dist/root-options.d.ts +12 -1
  201. package/dist/root-options.d.ts.map +1 -1
  202. package/dist/root-path-existing.d.ts +2 -0
  203. package/dist/root-path-existing.d.ts.map +1 -1
  204. package/dist/root-path-existing.js +14 -5
  205. package/dist/root-path-symlink.d.ts.map +1 -1
  206. package/dist/root-path-symlink.js +3 -2
  207. package/dist/root-path.d.ts +2 -0
  208. package/dist/root-path.d.ts.map +1 -1
  209. package/dist/root-path.js +54 -26
  210. package/dist/root-paths.d.ts +2 -6
  211. package/dist/root-paths.d.ts.map +1 -1
  212. package/dist/root-paths.js +24 -32
  213. package/dist/root-remove.d.ts +5 -0
  214. package/dist/root-remove.d.ts.map +1 -0
  215. package/dist/root-remove.js +286 -0
  216. package/dist/root-symlink-policy.d.ts +2 -1
  217. package/dist/root-symlink-policy.d.ts.map +1 -1
  218. package/dist/root-symlink-policy.js +2 -2
  219. package/dist/root-walk.d.ts.map +1 -1
  220. package/dist/root-walk.js +2 -1
  221. package/dist/root-write-mode.d.ts +2 -0
  222. package/dist/root-write-mode.d.ts.map +1 -1
  223. package/dist/root-write-mode.js +21 -7
  224. package/dist/root-write-verification.d.ts.map +1 -1
  225. package/dist/root-write-verification.js +12 -3
  226. package/dist/root.d.ts +2 -1
  227. package/dist/root.d.ts.map +1 -1
  228. package/dist/safe-path-segment.d.ts.map +1 -1
  229. package/dist/safe-path-segment.js +3 -1
  230. package/dist/secret-file.d.ts.map +1 -1
  231. package/dist/secret-file.js +2 -1
  232. package/dist/secret-read-async.d.ts.map +1 -1
  233. package/dist/secret-read-async.js +2 -1
  234. package/dist/secure-file-windows.d.ts +10 -0
  235. package/dist/secure-file-windows.d.ts.map +1 -0
  236. package/dist/secure-file-windows.js +186 -0
  237. package/dist/secure-file.d.ts.map +1 -1
  238. package/dist/secure-file.js +27 -7
  239. package/dist/secure-temp-dir.d.ts.map +1 -1
  240. package/dist/secure-temp-dir.js +2 -1
  241. package/dist/sibling-staged-file.d.ts +2 -0
  242. package/dist/sibling-staged-file.d.ts.map +1 -1
  243. package/dist/sibling-staged-file.js +49 -8
  244. package/dist/sibling-temp.d.ts +2 -0
  245. package/dist/sibling-temp.d.ts.map +1 -1
  246. package/dist/sibling-temp.js +8 -5
  247. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  248. package/dist/sidecar-lock-acquire.js +16 -5
  249. package/dist/sidecar-lock-policy.d.ts +2 -0
  250. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  251. package/dist/sidecar-lock-policy.js +17 -0
  252. package/dist/sidecar-lock.d.ts.map +1 -1
  253. package/dist/sidecar-lock.js +2 -3
  254. package/dist/staged-directory.d.ts.map +1 -1
  255. package/dist/staged-directory.js +4 -3
  256. package/dist/temp-target.d.ts +14 -12
  257. package/dist/temp-target.d.ts.map +1 -1
  258. package/dist/temp-target.js +15 -7
  259. package/dist/trash.d.ts.map +1 -1
  260. package/dist/trash.js +7 -5
  261. package/dist/unicode-path.d.ts +3 -0
  262. package/dist/unicode-path.d.ts.map +1 -0
  263. package/dist/unicode-path.js +13 -0
  264. package/dist/walk.d.ts.map +1 -1
  265. package/dist/walk.js +14 -12
  266. package/dist/write-file-handle.d.ts +1 -0
  267. package/dist/write-file-handle.d.ts.map +1 -1
  268. package/dist/write-file-handle.js +3 -2
  269. package/docs/advanced.md +7 -1
  270. package/docs/archive.md +31 -5
  271. package/docs/atomic.md +17 -1
  272. package/docs/config.md +1 -0
  273. package/docs/contributing.md +33 -2
  274. package/docs/copy.md +75 -6
  275. package/docs/directory-identity.md +85 -0
  276. package/docs/durability.md +40 -3
  277. package/docs/entries.md +109 -0
  278. package/docs/errors.md +3 -3
  279. package/docs/file-store.md +21 -0
  280. package/docs/filename.md +9 -2
  281. package/docs/guest.md +146 -0
  282. package/docs/in-place-write.md +81 -0
  283. package/docs/index.md +2 -0
  284. package/docs/install-path.md +59 -13
  285. package/docs/install.md +34 -0
  286. package/docs/native-helper.md +14 -5
  287. package/docs/native.md +18 -2
  288. package/docs/output.md +32 -6
  289. package/docs/path-case.md +64 -0
  290. package/docs/path-scope.md +1 -1
  291. package/docs/path.md +1 -1
  292. package/docs/permissions.md +37 -3
  293. package/docs/public-api.md +36 -2
  294. package/docs/root.md +33 -3
  295. package/docs/secure-file.md +17 -14
  296. package/docs/security-model.md +1 -1
  297. package/docs/sidecar-lock.md +12 -3
  298. package/docs/store.md +22 -1
  299. package/docs/temp.md +39 -6
  300. package/docs/types.md +1 -1
  301. package/docs/writing.md +153 -3
  302. package/package.json +14 -8
@@ -0,0 +1,186 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { recordFileObservationFailure } from "./file-observation.js";
3
+ import { getNativeBinding } from "./native.js";
4
+ const IDENTITY_RE = /^([0-9a-f]{8}):([0-9a-f]{16})$/;
5
+ const SID_RE = /^s-\d+-\d+(?:-\d+)+$/;
6
+ const OWNER_CLASSES = new Set(["current-user", "system", "administrators", "foreign"]);
7
+ const TRUSTED_OWNER_CLASSES = new Set(["current-user", "system", "administrators"]);
8
+ const SYSTEM_SID = "s-1-5-18";
9
+ const ADMINISTRATORS_SID = "s-1-5-32-544";
10
+ const WORLD_SIDS = new Set([
11
+ "s-1-1-0", "s-1-5-11", "s-1-5-32-545", "s-1-5-7",
12
+ "s-1-5-32-546", "s-1-5-4", "s-1-5-2",
13
+ ]);
14
+ const GENERIC_READ = 0x8000_0000;
15
+ const GENERIC_WRITE = 0x4000_0000;
16
+ const GENERIC_ALL = 0x1000_0000;
17
+ const DELETE_ACCESS = 0x0001_0000;
18
+ const WRITE_DAC = 0x0004_0000;
19
+ const WRITE_OWNER = 0x0008_0000;
20
+ const FILE_READ_DATA = 0x0000_0001;
21
+ const FILE_WRITE_DATA = 0x0000_0002;
22
+ const FILE_APPEND_DATA = 0x0000_0004;
23
+ const FILE_READ_EA = 0x0000_0008;
24
+ const FILE_WRITE_EA = 0x0000_0010;
25
+ const FILE_DELETE_CHILD = 0x0000_0040;
26
+ const FILE_READ_ATTRIBUTES = 0x0000_0080;
27
+ const FILE_WRITE_ATTRIBUTES = 0x0000_0100;
28
+ function permissionUnverified(message, cause) {
29
+ throw new FsSafeError("permission-unverified", message, cause === undefined ? {} : { cause });
30
+ }
31
+ function identityMismatch() {
32
+ const error = new FsSafeError("path-mismatch", "file identity changed or could not be verified");
33
+ recordFileObservationFailure(error, "identity");
34
+ throw error;
35
+ }
36
+ function isRecord(value) {
37
+ return typeof value === "object" && value !== null;
38
+ }
39
+ function isUint32(value) {
40
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff_ffff;
41
+ }
42
+ function parseIdentity(value) {
43
+ if (typeof value !== "string")
44
+ identityMismatch();
45
+ const match = IDENTITY_RE.exec(value);
46
+ if (!match)
47
+ identityMismatch();
48
+ return {
49
+ dev: BigInt(`0x${match[1]}`),
50
+ ino: BigInt(`0x${match[2]}`),
51
+ };
52
+ }
53
+ function validateFlags(value) {
54
+ if (!isRecord(value))
55
+ return false;
56
+ const raw = value.raw;
57
+ if (!isUint32(raw) || (raw & ~0xdf) !== 0)
58
+ return false;
59
+ const fields = [
60
+ ["objectInherit", 0x01], ["containerInherit", 0x02],
61
+ ["noPropagateInherit", 0x04], ["inheritOnly", 0x08], ["inherited", 0x10],
62
+ ["successfulAccess", 0x40], ["failedAccess", 0x80],
63
+ ];
64
+ return fields.every(([field, bit]) => typeof value[field] === "boolean" && value[field] === ((raw & bit) !== 0));
65
+ }
66
+ function validateAce(value) {
67
+ return isRecord(value) && typeof value.sid === "string" && SID_RE.test(value.sid) &&
68
+ isUint32(value.mask) && (value.aceType === "allow" || value.aceType === "deny") &&
69
+ validateFlags(value.flags);
70
+ }
71
+ function canRead(mask) {
72
+ return (mask & (GENERIC_ALL | GENERIC_READ | FILE_READ_DATA | FILE_READ_EA |
73
+ FILE_READ_ATTRIBUTES)) !== 0;
74
+ }
75
+ function canWrite(mask) {
76
+ return (mask & (GENERIC_ALL | GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA |
77
+ FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | FILE_DELETE_CHILD | DELETE_ACCESS |
78
+ WRITE_DAC | WRITE_OWNER)) !== 0;
79
+ }
80
+ function expectedOwnerClass(facts) {
81
+ if (facts.ownerSid === facts.currentUserSid)
82
+ return "current-user";
83
+ if (facts.ownerSid === SYSTEM_SID)
84
+ return "system";
85
+ if (facts.ownerSid === ADMINISTRATORS_SID)
86
+ return "administrators";
87
+ return "foreign";
88
+ }
89
+ function validateSecurity(value) {
90
+ if (!isRecord(value))
91
+ permissionUnverified("Windows descriptor ACL facts were malformed");
92
+ const facts = value;
93
+ const booleans = [
94
+ "worldWritable", "groupWritable", "worldReadable", "groupReadable",
95
+ "fallbackRequired", "daclPresent", "isLocal", "aceListComplete",
96
+ ];
97
+ if (!booleans.every((field) => typeof facts[field] === "boolean") ||
98
+ typeof facts.ownerSid !== "string" || !SID_RE.test(facts.ownerSid) ||
99
+ typeof facts.currentUserSid !== "string" || !SID_RE.test(facts.currentUserSid) ||
100
+ typeof facts.ownerClass !== "string" || !OWNER_CLASSES.has(facts.ownerClass) ||
101
+ !Array.isArray(facts.unsupportedAceTypes) ||
102
+ !facts.unsupportedAceTypes.every(isUint32) ||
103
+ !Array.isArray(facts.aces) || !facts.aces.every(validateAce)) {
104
+ permissionUnverified("Windows descriptor ACL facts were malformed");
105
+ }
106
+ if (facts.ownerClass !== expectedOwnerClass(facts)) {
107
+ permissionUnverified("Windows descriptor owner facts were inconsistent");
108
+ }
109
+ if (facts.fallbackRequired || !facts.isLocal || !facts.aceListComplete ||
110
+ facts.unsupportedAceTypes.length !== 0) {
111
+ permissionUnverified("Windows descriptor ACL facts were incomplete or unsupported");
112
+ }
113
+ if (!facts.daclPresent && facts.aces.length !== 0) {
114
+ permissionUnverified("Windows descriptor DACL facts were inconsistent");
115
+ }
116
+ return facts;
117
+ }
118
+ function validatePermissionSummary(facts) {
119
+ let worldReadable = !facts.daclPresent;
120
+ let worldWritable = !facts.daclPresent;
121
+ let groupReadable = false;
122
+ let groupWritable = false;
123
+ const trustedSids = new Set([facts.currentUserSid, SYSTEM_SID, ADMINISTRATORS_SID]);
124
+ for (const ace of facts.aces) {
125
+ if (ace.aceType !== "allow" || ace.flags.inheritOnly || trustedSids.has(ace.sid))
126
+ continue;
127
+ const world = WORLD_SIDS.has(ace.sid);
128
+ if (world) {
129
+ worldReadable ||= canRead(ace.mask);
130
+ worldWritable ||= canWrite(ace.mask);
131
+ }
132
+ else {
133
+ groupReadable ||= canRead(ace.mask);
134
+ groupWritable ||= canWrite(ace.mask);
135
+ }
136
+ }
137
+ if (facts.worldReadable !== worldReadable || facts.worldWritable !== worldWritable ||
138
+ facts.groupReadable !== groupReadable || facts.groupWritable !== groupWritable) {
139
+ permissionUnverified("Windows descriptor ACL summary was inconsistent");
140
+ }
141
+ }
142
+ export function inspectSecureWindowsDescriptor(params) {
143
+ let native;
144
+ try {
145
+ native = getNativeBinding();
146
+ }
147
+ catch (cause) {
148
+ permissionUnverified("Windows descriptor ACL verification requires the matching native helper", cause);
149
+ }
150
+ const inspect = native?.inspectWindowsSecureFileHandle;
151
+ if (typeof inspect !== "function") {
152
+ permissionUnverified("Windows descriptor ACL verification requires an up-to-date native helper");
153
+ }
154
+ let result;
155
+ try {
156
+ result = inspect.call(native, params.fd);
157
+ }
158
+ catch (cause) {
159
+ permissionUnverified("Windows descriptor ACL verification failed", cause);
160
+ }
161
+ if (!isRecord(result))
162
+ permissionUnverified("Windows descriptor ACL facts were malformed");
163
+ const observed = parseIdentity(result.identity);
164
+ if (observed.dev !== params.identity.dev || observed.ino !== params.identity.ino) {
165
+ identityMismatch();
166
+ }
167
+ const facts = validateSecurity(result.security);
168
+ validatePermissionSummary(facts);
169
+ return {
170
+ ok: true,
171
+ isSymlink: false,
172
+ isDir: params.stat.isDirectory(),
173
+ mode: typeof params.stat.mode === "number" ? params.stat.mode : null,
174
+ bits: null,
175
+ source: "windows-acl",
176
+ worldWritable: facts.worldWritable,
177
+ groupWritable: facts.groupWritable,
178
+ worldReadable: facts.worldReadable,
179
+ groupReadable: facts.groupReadable,
180
+ ownerSid: facts.ownerSid,
181
+ ownerTrusted: TRUSTED_OWNER_CLASSES.has(facts.ownerClass),
182
+ aclSummary: `native descriptor owner=${facts.ownerClass} world=` +
183
+ `${facts.worldReadable ? "r" : "-"}${facts.worldWritable ? "w" : "-"} ` +
184
+ `group=${facts.groupReadable ? "r" : "-"}${facts.groupWritable ? "w" : "-"}`,
185
+ };
186
+ }
@@ -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;AAK1B,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;AAuNF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA6B/B"}
@@ -9,8 +9,10 @@ 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";
15
+ import { inspectSecureWindowsDescriptor } from "./secure-file-windows.js";
14
16
  import { scheduleTimeout } from "./timing.js";
15
17
  function isAbsolutePathname(value) {
16
18
  return (path.isAbsolute(value) ||
@@ -20,6 +22,11 @@ function isAbsolutePathname(value) {
20
22
  function label(options) {
21
23
  return options.label ?? "Secure file";
22
24
  }
25
+ function assertNotHardlinked(options, stat) {
26
+ if (stat.nlink > 1n) {
27
+ throw new FsSafeError("hardlink", `${label(options)} must not be hardlinked: ${options.filePath}`);
28
+ }
29
+ }
23
30
  async function openSecureHandle(options, maxBytes) {
24
31
  assertNoUnsafeDeviceReadPath(options.filePath);
25
32
  if (isWindowsNetworkPath(options.filePath, "win32") && !options.trust?.allowNetworkPath) {
@@ -63,6 +70,7 @@ async function openSecureHandle(options, maxBytes) {
63
70
  throw new FsSafeError("not-file", `${label(options)} must be a file: ${options.filePath}`);
64
71
  }
65
72
  const openedIdentity = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }));
73
+ assertNotHardlinked(options, openedIdentity);
66
74
  await inspectFileIdentity(async () => {
67
75
  const pathStat = options.trust?.allowSymlink
68
76
  ? fsSync.statSync(options.filePath, { bigint: true })
@@ -70,14 +78,19 @@ async function openSecureHandle(options, maxBytes) {
70
78
  if (!options.trust?.allowSymlink && pathStat.isSymbolicLink()) {
71
79
  throw new FsSafeError("symlink", `${label(options)} must not be a symlink: ${options.filePath}`);
72
80
  }
81
+ assertNotHardlinked(options, pathStat);
73
82
  return pathStat;
74
83
  }, openedIdentity);
75
- const realPath = fsSync.realpathSync.native(options.filePath);
76
- await inspectFileIdentity(() => fsSync.statSync(realPath, { bigint: true }), openedIdentity);
84
+ const realPath = realpathSync.native(options.filePath);
85
+ await inspectFileIdentity(() => {
86
+ const realPathStat = fsSync.statSync(realPath, { bigint: true });
87
+ assertNotHardlinked(options, realPathStat);
88
+ return realPathStat;
89
+ }, openedIdentity);
77
90
  if (maxBytes !== undefined && openedStat.size > maxBytes) {
78
91
  throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${maxBytes}).`);
79
92
  }
80
- return { handle, pathStat: openedStat, realPath };
93
+ return { handle, identity: openedIdentity, pathStat: openedStat, realPath };
81
94
  }
82
95
  catch (err) {
83
96
  await handle.close().catch(() => undefined);
@@ -91,7 +104,7 @@ async function assertTrustedDirs(options, realPath) {
91
104
  const trusted = await Promise.all(options.trust.trustedDirs.map(async (dir) => {
92
105
  const resolved = path.resolve(dir);
93
106
  try {
94
- return fsSync.realpathSync.native(resolved);
107
+ return realpathSync.native(resolved);
95
108
  }
96
109
  catch {
97
110
  return resolved;
@@ -116,13 +129,15 @@ function inspectOpenedPermissions(stat, platform) {
116
129
  groupReadable: isGroupReadable(bits),
117
130
  };
118
131
  }
119
- async function assertSecurePermissions(options, stat, realPath) {
132
+ async function assertSecurePermissions(options, stat, realPath, identity, fd) {
120
133
  if (options.permissions?.allowInsecure) {
121
134
  return undefined;
122
135
  }
123
136
  const platform = options.inject?.platform ?? process.platform;
124
137
  const permissions = platform === "win32"
125
- ? await inspectPathPermissions(realPath, options.inject)
138
+ ? process.platform === "win32"
139
+ ? inspectSecureWindowsDescriptor({ fd, identity, stat })
140
+ : await inspectPathPermissions(realPath, options.inject)
126
141
  : inspectOpenedPermissions(stat, platform);
127
142
  const reason = permissions.error ? `: ${formatPermissionErrorDetail(permissions.error)}` : "";
128
143
  const diagnostics = {
@@ -182,8 +197,13 @@ export async function readSecureFile(options) {
182
197
  const opened = await openSecureHandle(options, maxBytes);
183
198
  try {
184
199
  await assertTrustedDirs(options, opened.realPath);
185
- const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath);
200
+ const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath, opened.identity, opened.handle.fd);
186
201
  const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs, maxBytes);
202
+ const finalIdentity = await inspectFileIdentity(() => fsSync.fstatSync(opened.handle.fd, { bigint: true }), opened.identity);
203
+ if (!finalIdentity.isFile()) {
204
+ throw new FsSafeError("not-file", `${label(options)} must remain a file: ${options.filePath}`);
205
+ }
206
+ assertNotHardlinked(options, finalIdentity);
187
207
  return { buffer, realPath: opened.realPath, stat: opened.pathStat, permissions };
188
208
  }
189
209
  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,8 @@
1
+ export declare function assertCallbackTempPathDeviceSafe(tempPath: string): void;
1
2
  export declare function writeCallbackSibling<T>(params: {
2
3
  tempPath: string;
3
4
  write: (tempPath: string) => Promise<T>;
5
+ producerIsolation?: "private-directory";
4
6
  resolveFinalPath: (result: T) => string;
5
7
  mode?: number;
6
8
  /** Preserve the caller's historical best-effort mode behavior. */
@@ -1 +1 @@
1
- {"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAmCA,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAyG3C"}
1
+ {"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAuCA,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAIvE;AAmCD,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAgH3C"}
@@ -2,12 +2,15 @@ import { syncFileBestEffort } from "./file-sync.js";
2
2
  import fsSync, {} from "node:fs";
3
3
  import fs, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
5
+ import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, createAsyncDirectoryGuard, } from "./directory-guard.js";
6
6
  import { syncDirectoryBestEffort } from "./directory-durability.js";
7
+ import { isWindowsReservedDeviceName } from "./device-path.js";
7
8
  import { FsSafeError } from "./errors.js";
8
9
  import { resolveReadOpenFlags } from "./read-open-flags.js";
10
+ import { root } from "./root.js";
9
11
  import { inspectFileIdentity } from "./strict-file-identity.js";
10
12
  import { registerTempPathForExit } from "./temp-cleanup.js";
13
+ import { createOwnedTempFile } from "./temp-target.js";
11
14
  import { serializePathWrite } from "./write-queue.js";
12
15
  function assertRegularFile(stat) {
13
16
  if (stat.isSymbolicLink()) {
@@ -27,17 +30,45 @@ async function inspectStage(inspect, expected) {
27
30
  return stat;
28
31
  }, expected);
29
32
  }
33
+ export function assertCallbackTempPathDeviceSafe(tempPath) {
34
+ if (isWindowsReservedDeviceName(tempPath)) {
35
+ throw new FsSafeError("invalid-path", "callback temp path uses a reserved Windows device name");
36
+ }
37
+ }
38
+ // Own the workspace before the producer can leave partial output. The finished
39
+ // file still enters the ordinary sibling admission and publication lifecycle.
40
+ async function writeIsolatedProducer(params) {
41
+ const targetRoot = await root(path.dirname(params.tempPath));
42
+ const { target, identity } = await createOwnedTempFile({
43
+ rootDir: targetRoot.rootReal,
44
+ prefix: "fs-safe-output",
45
+ fileName: path.basename(params.tempPath),
46
+ });
47
+ const assertCurrent = () => {
48
+ params.assertParent();
49
+ assertDirectoryIdentitySync(target.dir, { ...identity, realPath: target.dir });
50
+ };
51
+ try {
52
+ assertCurrent();
53
+ const result = await params.write(target.path);
54
+ assertCurrent();
55
+ await targetRoot.move(path.relative(targetRoot.rootReal, target.path), path.basename(params.tempPath), {
56
+ assertBeforeMutation: assertCurrent,
57
+ });
58
+ return result;
59
+ }
60
+ finally {
61
+ await target.cleanup();
62
+ }
63
+ }
30
64
  // Callback paths are not owned until all three admission observations agree.
31
65
  // Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
32
66
  // Read/write access is needed only when the caller requests file synchronization.
33
67
  export async function writeCallbackSibling(params) {
68
+ assertCallbackTempPathDeviceSafe(params.tempPath);
34
69
  const parent = path.dirname(params.tempPath);
35
- const guard = await createAsyncDirectoryGuard(parent);
36
- const parentIdentity = await inspectFileIdentity(() => fsSync.lstatSync(parent, { bigint: true }));
37
- const assertParent = async () => {
38
- await assertAsyncDirectoryGuard(guard);
39
- await inspectFileIdentity(() => fsSync.lstatSync(parent, { bigint: true }), parentIdentity);
40
- };
70
+ const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
71
+ const assertParent = () => assertAsyncDirectoryGuard(guard);
41
72
  let handle;
42
73
  let identity;
43
74
  let unregister;
@@ -54,7 +85,17 @@ export async function writeCallbackSibling(params) {
54
85
  }
55
86
  };
56
87
  try {
57
- const result = await params.write(params.tempPath);
88
+ const result = params.producerIsolation === "private-directory"
89
+ ? await writeIsolatedProducer({
90
+ tempPath: params.tempPath,
91
+ write: params.write,
92
+ assertParent: () => assertDirectoryIdentitySync(parent, {
93
+ dev: guard.stat.dev,
94
+ ino: guard.stat.ino,
95
+ realPath: guard.realPath,
96
+ }),
97
+ })
98
+ : await params.write(params.tempPath);
58
99
  await assertParent();
59
100
  const before = await inspectPath(params.tempPath);
60
101
  try {
@@ -1,6 +1,8 @@
1
1
  export type WriteSiblingTempFileOptions<T> = {
2
2
  dir: string;
3
3
  writeTemp: (tempPath: string) => Promise<T>;
4
+ /** Own a private sibling workspace before invoking the producer. */
5
+ producerIsolation?: "private-directory";
4
6
  resolveFinalPath: (result: T) => string;
5
7
  tempPrefix?: string;
6
8
  dirMode?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAoBxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChB"}
1
+ {"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAqBxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ChB"}
@@ -1,14 +1,15 @@
1
1
  import crypto, { randomUUID } from "node:crypto";
2
- import fsSync from "node:fs";
3
2
  import fs from "node:fs/promises";
4
3
  import path from "node:path";
5
4
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
6
5
  import { fitFileNameToPortableComponent, sanitizeUntrustedFileName } from "./filename.js";
7
6
  import { applyDirectoryMode } from "./replace-file-descriptor.js";
7
+ import { realpathSync } from "./realpath.js";
8
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
8
9
  import { root } from "./root.js";
9
10
  import { assertSafePathPrefix } from "./safe-path-segment.js";
10
11
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
11
- import { writeCallbackSibling } from "./sibling-staged-file.js";
12
+ import { assertCallbackTempPathDeviceSafe, writeCallbackSibling } from "./sibling-staged-file.js";
12
13
  import { tempFile } from "./temp-target.js";
13
14
  import { getFsSafeTestHooks } from "./test-hooks.js";
14
15
  function buildTempPath(dir, tempPrefix) {
@@ -19,7 +20,7 @@ function buildTempPath(dir, tempPrefix) {
19
20
  }
20
21
  export async function writeSiblingTempFile(options) {
21
22
  const dir = path.resolve(options.dir);
22
- await fs.mkdir(dir, { recursive: true, mode: options.dirMode ?? 0o700 });
23
+ await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: options.dirMode ?? 0o700 });
23
24
  if (options.chmodDir !== false) {
24
25
  await applyDirectoryMode({
25
26
  fsModule: fs,
@@ -31,6 +32,7 @@ export async function writeSiblingTempFile(options) {
31
32
  return await writeCallbackSibling({
32
33
  tempPath: buildTempPath(dir, options.tempPrefix),
33
34
  write: options.writeTemp,
35
+ producerIsolation: options.producerIsolation,
34
36
  resolveFinalPath: options.resolveFinalPath,
35
37
  mode: options.mode,
36
38
  ignoreModeError: true,
@@ -55,7 +57,7 @@ function buildSiblingTempPath(params) {
55
57
  export async function writeViaSiblingTempPath(params) {
56
58
  let rootDir;
57
59
  try {
58
- rootDir = fsSync.realpathSync.native(path.resolve(params.rootDir));
60
+ rootDir = realpathSync.native(path.resolve(params.rootDir));
59
61
  }
60
62
  catch {
61
63
  rootDir = path.resolve(params.rootDir);
@@ -63,7 +65,7 @@ export async function writeViaSiblingTempPath(params) {
63
65
  const requestedTargetPath = path.resolve(params.targetPath);
64
66
  let targetPath;
65
67
  try {
66
- const realDir = fsSync.realpathSync.native(path.dirname(requestedTargetPath));
68
+ const realDir = realpathSync.native(path.dirname(requestedTargetPath));
67
69
  targetPath = path.join(realDir, path.basename(requestedTargetPath));
68
70
  }
69
71
  catch {
@@ -91,6 +93,7 @@ export async function writeViaSiblingTempPath(params) {
91
93
  fallbackFileName: params.fallbackFileName ?? "output.bin",
92
94
  tempPrefix: params.tempPrefix ?? ".fs-safe-output-",
93
95
  });
96
+ assertCallbackTempPathDeviceSafe(tempPath);
94
97
  await getFsSafeTestHooks()?.beforeSiblingTempWrite?.(tempPath);
95
98
  await params.writeTemp(tempPath);
96
99
  await assertAsyncDirectoryGuard(rootGuard);
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAQA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAqT5B"}
1
+ {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA6T5B"}
@@ -5,9 +5,11 @@ import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { fileObservation } from "./file-observation.js";
7
7
  import { readFileHandleBounded } from "./bounded-read.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
8
10
  import { openSidecarRoot } from "./sidecar-lock-root.js";
9
11
  import { createNativeExclusiveFile } from "./native-operations.js";
10
- import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
12
+ import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockStaleMs, validateSidecarLockRetryOptions, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
11
13
  import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
12
14
  import { createSuppressedError } from "./suppressed-error.js";
13
15
  import { sleep } from "./timing.js";
@@ -22,9 +24,9 @@ async function resolveNormalizedTargetPath(targetPath, lockRoot) {
22
24
  await lockRoot.resolve(".");
23
25
  return path.join(parent, path.basename(resolved));
24
26
  }
25
- await fs.mkdir(dir, { recursive: true });
27
+ await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
26
28
  try {
27
- return path.join(fsSync.realpathSync.native(dir), path.basename(resolved));
29
+ return path.join(realpathSync.native(dir), path.basename(resolved));
28
30
  }
29
31
  catch {
30
32
  return resolved;
@@ -34,6 +36,8 @@ export async function acquireSidecarLock(options, context) {
34
36
  const retry = options.retry ?? {};
35
37
  validateSidecarLockRetryOptions(retry);
36
38
  validateSidecarLockTimeoutMs(options.timeoutMs);
39
+ validateSidecarLockStaleMs(options.staleMs);
40
+ validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
37
41
  context.ensureExitCleanupRegistered();
38
42
  const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath, options.lockRoot);
39
43
  const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
@@ -54,7 +58,7 @@ export async function acquireSidecarLock(options, context) {
54
58
  options.reentrantOwner !== undefined &&
55
59
  held.reentrantOwner !== undefined &&
56
60
  options.reentrantOwner === held.reentrantOwner) {
57
- held.refCount += 1;
61
+ held.refCount = (held.refCount ?? 1) + 1;
58
62
  // Retention is monotonic: any same-owner request to keep the sidecar on
59
63
  // exit upgrades the held lock; a later default acquisition never revokes it.
60
64
  if (options.retainOnExit === true) {
@@ -153,7 +157,7 @@ export async function acquireSidecarLock(options, context) {
153
157
  try {
154
158
  handle =
155
159
  (await createNativeExclusiveFile(lockPath, 0o600)) ??
156
- (await fs.open(lockPath, "wx"));
160
+ (await fs.open(lockPath, "wx", 0o600));
157
161
  }
158
162
  catch (createError) {
159
163
  lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
@@ -194,7 +198,11 @@ export async function acquireSidecarLock(options, context) {
194
198
  const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
195
199
  const interval = options.compromiseCheckIntervalMs;
196
200
  if (options.onCompromised && interval !== undefined && interval > 0) {
201
+ let compromiseCheckInFlight = false;
197
202
  createdHeld.compromiseTimer = setInterval(() => {
203
+ if (compromiseCheckInFlight)
204
+ return;
205
+ compromiseCheckInFlight = true;
198
206
  void returnedHandle
199
207
  .verifyStillHeld()
200
208
  .catch(() => false)
@@ -204,6 +212,9 @@ export async function acquireSidecarLock(options, context) {
204
212
  createdHeld.compromiseTimer = undefined;
205
213
  options.onCompromised?.({ lockPath, normalizedTargetPath });
206
214
  }
215
+ })
216
+ .finally(() => {
217
+ compromiseCheckInFlight = false;
207
218
  });
208
219
  }, interval);
209
220
  createdHeld.compromiseTimer.unref();
@@ -1,6 +1,8 @@
1
1
  import type { SidecarLockRetryOptions } from "./sidecar-lock-types.js";
2
2
  export declare function validateSidecarLockRetryOptions(retry: SidecarLockRetryOptions): void;
3
3
  export declare function validateSidecarLockTimeoutMs(timeoutMs: number | undefined): void;
4
+ export declare function validateSidecarLockStaleMs(staleMs: number | undefined): void;
5
+ export declare function validateSidecarLockCompromiseCheckIntervalMs(intervalMs: number | undefined): void;
4
6
  export declare function computeSidecarLockDelayMs(retry: SidecarLockRetryOptions, attempt: number): number;
5
7
  export declare const maxTransientLockDenials = 8;
6
8
  export declare function isTransientLockFileDenial(error: unknown, lockPath: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAQvE,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAkBpF;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
1
+ {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAUvE,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAkBpF;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAO5E;AAED,wBAAgB,4CAA4C,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAOjG;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
@@ -1,4 +1,5 @@
1
1
  import fsSync from "node:fs";
2
+ const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
2
3
  function assertFiniteNonNegative(value, label) {
3
4
  if (!Number.isFinite(value) || value < 0) {
4
5
  throw new RangeError(`${label} must be a finite non-negative number`);
@@ -27,6 +28,22 @@ export function validateSidecarLockTimeoutMs(timeoutMs) {
27
28
  return;
28
29
  assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
29
30
  }
31
+ export function validateSidecarLockStaleMs(staleMs) {
32
+ // The lower-level sidecar manager historically treats an omitted threshold
33
+ // as disabling age-based reclamation. Its public type requires a number, but
34
+ // retain that fail-closed runtime behavior for JavaScript callers and older
35
+ // consumers.
36
+ if (staleMs === undefined || staleMs === Number.POSITIVE_INFINITY)
37
+ return;
38
+ assertFiniteNonNegative(staleMs, "lock staleMs");
39
+ }
40
+ export function validateSidecarLockCompromiseCheckIntervalMs(intervalMs) {
41
+ if (intervalMs === undefined || intervalMs === 0)
42
+ return;
43
+ if (!Number.isFinite(intervalMs) || intervalMs < 1 || intervalMs > MAX_TIMER_DELAY_MS) {
44
+ throw new RangeError(`lock compromiseCheckIntervalMs must be 0 or a finite number between 1 and ${MAX_TIMER_DELAY_MS}`);
45
+ }
46
+ }
30
47
  export function computeSidecarLockDelayMs(retry, attempt) {
31
48
  validateSidecarLockRetryOptions(retry);
32
49
  const minTimeout = retry.minTimeout ?? 50;
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAsIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAuFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
1
+ {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAmIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAyFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}