@openclaw/fs-safe 0.5.6 → 0.6.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 (145) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +16 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts +2 -0
  7. package/dist/archive-crc32.d.ts.map +1 -0
  8. package/dist/archive-crc32.js +14 -0
  9. package/dist/archive-kind.js +2 -2
  10. package/dist/archive-native.d.ts.map +1 -1
  11. package/dist/archive-native.js +7 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +24 -5
  14. package/dist/archive-tar-meta.d.ts.map +1 -1
  15. package/dist/archive-tar-meta.js +53 -6
  16. package/dist/archive-tar-pax.d.ts +8 -0
  17. package/dist/archive-tar-pax.d.ts.map +1 -0
  18. package/dist/archive-tar-pax.js +100 -0
  19. package/dist/archive-tar-runtime.d.ts +1 -0
  20. package/dist/archive-tar-runtime.d.ts.map +1 -1
  21. package/dist/archive-zip-admission.d.ts +7 -0
  22. package/dist/archive-zip-admission.d.ts.map +1 -0
  23. package/dist/archive-zip-admission.js +60 -0
  24. package/dist/archive-zip-count.d.ts +2 -0
  25. package/dist/archive-zip-count.d.ts.map +1 -0
  26. package/dist/archive-zip-count.js +140 -0
  27. package/dist/archive-zip-directory.d.ts +8 -0
  28. package/dist/archive-zip-directory.d.ts.map +1 -0
  29. package/dist/archive-zip-directory.js +224 -0
  30. package/dist/archive-zip-integrity.d.ts.map +1 -1
  31. package/dist/archive-zip-integrity.js +1 -14
  32. package/dist/archive-zip-names.d.ts +12 -0
  33. package/dist/archive-zip-names.d.ts.map +1 -0
  34. package/dist/archive-zip-names.js +99 -0
  35. package/dist/archive-zip-preflight.d.ts +1 -1
  36. package/dist/archive-zip-preflight.d.ts.map +1 -1
  37. package/dist/archive-zip-preflight.js +8 -145
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +32 -23
  40. package/dist/file-hash.d.ts.map +1 -1
  41. package/dist/file-hash.js +25 -19
  42. package/dist/file-lock-sync.d.ts.map +1 -1
  43. package/dist/file-lock-sync.js +19 -7
  44. package/dist/native-binding.d.ts +3 -0
  45. package/dist/native-binding.d.ts.map +1 -1
  46. package/dist/native-operations.d.ts +4 -1
  47. package/dist/native-operations.d.ts.map +1 -1
  48. package/dist/native-operations.js +22 -6
  49. package/dist/native-pinned-write-windows.d.ts +8 -0
  50. package/dist/native-pinned-write-windows.d.ts.map +1 -0
  51. package/dist/native-pinned-write-windows.js +92 -0
  52. package/dist/native-pinned-write.d.ts.map +1 -1
  53. package/dist/native-pinned-write.js +136 -127
  54. package/dist/native-staged-file.d.ts +24 -0
  55. package/dist/native-staged-file.d.ts.map +1 -0
  56. package/dist/native-staged-file.js +337 -0
  57. package/dist/native.d.ts.map +1 -1
  58. package/dist/native.js +4 -4
  59. package/dist/opened-realpath.d.ts +2 -0
  60. package/dist/opened-realpath.d.ts.map +1 -1
  61. package/dist/opened-realpath.js +12 -7
  62. package/dist/owner-dacl.d.ts.map +1 -1
  63. package/dist/owner-dacl.js +2 -1
  64. package/dist/permission-exec.d.ts +19 -0
  65. package/dist/permission-exec.d.ts.map +1 -1
  66. package/dist/permission-exec.js +57 -11
  67. package/dist/permissions-public.d.ts +1 -1
  68. package/dist/permissions-public.d.ts.map +1 -1
  69. package/dist/permissions-windows.d.ts +3 -0
  70. package/dist/permissions-windows.d.ts.map +1 -1
  71. package/dist/permissions-windows.js +16 -5
  72. package/dist/permissions.d.ts +5 -0
  73. package/dist/permissions.d.ts.map +1 -1
  74. package/dist/pinned-write.d.ts +6 -0
  75. package/dist/pinned-write.d.ts.map +1 -1
  76. package/dist/pinned-write.js +19 -29
  77. package/dist/private-directory.d.ts.map +1 -1
  78. package/dist/private-directory.js +3 -2
  79. package/dist/root-impl.d.ts.map +1 -1
  80. package/dist/root-impl.js +78 -72
  81. package/dist/root-paths.d.ts +11 -14
  82. package/dist/root-paths.d.ts.map +1 -1
  83. package/dist/root-paths.js +36 -27
  84. package/dist/root-write-verification.d.ts +11 -0
  85. package/dist/root-write-verification.d.ts.map +1 -0
  86. package/dist/root-write-verification.js +91 -0
  87. package/dist/secret-file.d.ts +1 -6
  88. package/dist/secret-file.d.ts.map +1 -1
  89. package/dist/secret-file.js +43 -117
  90. package/dist/secret-read-async.d.ts +1 -1
  91. package/dist/secret-read-async.d.ts.map +1 -1
  92. package/dist/secret-read-async.js +45 -67
  93. package/dist/secret-read-policy.d.ts +13 -0
  94. package/dist/secret-read-policy.d.ts.map +1 -0
  95. package/dist/secret-read-policy.js +28 -0
  96. package/dist/secret.d.ts +2 -1
  97. package/dist/secret.d.ts.map +1 -1
  98. package/dist/secret.js +2 -1
  99. package/dist/secure-file.d.ts.map +1 -1
  100. package/dist/secure-file.js +25 -16
  101. package/dist/staged-directory.d.ts +16 -0
  102. package/dist/staged-directory.d.ts.map +1 -0
  103. package/dist/staged-directory.js +60 -0
  104. package/dist/staged-file-types.d.ts +56 -0
  105. package/dist/staged-file-types.d.ts.map +1 -0
  106. package/dist/staged-file-types.js +1 -0
  107. package/dist/staged-file.d.ts +10 -0
  108. package/dist/staged-file.d.ts.map +1 -0
  109. package/dist/staged-file.js +15 -0
  110. package/dist/strict-file-identity.d.ts +6 -0
  111. package/dist/strict-file-identity.d.ts.map +1 -0
  112. package/dist/strict-file-identity.js +48 -0
  113. package/dist/windows-owner.d.ts +3 -0
  114. package/dist/windows-owner.d.ts.map +1 -1
  115. package/dist/windows-owner.js +10 -2
  116. package/docs/advanced.md +11 -1
  117. package/docs/archive.md +62 -15
  118. package/docs/config.md +7 -0
  119. package/docs/contributing.md +45 -7
  120. package/docs/durability.md +17 -4
  121. package/docs/errors.md +16 -1
  122. package/docs/index.md +3 -1
  123. package/docs/install.md +15 -7
  124. package/docs/migrating-to-0.5.md +7 -7
  125. package/docs/migrating-to-0.6.md +43 -0
  126. package/docs/native-helper.md +15 -5
  127. package/docs/native.md +40 -11
  128. package/docs/path-scope.md +28 -2
  129. package/docs/permissions.md +13 -2
  130. package/docs/root.md +1 -1
  131. package/docs/secret-file.md +8 -0
  132. package/docs/secure-file.md +17 -1
  133. package/docs/security-model.md +15 -1
  134. package/docs/sidecar-lock.md +11 -1
  135. package/docs/staged-file.md +178 -0
  136. package/docs/testing.md +1 -1
  137. package/docs/writing.md +19 -0
  138. package/package.json +15 -8
  139. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  140. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  141. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  142. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  143. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  144. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  145. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
@@ -1,6 +1,6 @@
1
1
  import os from "node:os";
2
2
  import { getNativeBinding } from "./native.js";
3
- import { executePermissionCommand } from "./permission-exec.js";
3
+ import { executePermissionCommand, formatPermissionErrorDetail, getPermissionCommandFailure, } from "./permission-exec.js";
4
4
  import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
5
5
  import { resolveWindowsSystemCommand } from "./windows-command.js";
6
6
  import { inspectWindowsOwner, resolveWindowsCurrentUserSid, resolveWindowsPrincipalSids, } from "./windows-owner.js";
@@ -90,7 +90,7 @@ export async function inspectWindowsPermissions(params) {
90
90
  });
91
91
  if (owner.error) {
92
92
  const error = `Windows owner inspection failed: ${owner.error}`;
93
- return { ...unverified, ownerError: owner.error, error };
93
+ return { ...unverified, ownerError: owner.error, error, errorDetail: owner.errorDetail, errorCause: owner.errorCause };
94
94
  }
95
95
  const acl = await inspectWindowsAcl(params.targetPath, {
96
96
  env: params.opts?.env,
@@ -105,7 +105,7 @@ export async function inspectWindowsPermissions(params) {
105
105
  ...(owner.error ? { ownerError: owner.error } : {}),
106
106
  };
107
107
  if (!acl.ok)
108
- return { ...unverified, ...ownerFields, error: acl.error };
108
+ return { ...unverified, ...ownerFields, error: acl.error, errorDetail: acl.errorDetail, errorCause: acl.errorCause };
109
109
  return {
110
110
  ok: true,
111
111
  isSymlink: params.stat.isSymlink,
@@ -231,7 +231,13 @@ export function summarizeWindowsAcl(entries, env) {
231
231
  return { trusted, untrustedWorld, untrustedGroup };
232
232
  }
233
233
  export async function inspectWindowsAcl(targetPath, opts) {
234
- const exec = opts?.exec ?? defaultPermissionExec;
234
+ let command = "";
235
+ let startedAt = performance.now();
236
+ const exec = async (file, args) => {
237
+ command = file;
238
+ startedAt = performance.now();
239
+ return await (opts?.exec ?? defaultPermissionExec)(file, args);
240
+ };
235
241
  try {
236
242
  if (opts?.principalTranslationFailed)
237
243
  throw new Error("Windows ACL principal SID translation failed");
@@ -260,7 +266,12 @@ export async function inspectWindowsAcl(targetPath, opts) {
260
266
  return { ok: true, entries, trusted, untrustedWorld, untrustedGroup };
261
267
  }
262
268
  catch (err) {
263
- return { ok: false, entries: [], trusted: [], untrustedWorld: [], untrustedGroup: [], error: String(err) };
269
+ return {
270
+ ok: false, entries: [], trusted: [], untrustedWorld: [], untrustedGroup: [],
271
+ error: formatPermissionErrorDetail(String(err)),
272
+ errorDetail: getPermissionCommandFailure(err, command, performance.now() - startedAt),
273
+ errorCause: err,
274
+ };
264
275
  }
265
276
  }
266
277
  export function formatWindowsAclSummary(summary) {
@@ -1,4 +1,6 @@
1
+ import type { PermissionCommandFailure } from "./permission-exec.js";
1
2
  import { type PermissionExec } from "./permissions-windows.js";
3
+ export type { PermissionCommandFailure } from "./permission-exec.js";
2
4
  export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
3
5
  export type { IcaclsResetCommandOptions, PermissionExec, WindowsAclEntry, WindowsAclSummary, WindowsUserInfoProvider, } from "./permissions-windows.js";
4
6
  export type PermissionCheck = {
@@ -20,6 +22,9 @@ export type PermissionCheck = {
20
22
  ownerError?: string;
21
23
  aclSummary?: string;
22
24
  error?: string;
25
+ errorDetail?: PermissionCommandFailure;
26
+ /** Original inspection failure, retained separately from serializable diagnostics. */
27
+ errorCause?: unknown;
23
28
  };
24
29
  export type PermissionCheckOptions = {
25
30
  platform?: NodeJS.Platform;
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { Readable } from "node:stream";
2
+ import { type AsyncDirectoryGuard } from "./directory-guard.js";
2
3
  import type { FileIdentityStat } from "./file-identity.js";
3
4
  export type PinnedWriteInput = {
4
5
  kind: "buffer";
@@ -10,6 +11,10 @@ export type PinnedWriteInput = {
10
11
  };
11
12
  export type RenameIdentityMismatchPolicy = "throw" | "verify-content";
12
13
  export type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
14
+ export type PublishedWriteIdentity = Readonly<{
15
+ dev: bigint;
16
+ ino: bigint;
17
+ }>;
13
18
  export type PinnedWriteParams = {
14
19
  rootPath: string;
15
20
  relativeParentPath: string;
@@ -21,6 +26,7 @@ export type PinnedWriteParams = {
21
26
  input: PinnedWriteInput;
22
27
  rootIdentity?: FileIdentityStat;
23
28
  onRenameIdentityMismatch?: RenameIdentityMismatchPolicy;
29
+ verifyPublished?: (fd: number, identity: PublishedWriteIdentity, parentGuard: AsyncDirectoryGuard) => Promise<void>;
24
30
  };
25
31
  export declare function runPinnedWriteHelper(params: PinnedWriteParams): Promise<FileIdentityStat>;
26
32
  export declare function runPinnedWriteWithRenamePolicy(params: PinnedWriteParams & {
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAU3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAgEzC,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;CACzD,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
1
+ {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAkE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhI,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAU3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAgEzC,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;IAExD,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,mBAAmB,KAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
@@ -115,7 +115,7 @@ async function runPinnedWriteFallback(params) {
115
115
  : await createNearestExistingDirectoryGuard(params.rootPath, parentPath);
116
116
  const targetPath = path.join(parentPath, params.basename);
117
117
  if (params.overwrite === false) {
118
- let handle = await withAsyncDirectoryGuards([parentGuard], async () => await fs.open(targetPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL, params.mode), {
118
+ const handle = await withAsyncDirectoryGuards([parentGuard], async () => await fs.open(targetPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL, params.mode), {
119
119
  onPostGuardFailure: async (openedHandle) => {
120
120
  // The parent failed verification, so targetPath may now resolve
121
121
  // somewhere else. Close the fd, but do not clean up by path.
@@ -124,6 +124,7 @@ async function runPinnedWriteFallback(params) {
124
124
  });
125
125
  let created = true;
126
126
  try {
127
+ const verificationIdentity = await handle.stat({ bigint: true });
127
128
  await handle.chmod(params.mode);
128
129
  if (params.input.kind === "buffer") {
129
130
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
@@ -139,9 +140,10 @@ async function runPinnedWriteFallback(params) {
139
140
  }
140
141
  await syncFileBestEffort(handle);
141
142
  const stat = await handle.stat();
142
- await handle.close().catch(() => undefined);
143
143
  await syncDirectoryBestEffort(parentPath);
144
+ // Publication is complete. A failed outer check must not remove its target.
144
145
  created = false;
146
+ await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
145
147
  return { dev: stat.dev, ino: stat.ino };
146
148
  }
147
149
  finally {
@@ -160,10 +162,11 @@ async function runPinnedWriteFallback(params) {
160
162
  : 0);
161
163
  let handle;
162
164
  let tempStat;
163
- let targetStat;
165
+ let readHandle;
164
166
  let renamed = false;
165
167
  try {
166
168
  handle = await fs.open(tempPath, tempFlags, params.mode);
169
+ let verificationIdentity = await handle.stat({ bigint: true });
167
170
  await handle.chmod(params.mode);
168
171
  if (params.input.kind === "buffer") {
169
172
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
@@ -184,14 +187,13 @@ async function runPinnedWriteFallback(params) {
184
187
  }
185
188
  const expectedTempStat = tempStat;
186
189
  await syncFileBestEffort(handle);
187
- await handle.close().catch(() => undefined);
188
- handle = undefined;
190
+ let verifiedIdentity = expectedTempStat;
189
191
  await withAsyncDirectoryGuards([parentGuard], async () => {
190
192
  await fs.rename(tempPath, targetPath);
191
193
  renamed = true;
192
194
  await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
193
195
  await syncDirectoryBestEffort(parentPath);
194
- targetStat = await fs.lstat(targetPath);
196
+ const targetStat = await fs.lstat(targetPath);
195
197
  if (targetStat.isSymbolicLink()) {
196
198
  throw new FsSafeError("path-mismatch", "fallback target changed during write");
197
199
  }
@@ -211,38 +213,26 @@ async function runPinnedWriteFallback(params) {
211
213
  (process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants
212
214
  ? fsSync.constants.O_NOFOLLOW
213
215
  : 0);
214
- const readHandle = await fs.open(targetPath, readFlags);
215
- let actualHash;
216
- let readHandleStat;
217
- try {
218
- // Capture fd-based identity before reading — this is stable across all
219
- // subsequent lookups (on FUSE and locally), unlike the lstat-based
220
- // targetStat that triggered this fallback.
221
- readHandleStat = await readHandle.stat();
222
- actualHash = sha256Hex(await readHandle.readFile());
223
- }
224
- finally {
225
- await readHandle.close().catch(() => undefined);
226
- }
216
+ readHandle = await fs.open(targetPath, readFlags);
217
+ const readHandleStat = await readHandle.stat({ bigint: true });
218
+ const actualHash = sha256Hex(await readHandle.readFile());
227
219
  if (actualHash !== expectedHash) {
228
220
  throw new FsSafeError("path-mismatch", "fallback target changed during write");
229
221
  }
230
- // Replace the unreliable lstat-based targetStat with the fd-based stat so
231
- // the returned identity is consistent with what subsequent verifications
232
- // (e.g. verifyAtomicWriteResult) will obtain by opening the same file.
233
- targetStat = readHandleStat;
222
+ // The content-verified destination, not the old temp inode, is now pinned.
223
+ verificationIdentity = readHandleStat;
224
+ // Preserve the helper's legacy numeric return facts, not the private proof.
225
+ verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
234
226
  }
235
227
  });
228
+ await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
229
+ return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
236
230
  }
237
- catch (error) {
231
+ finally {
232
+ await readHandle?.close().catch(() => undefined);
238
233
  await handle?.close().catch(() => undefined);
239
234
  if (!renamed) {
240
235
  await fs.rm(tempPath, { force: true }).catch(() => undefined);
241
236
  }
242
- throw error;
243
- }
244
- if (!targetStat) {
245
- throw new FsSafeError("path-mismatch", "fallback target was not verified");
246
237
  }
247
- return { dev: targetStat.dev, ino: targetStat.ino };
248
238
  }
@@ -1 +1 @@
1
- {"version":3,"file":"private-directory.d.ts","sourceRoot":"","sources":["../src/private-directory.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,IAAI,CAAC,CAiBf"}
1
+ {"version":3,"file":"private-directory.d.ts","sourceRoot":"","sources":["../src/private-directory.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -3,11 +3,12 @@ import { getNativeBinding } from "./native.js";
3
3
  export async function createPrivateDirectory(targetPath, options) {
4
4
  const platform = options?.platform ?? process.platform;
5
5
  if (platform !== "win32") {
6
- throw new FsSafeError("helper-unavailable", "private-directory creation is available only on Windows through the bundled native binding");
6
+ throw new FsSafeError("helper-unavailable", "private-directory creation is supported only on Windows");
7
7
  }
8
8
  const native = getNativeBinding();
9
9
  if (!native) {
10
- throw new FsSafeError("helper-unavailable", "private Windows directory creation requires the bundled native binding");
10
+ throw new FsSafeError("helper-unavailable", "private Windows directory creation requires the matching optional native platform package; " +
11
+ "install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
11
12
  }
12
13
  native.createPrivateDirectory(targetPath);
13
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
package/dist/root-impl.js CHANGED
@@ -9,7 +9,7 @@ import { sameFileIdentity, sameFileIdentityForCleanup, } from "./file-identity.j
9
9
  import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
10
10
  import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
11
11
  import { assertMutationNotDenied, mergeDenyMutationPolicies, } from "./deny-mutations.js";
12
- import { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
12
+ import { resolveOpenedFileRealPathForFd, resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
13
13
  import { runPinnedWriteHelper, runPinnedWriteWithRenamePolicy, } from "./pinned-write.js";
14
14
  import { getNativeBinding } from "./native.js";
15
15
  import { validatePinnedOperationPayload } from "./pinned-operation.js";
@@ -25,6 +25,8 @@ import { stringifyJsonDocument } from "./json-stringify.js";
25
25
  import { walkRoot } from "./root-walk.js";
26
26
  import { registerTempPathForExit } from "./temp-cleanup.js";
27
27
  import { serializePathWrite } from "./write-queue.js";
28
+ import { verifyAtomicWriteResult } from "./root-write-verification.js";
29
+ import { inspectFileIdentity } from "./strict-file-identity.js";
28
30
  export { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
29
31
  function logWarn(message) {
30
32
  if (process.env.FS_SAFE_DEBUG_WARNINGS === "1") {
@@ -74,22 +76,27 @@ async function openVerifiedLocalFile(filePath, options) {
74
76
  assertNoUnsafeDeviceReadPath(filePath);
75
77
  const fsSafeTestHooks = getFsSafeTestHooks();
76
78
  let preOpenStat;
79
+ let observedBeforeOpen = false;
77
80
  // Reject directories before opening so we never surface EISDIR to callers (e.g. tool
78
81
  // results that get sent to messaging channels). See openclaw/openclaw#31186.
79
82
  try {
80
- preOpenStat = await fs.lstat(filePath);
81
- if (preOpenStat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
82
- throw new FsSafeError("symlink", "symlink not allowed");
83
- }
84
- if (!preOpenStat.isFile() && !preOpenStat.isSymbolicLink()) {
85
- throw new FsSafeError("not-file", "not a file");
86
- }
83
+ preOpenStat = await inspectFileIdentity(async () => {
84
+ const stat = await fs.lstat(filePath, { bigint: true });
85
+ observedBeforeOpen = true;
86
+ if (stat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
87
+ throw new FsSafeError("symlink", "symlink not allowed");
88
+ }
89
+ if (!stat.isFile() && !stat.isSymbolicLink()) {
90
+ throw new FsSafeError("not-file", "not a file");
91
+ }
92
+ return stat;
93
+ });
87
94
  }
88
95
  catch (err) {
89
- if (err instanceof FsSafeError) {
96
+ if (err instanceof FsSafeError || observedBeforeOpen) {
90
97
  throw err;
91
98
  }
92
- // ENOENT and other lstat errors: fall through and let fs.open handle.
99
+ // Only an initial lookup failure falls through; a failed re-inspection aborts.
93
100
  }
94
101
  if (preOpenStat) {
95
102
  await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
@@ -127,37 +134,28 @@ async function openVerifiedLocalFile(filePath, options) {
127
134
  if (!stat.isFile()) {
128
135
  throw new FsSafeError("not-file", "not a file");
129
136
  }
130
- if (preOpenStat &&
131
- !preOpenStat.isSymbolicLink() &&
132
- !sameFileIdentity(stat, preOpenStat)) {
133
- throw new FsSafeError("path-mismatch", "path changed before open");
134
- }
137
+ // Keep numeric Stats for the public receipt, never for identity verification.
138
+ const identity = await inspectFileIdentity(() => handle.stat({ bigint: true }), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined);
135
139
  if (options?.hardlinks === "reject" && stat.nlink > 1) {
136
140
  throw hardlinkedPathNotAllowedError();
137
141
  }
138
- if (options?.symlinks === "follow-within-root") {
139
- const pathStat = await fs.stat(filePath);
140
- if (!sameFileIdentity(stat, pathStat)) {
141
- throw new FsSafeError("path-mismatch", "path changed during read");
142
- }
143
- }
144
- else {
145
- const pathStat = await fs.lstat(filePath);
146
- if (pathStat.isSymbolicLink()) {
142
+ await inspectFileIdentity(async () => {
143
+ const pathStat = options?.symlinks === "follow-within-root"
144
+ ? await fs.stat(filePath, { bigint: true })
145
+ : await fs.lstat(filePath, { bigint: true });
146
+ if (pathStat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
147
147
  throw new FsSafeError("symlink", "symlink not allowed");
148
148
  }
149
- if (!sameFileIdentity(stat, pathStat)) {
150
- throw new FsSafeError("path-mismatch", "path changed during read");
149
+ return pathStat;
150
+ }, identity);
151
+ const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath);
152
+ await inspectFileIdentity(async () => {
153
+ const realStat = await fs.stat(realPath, { bigint: true });
154
+ if (options?.hardlinks === "reject" && realStat.nlink > 1n) {
155
+ throw hardlinkedPathNotAllowedError();
151
156
  }
152
- }
153
- const realPath = await resolveOpenedFileRealPathForHandle(handle, filePath);
154
- const realStat = await fs.stat(realPath);
155
- if (options?.hardlinks === "reject" && realStat.nlink > 1) {
156
- throw hardlinkedPathNotAllowedError();
157
- }
158
- if (!sameFileIdentity(stat, realStat)) {
159
- throw new FsSafeError("path-mismatch", "path mismatch");
160
- }
157
+ return realStat;
158
+ }, identity);
161
159
  return openResult({ handle, realPath, stat });
162
160
  }
163
161
  catch (err) {
@@ -474,26 +472,13 @@ async function writeTempFileForAtomicReplace(params) {
474
472
  await tempHandle.writeFile(params.data);
475
473
  }
476
474
  return {
477
- identity: await tempHandle.stat(),
478
- cleanupIdentity: await tempHandle.stat({ bigint: true }),
475
+ handle: tempHandle,
476
+ identity: await tempHandle.stat({ bigint: true }),
479
477
  };
480
478
  }
481
- finally {
479
+ catch (error) {
482
480
  await tempHandle.close().catch(() => { });
483
- }
484
- }
485
- async function verifyAtomicWriteResult(params) {
486
- const opened = await openVerifiedLocalFile(params.targetPath, { hardlinks: "reject" });
487
- try {
488
- if (!sameFileIdentity(opened.stat, params.expectedIdentity)) {
489
- throw new FsSafeError("path-mismatch", "path changed during write");
490
- }
491
- if (!isPathInside(params.root.rootWithSep, opened.realPath)) {
492
- throw outsideWorkspaceError();
493
- }
494
- }
495
- finally {
496
- await opened.handle.close().catch(() => { });
481
+ throw error;
497
482
  }
498
483
  }
499
484
  async function resolveGuardedWritePathInRoot(root, params) {
@@ -709,9 +694,9 @@ async function writeFileInRoot(root, params) {
709
694
  });
710
695
  }
711
696
  async function commitPinnedWriteInRoot(root, pinned, params) {
712
- let identity;
697
+ let verifyingPublication = false;
713
698
  try {
714
- identity = await runPinnedWriteWithRenamePolicy({
699
+ await runPinnedWriteWithRenamePolicy({
715
700
  rootPath: pinned.rootReal,
716
701
  relativeParentPath: pinned.relativeParentPath,
717
702
  basename: pinned.basename,
@@ -722,9 +707,27 @@ async function commitPinnedWriteInRoot(root, pinned, params) {
722
707
  overwrite: params.overwrite,
723
708
  input: { kind: "buffer", data: params.data, encoding: params.encoding },
724
709
  rootIdentity: root.rootIdentity,
710
+ verifyPublished: async (fd, expectedIdentity, parentGuard) => {
711
+ verifyingPublication = true;
712
+ try {
713
+ await verifyAtomicWriteResult({
714
+ root,
715
+ targetPath: pinned.targetPath,
716
+ fd,
717
+ expectedIdentity,
718
+ parentGuard,
719
+ });
720
+ }
721
+ catch (error) {
722
+ emitWriteBoundaryWarning(`post-write verification failed: ${String(error)}`);
723
+ throw error;
724
+ }
725
+ },
725
726
  });
726
727
  }
727
728
  catch (error) {
729
+ if (verifyingPublication)
730
+ throw error;
728
731
  const errorCode = error?.code;
729
732
  if (errorCode === "file_lock_stale" || errorCode === "file_lock_timeout") {
730
733
  throw error;
@@ -736,17 +739,6 @@ async function commitPinnedWriteInRoot(root, pinned, params) {
736
739
  }
737
740
  throw normalizePinnedWriteError(error);
738
741
  }
739
- try {
740
- await verifyAtomicWriteResult({
741
- root,
742
- targetPath: pinned.targetPath,
743
- expectedIdentity: identity,
744
- });
745
- }
746
- catch (err) {
747
- emitWriteBoundaryWarning(`post-write verification failed: ${String(err)}`);
748
- throw err;
749
- }
750
742
  }
751
743
  async function copyFileInRoot(root, params) {
752
744
  assertValidRootRelativePath(params.relativePath);
@@ -863,7 +855,7 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
863
855
  targetPath: resolved,
864
856
  relativeParentPath: path.posix.dirname(relativePosix) === "." ? "" : path.posix.dirname(relativePosix),
865
857
  basename,
866
- mode: mode || 0o600,
858
+ mode,
867
859
  };
868
860
  }
869
861
  async function resolvePinnedPathInRoot(root, params) {
@@ -1108,6 +1100,7 @@ async function writeFileFallback(root, params) {
1108
1100
  const destinationGuard = await createAsyncDirectoryGuard(path.dirname(destinationPath));
1109
1101
  let tempPath = null;
1110
1102
  let unregisterTempPath = null;
1103
+ let writtenHandle;
1111
1104
  try {
1112
1105
  tempPath = buildAtomicWriteTempPath(destinationPath);
1113
1106
  unregisterTempPath = registerTempPathForExit(tempPath);
@@ -1115,14 +1108,15 @@ async function writeFileFallback(root, params) {
1115
1108
  tempPath,
1116
1109
  data: params.data,
1117
1110
  encoding: params.encoding,
1118
- mode: mode || 0o600,
1111
+ mode,
1119
1112
  });
1120
- unregisterTempPath.setIdentity(written.cleanupIdentity);
1113
+ writtenHandle = written.handle;
1114
+ unregisterTempPath.setIdentity(written.identity);
1121
1115
  const commitTempPath = tempPath;
1122
1116
  await withAsyncDirectoryGuards([destinationGuard], async () => {
1123
1117
  await fs.rename(commitTempPath, destinationPath);
1118
+ tempPath = null;
1124
1119
  });
1125
- tempPath = null;
1126
1120
  unregisterTempPath();
1127
1121
  unregisterTempPath = null;
1128
1122
  try {
@@ -1130,6 +1124,8 @@ async function writeFileFallback(root, params) {
1130
1124
  root,
1131
1125
  targetPath: destinationPath,
1132
1126
  expectedIdentity: written.identity,
1127
+ fd: written.handle.fd,
1128
+ parentGuard: destinationGuard,
1133
1129
  });
1134
1130
  }
1135
1131
  catch (err) {
@@ -1138,6 +1134,7 @@ async function writeFileFallback(root, params) {
1138
1134
  }
1139
1135
  }
1140
1136
  finally {
1137
+ await writtenHandle?.close().catch(() => undefined);
1141
1138
  if (tempPath) {
1142
1139
  await fs.rm(tempPath, { force: true }).catch(() => { });
1143
1140
  }
@@ -1155,19 +1152,22 @@ async function writeMissingFileFallback(root, params) {
1155
1152
  const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath));
1156
1153
  let created = false;
1157
1154
  let createdIdentity;
1155
+ let writtenHandle;
1156
+ let verifyingPublication = false;
1158
1157
  try {
1159
1158
  const { handle, writtenStat } = await withAsyncDirectoryGuards([parentGuard], async () => {
1160
1159
  const handle = await fs.open(targetPath, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600);
1161
1160
  created = true;
1162
1161
  try {
1163
1162
  createdIdentity = await handle.stat();
1163
+ const writtenStat = await handle.stat({ bigint: true });
1164
1164
  if (typeof params.data === "string") {
1165
1165
  await handle.writeFile(params.data, params.encoding ?? "utf8");
1166
1166
  }
1167
1167
  else {
1168
1168
  await handle.writeFile(params.data);
1169
1169
  }
1170
- return { handle, writtenStat: await handle.stat() };
1170
+ return { handle, writtenStat };
1171
1171
  }
1172
1172
  catch (error) {
1173
1173
  await handle.close().catch(() => undefined);
@@ -1179,15 +1179,20 @@ async function writeMissingFileFallback(root, params) {
1179
1179
  await handle.close().catch(() => undefined);
1180
1180
  },
1181
1181
  });
1182
- await handle.close();
1182
+ writtenHandle = handle;
1183
+ created = false;
1184
+ verifyingPublication = true;
1183
1185
  await verifyAtomicWriteResult({
1184
1186
  root,
1185
1187
  targetPath,
1186
1188
  expectedIdentity: writtenStat,
1189
+ fd: handle.fd,
1190
+ parentGuard,
1187
1191
  });
1188
- created = false;
1189
1192
  }
1190
1193
  catch (err) {
1194
+ if (verifyingPublication)
1195
+ throw err;
1191
1196
  if (hasNodeErrorCode(err, "EEXIST")) {
1192
1197
  throw new FsSafeError("already-exists", "file already exists", {
1193
1198
  cause: err instanceof Error ? err : undefined,
@@ -1196,6 +1201,7 @@ async function writeMissingFileFallback(root, params) {
1196
1201
  throw err;
1197
1202
  }
1198
1203
  finally {
1204
+ await writtenHandle?.close().catch(() => undefined);
1199
1205
  if (created && createdIdentity) {
1200
1206
  await removePathIfIdentityUnchanged(targetPath, createdIdentity).catch(() => undefined);
1201
1207
  }
@@ -1,7 +1,16 @@
1
+ import { FsSafeError } from "./errors.js";
1
2
  type InvalidPathResult = {
2
3
  ok: false;
3
4
  error: string;
4
5
  };
6
+ type DirectoryResult = {
7
+ ok: true;
8
+ path: string;
9
+ } | {
10
+ ok: false;
11
+ error: string;
12
+ diagnostic?: FsSafeError;
13
+ };
5
14
  type ResolvePathsWithinRootParams = {
6
15
  rootDir: string;
7
16
  requestedPaths: string[];
@@ -39,13 +48,7 @@ export type PathScope = {
39
48
  }>;
40
49
  ensureDir(requestedPath: string, options?: PathScopeResolveOptions & {
41
50
  mode?: number;
42
- }): Promise<{
43
- ok: true;
44
- path: string;
45
- } | {
46
- ok: false;
47
- error: string;
48
- }>;
51
+ }): Promise<DirectoryResult>;
49
52
  };
50
53
  export declare function resolvePathWithinRoot(params: {
51
54
  rootDir: string;
@@ -77,13 +80,7 @@ export declare function ensureDirectoryWithinRoot(params: {
77
80
  scopeLabel: string;
78
81
  defaultDirName?: string;
79
82
  mode?: number;
80
- }): Promise<{
81
- ok: true;
82
- path: string;
83
- } | {
84
- ok: false;
85
- error: string;
86
- }>;
83
+ }): Promise<DirectoryResult>;
87
84
  export declare function resolvePathsWithinRoot(params: ResolvePathsWithinRootParams): ResolvePathsWithinRootResult;
88
85
  export declare function resolveExistingPathsWithinRoot(params: ResolvePathsWithinRootParams): Promise<ResolvePathsWithinRootResult>;
89
86
  export declare function resolveStrictExistingPathsWithinRoot(params: ResolvePathsWithinRootParams): Promise<ResolvePathsWithinRootResult>;
@@ -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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"}