@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,153 +1,14 @@
1
- import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, resolveExtractLimits, } from "./archive-limits.js";
1
+ import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, resolveExtractLimits, } from "./archive-limits.js";
2
2
  import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
3
- const ZIP_EOCD_SIGNATURE = 0x06054b50;
4
- const ZIP64_EOCD_SIGNATURE = 0x06064b50;
5
- const ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50;
6
- const ZIP_EOCD_MIN_BYTES = 22;
7
- const ZIP_EOCD_MAX_COMMENT_BYTES = 0xffff;
8
- const ZIP64_ENTRY_COUNT_SENTINEL = 0xffff;
9
- const ZIP64_UINT32_SENTINEL = 0xffffffff;
10
- const ZIP_CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
11
- const ZIP_CENTRAL_FILE_HEADER_MIN_BYTES = 46;
12
- const ZIP_CENTRAL_FILE_HEADER_NAME_LENGTH_OFFSET = 28;
13
- const ZIP_CENTRAL_FILE_HEADER_EXTRA_LENGTH_OFFSET = 30;
14
- const ZIP_CENTRAL_FILE_HEADER_COMMENT_LENGTH_OFFSET = 32;
15
- const ZIP_EOCD_TOTAL_ENTRIES_OFFSET = 10;
16
- const ZIP_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET = 12;
17
- const ZIP_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET = 16;
18
- const ZIP_EOCD_COMMENT_LENGTH_OFFSET = 20;
19
- const ZIP64_EOCD_LOCATOR_BYTES = 20;
20
- const ZIP64_EOCD_OFFSET_OFFSET = 8;
21
- const ZIP64_EOCD_TOTAL_ENTRIES_OFFSET = 32;
22
- const ZIP64_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET = 40;
23
- const ZIP64_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET = 48;
24
- function asBufferView(buffer) {
25
- if (Buffer.isBuffer(buffer)) {
26
- return buffer;
27
- }
28
- return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
29
- }
30
- function readSafeUInt64LE(buffer, offset) {
31
- const value = buffer.readBigUInt64LE(offset);
32
- if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
33
- return Number.MAX_SAFE_INTEGER;
34
- }
35
- return Number(value);
36
- }
37
- function findZipEndOfCentralDirectory(buffer) {
38
- if (buffer.byteLength < ZIP_EOCD_MIN_BYTES) {
39
- return -1;
40
- }
41
- const minOffset = Math.max(0, buffer.byteLength - ZIP_EOCD_MIN_BYTES - ZIP_EOCD_MAX_COMMENT_BYTES);
42
- for (let offset = buffer.byteLength - ZIP_EOCD_MIN_BYTES; offset >= minOffset; offset -= 1) {
43
- if (buffer.readUInt32LE(offset) !== ZIP_EOCD_SIGNATURE) {
44
- continue;
45
- }
46
- const commentLength = buffer.readUInt16LE(offset + ZIP_EOCD_COMMENT_LENGTH_OFFSET);
47
- if (offset + ZIP_EOCD_MIN_BYTES + commentLength === buffer.byteLength) {
48
- return offset;
49
- }
50
- }
51
- return -1;
52
- }
53
- function readZip64CentralDirectoryInfo(buffer, eocdOffset) {
54
- const locatorOffset = eocdOffset - ZIP64_EOCD_LOCATOR_BYTES;
55
- if (locatorOffset < 0 || buffer.readUInt32LE(locatorOffset) !== ZIP64_EOCD_LOCATOR_SIGNATURE) {
56
- return null;
57
- }
58
- const zip64EocdOffset = readSafeUInt64LE(buffer, locatorOffset + ZIP64_EOCD_OFFSET_OFFSET);
59
- if (zip64EocdOffset < 0 ||
60
- zip64EocdOffset + ZIP64_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET + 8 > buffer.byteLength ||
61
- buffer.readUInt32LE(zip64EocdOffset) !== ZIP64_EOCD_SIGNATURE) {
62
- return null;
63
- }
64
- return {
65
- declaredEntryCount: readSafeUInt64LE(buffer, zip64EocdOffset + ZIP64_EOCD_TOTAL_ENTRIES_OFFSET),
66
- centralDirectorySize: readSafeUInt64LE(buffer, zip64EocdOffset + ZIP64_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET),
67
- centralDirectoryOffset: readSafeUInt64LE(buffer, zip64EocdOffset + ZIP64_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET),
68
- endOfCentralDirectoryOffset: eocdOffset,
69
- };
70
- }
71
- function readZipCentralDirectoryInfo(buffer) {
72
- const eocdOffset = findZipEndOfCentralDirectory(buffer);
73
- if (eocdOffset < 0) {
74
- return null;
75
- }
76
- const declaredEntryCount = buffer.readUInt16LE(eocdOffset + ZIP_EOCD_TOTAL_ENTRIES_OFFSET);
77
- const centralDirectorySize = buffer.readUInt32LE(eocdOffset + ZIP_EOCD_CENTRAL_DIRECTORY_SIZE_OFFSET);
78
- const centralDirectoryOffset = buffer.readUInt32LE(eocdOffset + ZIP_EOCD_CENTRAL_DIRECTORY_OFFSET_OFFSET);
79
- const usesZip64 = declaredEntryCount === ZIP64_ENTRY_COUNT_SENTINEL ||
80
- centralDirectorySize === ZIP64_UINT32_SENTINEL ||
81
- centralDirectoryOffset === ZIP64_UINT32_SENTINEL;
82
- if (usesZip64) {
83
- return (readZip64CentralDirectoryInfo(buffer, eocdOffset) ?? {
84
- declaredEntryCount,
85
- centralDirectoryOffset,
86
- centralDirectorySize,
87
- endOfCentralDirectoryOffset: eocdOffset,
88
- });
89
- }
90
- return {
91
- declaredEntryCount,
92
- centralDirectoryOffset,
93
- centralDirectorySize,
94
- endOfCentralDirectoryOffset: eocdOffset,
95
- };
96
- }
97
- function countZipCentralDirectoryHeaders(buffer, info) {
98
- const start = info.centralDirectoryOffset;
99
- const declaredEnd = start + info.centralDirectorySize;
100
- const scanEnd = info.endOfCentralDirectoryOffset;
101
- if (!Number.isSafeInteger(start) ||
102
- !Number.isSafeInteger(declaredEnd) ||
103
- !Number.isSafeInteger(scanEnd) ||
104
- start < 0 ||
105
- declaredEnd < start ||
106
- scanEnd < start ||
107
- scanEnd > buffer.byteLength) {
108
- return null;
109
- }
110
- let offset = start;
111
- let count = 0;
112
- while (offset < scanEnd) {
113
- if (scanEnd - offset < ZIP_CENTRAL_FILE_HEADER_MIN_BYTES) {
114
- break;
115
- }
116
- if (buffer.readUInt32LE(offset) !== ZIP_CENTRAL_FILE_HEADER_SIGNATURE) {
117
- break;
118
- }
119
- const nameLength = buffer.readUInt16LE(offset + ZIP_CENTRAL_FILE_HEADER_NAME_LENGTH_OFFSET);
120
- const extraLength = buffer.readUInt16LE(offset + ZIP_CENTRAL_FILE_HEADER_EXTRA_LENGTH_OFFSET);
121
- const commentLength = buffer.readUInt16LE(offset + ZIP_CENTRAL_FILE_HEADER_COMMENT_LENGTH_OFFSET);
122
- const nextOffset = offset + ZIP_CENTRAL_FILE_HEADER_MIN_BYTES + nameLength + extraLength + commentLength;
123
- if (nextOffset <= offset || nextOffset > scanEnd) {
124
- return null;
125
- }
126
- count += 1;
127
- offset = nextOffset;
128
- }
129
- return count > 0 || info.declaredEntryCount === 0 ? count : null;
130
- }
131
- export function readZipCentralDirectoryEntryCount(buffer) {
132
- const view = asBufferView(buffer);
133
- const info = readZipCentralDirectoryInfo(view);
134
- if (!info) {
135
- return null;
136
- }
137
- const countedEntryCount = countZipCentralDirectoryHeaders(view, info);
138
- return countedEntryCount === null
139
- ? info.declaredEntryCount
140
- : Math.max(info.declaredEntryCount, countedEntryCount);
141
- }
3
+ import { admitZipBuffer } from "./archive-zip-admission.js";
4
+ import { validateArchiveEntryPath } from "./archive-entry.js";
5
+ export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
142
6
  export async function loadZipArchiveWithPreflight(buffer, limits) {
143
7
  const resolvedLimits = resolveExtractLimits(limits);
144
8
  if (buffer.byteLength > resolvedLimits.maxArchiveBytes) {
145
9
  throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
146
10
  }
147
- const entryCount = readZipCentralDirectoryEntryCount(buffer);
148
- if (entryCount !== null) {
149
- assertArchiveEntryCountWithinLimit(entryCount, resolvedLimits);
150
- }
11
+ const entryCount = admitZipBuffer(buffer, resolvedLimits);
151
12
  const JSZip = await importOptionalJsZip();
152
13
  let archive;
153
14
  try {
@@ -156,9 +17,11 @@ export async function loadZipArchiveWithPreflight(buffer, limits) {
156
17
  catch (error) {
157
18
  throw new ArchiveFormatError(`invalid ZIP archive: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
158
19
  }
159
- if (entryCount !== null && Object.keys(archive.files).length !== entryCount) {
20
+ if (Object.keys(archive.files).length !== entryCount) {
160
21
  throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
161
22
  }
23
+ for (const name of Object.keys(archive.files))
24
+ validateArchiveEntryPath(name);
162
25
  return archive;
163
26
  }
164
27
  async function importOptionalJsZip() {
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA6DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAoNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkKjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAoNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwKjF"}
package/dist/archive.js CHANGED
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Readable } from "node:stream";
5
5
  import { pipeline } from "node:stream/promises";
6
- import { createArchiveOutputPathTracker, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
6
+ import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
7
7
  import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
8
8
  import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, } from "./archive-limits.js";
9
9
  import { resolveArchiveKind } from "./archive-kind.js";
@@ -155,6 +155,9 @@ async function extractZip(params) {
155
155
  })) {
156
156
  continue;
157
157
  }
158
+ if (isSymlink) {
159
+ throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
160
+ }
158
161
  const mode = zipEntryMode(entry, params.entryModes);
159
162
  await prepareZipOutputPath({
160
163
  destinationDir: stagingRealDir,
@@ -168,9 +171,6 @@ async function extractZip(params) {
168
171
  await fs.chmod(output.outPath, mode);
169
172
  continue;
170
173
  }
171
- if (isSymlink) {
172
- throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
173
- }
174
174
  await writeZipFileEntry({
175
175
  entry,
176
176
  outPath: output.outPath,
@@ -216,7 +216,8 @@ export async function extractArchive(params) {
216
216
  return;
217
217
  }
218
218
  if (kind === "tar-zstd" || kind === "tar-bzip2") {
219
- throw new FsSafeError("helper-unavailable", `${kind} archives require a supported bundled native binding`);
219
+ throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
220
+ "install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
220
221
  }
221
222
  if (kind === "tar") {
222
223
  await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
@@ -239,6 +240,7 @@ export async function extractArchive(params) {
239
240
  destinationRealDir,
240
241
  run: async (stagingDir) => {
241
242
  deadline.check();
243
+ const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
242
244
  const checkTarEntrySafety = createTarEntryPreflightChecker({
243
245
  rootDir: destinationRealDir,
244
246
  stripComponents: params.stripComponents,
@@ -253,35 +255,42 @@ export async function extractArchive(params) {
253
255
  // the same safe-open boundary checks used by direct file writes.
254
256
  const extractor = tar.x({
255
257
  cwd: stagingDir,
256
- strip: Math.max(0, Math.floor(params.stripComponents ?? 0)),
258
+ // fs-safe owns stripping: node-tar counts the `.` and empty path
259
+ // components that stripArchivePath() drops, so the two disagree.
260
+ strip: 0,
257
261
  gzip: params.tarGzip,
258
262
  signal: deadline.signal,
259
263
  preservePaths: false,
260
264
  noChmod: true,
261
265
  preserveOwner: false,
266
+ noMtime: true,
262
267
  strict: true,
263
268
  maxMetaEntrySize: limits.maxMetaEntryBytes,
264
269
  filter(_entryPath, entry) {
265
270
  try {
266
271
  const info = readTarEntryInfo(entry);
267
- const accepted = checkTarEntrySafety(info);
268
- if (accepted) {
269
- entry.path = normalizeArchiveEntryPath(info.path);
270
- const relPath = stripArchivePath(info.path, Math.max(0, Math.floor(params.stripComponents ?? 0)));
271
- if (relPath) {
272
- acceptedEntries.push({
273
- path: relPath,
274
- mode: resolveArchiveEntryMode({
275
- kind: info.type === "Directory" || info.type === "GNUDumpDir"
276
- ? "directory"
277
- : "file",
278
- archivedMode: info.mode,
279
- policy: params.entryModes,
280
- }),
281
- });
282
- }
272
+ if (!checkTarEntrySafety(info)) {
273
+ return false;
274
+ }
275
+ const relPath = stripArchivePath(info.path, strip);
276
+ if (!relPath) {
277
+ return false;
283
278
  }
284
- return accepted;
279
+ // Hand node-tar the exact path this entry was validated,
280
+ // limited and collision-checked under, and that the mode
281
+ // pass below resolves after extraction.
282
+ entry.path = relPath;
283
+ acceptedEntries.push({
284
+ path: relPath,
285
+ mode: resolveArchiveEntryMode({
286
+ kind: info.type === "Directory" || info.type === "GNUDumpDir"
287
+ ? "directory"
288
+ : "file",
289
+ archivedMode: info.mode,
290
+ policy: params.entryModes,
291
+ }),
292
+ });
293
+ return true;
285
294
  }
286
295
  catch (error) {
287
296
  // Abort through the parser so pipeline tears down both the
@@ -1 +1 @@
1
- {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,GACrD,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AA2CD,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAElF"}
1
+ {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,GACrD,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AA+CD,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAElF"}
package/dist/file-hash.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { sameFileIdentity } from "./file-identity.js";
5
4
  import { getNativeBinding } from "./native.js";
6
5
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
+ import { inspectFileIdentity } from "./strict-file-identity.js";
7
7
  export async function hashFileHandle(handle, native = getNativeBinding()) {
8
8
  const stat = await handle.stat();
9
9
  if (!stat.isFile()) {
@@ -25,13 +25,16 @@ export async function hashFileHandle(handle, native = getNativeBinding()) {
25
25
  }
26
26
  }
27
27
  async function hashPath(filePath) {
28
- const before = await fs.lstat(filePath);
29
- if (before.isSymbolicLink()) {
30
- throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
31
- }
32
- if (!before.isFile()) {
33
- throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
34
- }
28
+ const before = await inspectFileIdentity(async () => {
29
+ const stat = await fs.lstat(filePath, { bigint: true });
30
+ if (stat.isSymbolicLink()) {
31
+ throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
32
+ }
33
+ if (!stat.isFile()) {
34
+ throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
35
+ }
36
+ return stat;
37
+ });
35
38
  let handle;
36
39
  try {
37
40
  handle = await fs.open(filePath, resolveReadOpenFlags());
@@ -45,17 +48,20 @@ async function hashPath(filePath) {
45
48
  throw error;
46
49
  }
47
50
  try {
48
- const opened = await handle.stat();
49
- const current = await fs.lstat(filePath);
50
- if (!opened.isFile()) {
51
- throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
52
- }
53
- if (current.isSymbolicLink() ||
54
- !current.isFile() ||
55
- !sameFileIdentity(before, opened) ||
56
- !sameFileIdentity(opened, current)) {
57
- throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
58
- }
51
+ const opened = await inspectFileIdentity(async () => {
52
+ const stat = await handle.stat({ bigint: true });
53
+ if (!stat.isFile()) {
54
+ throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
55
+ }
56
+ return stat;
57
+ }, before);
58
+ await inspectFileIdentity(async () => {
59
+ const stat = await fs.lstat(filePath, { bigint: true });
60
+ if (stat.isSymbolicLink() || !stat.isFile()) {
61
+ throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
62
+ }
63
+ return stat;
64
+ }, opened);
59
65
  return await hashFileHandle(handle);
60
66
  }
61
67
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA0IF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAuJpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
1
+ {"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA0IF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAkKpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { readSidecarLockSnapshotSync, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
5
5
  import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, } from "./sidecar-lock-policy.js";
6
+ import { getFsSafeLockConfig } from "./lock-config.js";
6
7
  import { sleepSync } from "./timing.js";
7
8
  const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
8
9
  const SYNC_CLEANUP_REGISTERED_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupRegistered");
@@ -137,14 +138,19 @@ export function acquireFileLockSync(targetPath, options) {
137
138
  held.refCount += 1;
138
139
  return createSyncHeldLockHandle(held);
139
140
  }
140
- const staleMs = options.staleMs ?? 30_000;
141
- const retry = options.retry ?? {};
141
+ // Process defaults fill the same fields here as withLockDefaults() fills for
142
+ // the asynchronous manager, so both acquirers honor configureFsSafeLocks().
143
+ const defaults = getFsSafeLockConfig();
144
+ const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
145
+ const retry = options.retry ?? defaults.retry ?? {};
146
+ const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
147
+ const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
142
148
  const startedAt = Date.now();
143
149
  let attempt = 0;
144
150
  const reclaimGuardPath = `${lockPath}.reclaim`;
145
151
  const waitForRetry = () => {
146
152
  const elapsed = Date.now() - startedAt;
147
- const timedOut = options.timeoutMs !== undefined && elapsed >= options.timeoutMs;
153
+ const timedOut = timeoutMs !== undefined && elapsed >= timeoutMs;
148
154
  if (timedOut || (retry.retries !== undefined && attempt >= retry.retries)) {
149
155
  throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
150
156
  code: "file_lock_timeout",
@@ -190,9 +196,15 @@ export function acquireFileLockSync(targetPath, options) {
190
196
  const returnedHandle = createSyncHeldLockHandle(createdHeld);
191
197
  if (options.onCompromised && (options.compromiseCheckIntervalMs ?? 0) > 0) {
192
198
  createdHeld.timer = setInterval(() => {
193
- if (!returnedHandle.verifyStillHeld()) {
194
- if (createdHeld.timer)
195
- clearInterval(createdHeld.timer);
199
+ let stillHeld;
200
+ try {
201
+ stillHeld = returnedHandle.verifyStillHeld();
202
+ }
203
+ catch {
204
+ stillHeld = false;
205
+ }
206
+ if (!stillHeld && createdHeld.timer) {
207
+ clearInterval(createdHeld.timer);
196
208
  createdHeld.timer = undefined;
197
209
  options.onCompromised?.({ lockPath, normalizedTargetPath });
198
210
  }
@@ -232,7 +244,7 @@ export function acquireFileLockSync(targetPath, options) {
232
244
  })
233
245
  : defaultShouldReclaim(snapshot.payload, lockPath, staleMs, nowMs);
234
246
  if (reclaim) {
235
- if (options.staleRecovery === "remove-if-unchanged" &&
247
+ if (staleRecovery === "remove-if-unchanged" &&
236
248
  snapshot.raw !== undefined &&
237
249
  options.shouldRemoveStaleLock?.({
238
250
  lockPath,
@@ -62,6 +62,9 @@ export interface NativeWindowsSecurityFacts {
62
62
  aces: NativeWindowsAccessControlEntry[];
63
63
  }
64
64
  export interface NativeBinding {
65
+ createStagedFile?(parentFd: number, basename: string): number;
66
+ stagedFileMatches?(parentFd: number, basename: string, fileFd: number): boolean;
67
+ removeStagedFile?(parentFd: number, basename: string, fileFd: number): "removed" | "name-absent" | "preserved";
65
68
  cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
66
69
  copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
67
70
  createPrivateDirectory(path: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { type Stats } from "node:fs";
2
2
  import type { ContainmentGuarantee } from "./containment.js";
3
3
  import { type NativeBinding } from "./native.js";
4
+ import type { PinnedWriteInput } from "./pinned-write.js";
4
5
  export type NativeFileHandle = {
5
6
  readonly fd: number;
6
7
  readonly containment: ContainmentGuarantee;
@@ -8,6 +9,9 @@ export type NativeFileHandle = {
8
9
  stat(): Promise<Stats>;
9
10
  writeFile(data: string | Buffer, encoding?: BufferEncoding): Promise<void>;
10
11
  };
12
+ export declare function nativeOpenFlags(flags: number): number;
13
+ export declare function writeNativeFd(fd: number, data: Buffer): void;
14
+ export declare function writeNativeInput(fd: number, input: PinnedWriteInput, maxBytes?: number): Promise<void>;
11
15
  export declare function removeNativeCreatedFileIfStillPinned(params: {
12
16
  binding: NativeBinding;
13
17
  parentPath: string;
@@ -17,5 +21,4 @@ export declare function removeNativeCreatedFileIfStillPinned(params: {
17
21
  }): void;
18
22
  export declare function createNativeExclusiveFile(targetPath: string, mode: number): Promise<NativeFileHandle | undefined>;
19
23
  export declare function syncNativeFileBestEffort(fd: number): void;
20
- export declare function writeNativeFd(fd: number, data: Buffer): void;
21
24
  //# sourceMappingURL=native-operations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AA0CF,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,GAAG,IAAI,CAsBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAyDvC;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQzD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5D"}
1
+ {"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAsBD,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,GAAG,IAAI,CAsBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAyDvC;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQzD"}
@@ -1,15 +1,16 @@
1
1
  import fsSync, {} from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { FsSafeError } from "./errors.js";
4
5
  import { sameFileIdentityForCleanup } from "./file-identity.js";
5
6
  import { getNativeBinding } from "./native.js";
6
- function nativeOpenFlags(flags) {
7
+ export function nativeOpenFlags(flags) {
7
8
  const closeOnExec = fsSync.constants.O_CLOEXEC;
8
9
  return (flags |
9
10
  (closeOnExec ?? 0) |
10
11
  (typeof fsSync.constants.O_NOFOLLOW === "number" ? fsSync.constants.O_NOFOLLOW : 0));
11
12
  }
12
- function writeAll(fd, data) {
13
+ export function writeNativeFd(fd, data) {
13
14
  let offset = 0;
14
15
  while (offset < data.byteLength) {
15
16
  const written = fsSync.writeSync(fd, data, offset, data.byteLength - offset);
@@ -19,6 +20,24 @@ function writeAll(fd, data) {
19
20
  offset += written;
20
21
  }
21
22
  }
23
+ export async function writeNativeInput(fd, input, maxBytes) {
24
+ let bytes = 0;
25
+ const write = (data) => {
26
+ bytes += data.byteLength;
27
+ if (maxBytes !== undefined && bytes > maxBytes) {
28
+ throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
29
+ }
30
+ writeNativeFd(fd, data);
31
+ };
32
+ if (input.kind === "buffer") {
33
+ write(typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data);
34
+ }
35
+ else {
36
+ for await (const chunk of input.stream) {
37
+ write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
38
+ }
39
+ }
40
+ }
22
41
  function wrapNativeFd(fd, containment) {
23
42
  let open = true;
24
43
  return {
@@ -34,7 +53,7 @@ function wrapNativeFd(fd, containment) {
34
53
  return fsSync.fstatSync(fd);
35
54
  },
36
55
  async writeFile(data, encoding) {
37
- writeAll(fd, Buffer.isBuffer(data) ? data : Buffer.from(data, encoding ?? "utf8"));
56
+ writeNativeFd(fd, Buffer.isBuffer(data) ? data : Buffer.from(data, encoding ?? "utf8"));
38
57
  },
39
58
  };
40
59
  }
@@ -123,6 +142,3 @@ export function syncNativeFileBestEffort(fd) {
123
142
  }
124
143
  }
125
144
  }
126
- export function writeNativeFd(fd, data) {
127
- writeAll(fd, data);
128
- }
@@ -0,0 +1,8 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ import type { AsyncDirectoryGuard } from "./directory-guard.js";
3
+ import type { FileIdentityStat } from "./file-identity.js";
4
+ import type { NativeBinding } from "./native.js";
5
+ import type { PinnedWriteParams } from "./pinned-write.js";
6
+ export declare function sameNativeIdentity(left: Pick<FileIdentityStat, "dev" | "ino">, right: Pick<FileIdentityStat, "dev" | "ino">): boolean;
7
+ export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root: FileHandle, parentFd: number, parentGuard: AsyncDirectoryGuard): Promise<FileIdentityStat>;
8
+ //# sourceMappingURL=native-pinned-write-windows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-pinned-write-windows.d.ts","sourceRoot":"","sources":["../src/native-pinned-write-windows.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAO3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,EAC3C,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,GAC3C,OAAO,CAET;AAWD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAgF3B"}