@openclaw/fs-safe 0.5.5 → 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.
- package/CHANGELOG.md +49 -0
- package/README.md +16 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts +2 -0
- package/dist/archive-crc32.d.ts.map +1 -0
- package/dist/archive-crc32.js +14 -0
- package/dist/archive-kind.js +2 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +24 -5
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +53 -6
- package/dist/archive-tar-pax.d.ts +8 -0
- package/dist/archive-tar-pax.d.ts.map +1 -0
- package/dist/archive-tar-pax.js +100 -0
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-zip-admission.d.ts +7 -0
- package/dist/archive-zip-admission.d.ts.map +1 -0
- package/dist/archive-zip-admission.js +60 -0
- package/dist/archive-zip-count.d.ts +2 -0
- package/dist/archive-zip-count.d.ts.map +1 -0
- package/dist/archive-zip-count.js +140 -0
- package/dist/archive-zip-directory.d.ts +8 -0
- package/dist/archive-zip-directory.d.ts.map +1 -0
- package/dist/archive-zip-directory.js +224 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +1 -14
- package/dist/archive-zip-names.d.ts +12 -0
- package/dist/archive-zip-names.d.ts.map +1 -0
- package/dist/archive-zip-names.js +99 -0
- package/dist/archive-zip-preflight.d.ts +1 -1
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -145
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +32 -23
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-identity.d.ts +1 -0
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +10 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +19 -7
- package/dist/move-path.js +2 -2
- package/dist/native-binding.d.ts +3 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +4 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +24 -8
- package/dist/native-pinned-write-windows.d.ts +8 -0
- package/dist/native-pinned-write-windows.d.ts.map +1 -0
- package/dist/native-pinned-write-windows.js +92 -0
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +136 -127
- package/dist/native-staged-file.d.ts +24 -0
- package/dist/native-staged-file.d.ts.map +1 -0
- package/dist/native-staged-file.js +337 -0
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +4 -4
- package/dist/opened-realpath.d.ts +2 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +12 -7
- package/dist/output-sibling.js +3 -3
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -1
- package/dist/permission-exec.d.ts +19 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +57 -11
- package/dist/permissions-public.d.ts +1 -1
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-windows.d.ts +3 -0
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +16 -5
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +19 -29
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +23 -15
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/replace-file-descriptor.d.ts +3 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +2 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +84 -75
- package/dist/root-paths.d.ts +11 -14
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +36 -27
- package/dist/root-write-verification.d.ts +11 -0
- package/dist/root-write-verification.d.ts.map +1 -0
- package/dist/root-write-verification.js +91 -0
- package/dist/secret-file.d.ts +1 -6
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +43 -117
- package/dist/secret-read-async.d.ts +1 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +45 -67
- package/dist/secret-read-policy.d.ts +13 -0
- package/dist/secret-read-policy.d.ts.map +1 -0
- package/dist/secret-read-policy.js +28 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +25 -16
- package/dist/sibling-temp.js +1 -1
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -1
- package/dist/staged-directory.d.ts +16 -0
- package/dist/staged-directory.d.ts.map +1 -0
- package/dist/staged-directory.js +60 -0
- package/dist/staged-file-types.d.ts +56 -0
- package/dist/staged-file-types.d.ts.map +1 -0
- package/dist/staged-file-types.js +1 -0
- package/dist/staged-file.d.ts +10 -0
- package/dist/staged-file.d.ts.map +1 -0
- package/dist/staged-file.js +15 -0
- package/dist/strict-file-identity.d.ts +6 -0
- package/dist/strict-file-identity.d.ts.map +1 -0
- package/dist/strict-file-identity.js +48 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -3
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +9 -7
- package/dist/windows-owner.d.ts +3 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +10 -2
- package/docs/advanced.md +11 -1
- package/docs/archive.md +62 -15
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +17 -4
- package/docs/errors.md +16 -1
- package/docs/index.md +3 -1
- package/docs/install.md +15 -7
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +15 -5
- package/docs/native.md +40 -11
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/root.md +1 -1
- package/docs/secret-file.md +8 -0
- package/docs/secure-file.md +17 -1
- package/docs/security-model.md +15 -1
- package/docs/sidecar-lock.md +13 -3
- package/docs/staged-file.md +178 -0
- package/docs/testing.md +1 -1
- package/docs/writing.md +19 -0
- package/package.json +16 -9
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
|
@@ -1,153 +1,14 @@
|
|
|
1
|
-
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError,
|
|
1
|
+
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, resolveExtractLimits, } from "./archive-limits.js";
|
|
2
2
|
import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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 =
|
|
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 (
|
|
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() {
|
package/dist/archive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
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
|
package/dist/file-hash.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 {
|
package/dist/file-identity.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export type FileIdentityStat = {
|
|
|
4
4
|
};
|
|
5
5
|
export declare function sha256Hex(data: string | Buffer, encoding?: BufferEncoding): string;
|
|
6
6
|
export declare function sameFileIdentity(left: FileIdentityStat, right: FileIdentityStat, platform?: NodeJS.Platform): boolean;
|
|
7
|
+
export declare function sameFileIdentityForCleanup(left: FileIdentityStat, right: FileIdentityStat, platform?: NodeJS.Platform): boolean;
|
|
7
8
|
//# sourceMappingURL=file-identity.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-identity.d.ts","sourceRoot":"","sources":["../src/file-identity.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC;AAsBF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAGlF;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,gBAAgB,EACvB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAQT"}
|
|
1
|
+
{"version":3,"file":"file-identity.d.ts","sourceRoot":"","sources":["../src/file-identity.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC;AAsBF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAGlF;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,gBAAgB,EACvB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAQT;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,gBAAgB,EACvB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAWT"}
|
package/dist/file-identity.js
CHANGED
|
@@ -22,3 +22,13 @@ export function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
22
22
|
return (!isStatValueProvablyDifferent(left.dev, right.dev, platform) &&
|
|
23
23
|
!isStatValueProvablyDifferent(left.ino, right.ino, platform));
|
|
24
24
|
}
|
|
25
|
+
export function sameFileIdentityForCleanup(left, right, platform = process.platform) {
|
|
26
|
+
// A zero Windows device or inode is unknown, not proof that a pathname still
|
|
27
|
+
// names the object we created. Cleanup must fail closed rather than delete a
|
|
28
|
+
// replacement that happens to share the known half of the identity.
|
|
29
|
+
if (platform === "win32" &&
|
|
30
|
+
(isZero(left.dev) || isZero(left.ino) || isZero(right.dev) || isZero(right.ino))) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return sameStatValue(left.dev, right.dev) && sameStatValue(left.ino, right.ino);
|
|
34
|
+
}
|
|
@@ -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;
|
|
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"}
|
package/dist/file-lock-sync.js
CHANGED
|
@@ -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
|
-
|
|
141
|
-
|
|
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 =
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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 (
|
|
247
|
+
if (staleRecovery === "remove-if-unchanged" &&
|
|
236
248
|
snapshot.raw !== undefined &&
|
|
237
249
|
options.shouldRemoveStaleLock?.({
|
|
238
250
|
lockPath,
|
package/dist/move-path.js
CHANGED
|
@@ -299,7 +299,7 @@ export async function movePathWithCopyFallback(options) {
|
|
|
299
299
|
sourceHardlinks: options.sourceHardlinks ?? "allow",
|
|
300
300
|
...(rejectHardlinks ? { budget: { discovered: 1 } } : {}),
|
|
301
301
|
});
|
|
302
|
-
unregisterStaged.setIdentity(await fs.lstat(staged));
|
|
302
|
+
unregisterStaged.setIdentity(await fs.lstat(staged, { bigint: true }));
|
|
303
303
|
await assertCopyDestinationOutsideSource(sourcePath, targetPath);
|
|
304
304
|
await guardedRename({ from: staged, to: targetPath });
|
|
305
305
|
stagedCommitted = true;
|
|
@@ -312,7 +312,7 @@ export async function movePathWithCopyFallback(options) {
|
|
|
312
312
|
finally {
|
|
313
313
|
if (!stagedCommitted) {
|
|
314
314
|
try {
|
|
315
|
-
const stagedIdentity = await fs.lstat(staged);
|
|
315
|
+
const stagedIdentity = await fs.lstat(staged, { bigint: true });
|
|
316
316
|
if (!stagedIdentity.isSymbolicLink())
|
|
317
317
|
unregisterStaged.setIdentity(stagedIdentity);
|
|
318
318
|
}
|
package/dist/native-binding.d.ts
CHANGED
|
@@ -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":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;CAAG;AAErE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,eAAe,GAAG,aAAa,CAAC;CAC9C;AAED,MAAM,WAAW,+BAA+B;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,aAAa,EAAE,OAAO,CAAC;QACvB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,kBAAkB,EAAE,OAAO,CAAC;QAC5B,WAAW,EAAE,OAAO,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,+BAA+B,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;
|
|
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;
|
|
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 {
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
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
|
|
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
|
-
|
|
56
|
+
writeNativeFd(fd, Buffer.isBuffer(data) ? data : Buffer.from(data, encoding ?? "utf8"));
|
|
38
57
|
},
|
|
39
58
|
};
|
|
40
59
|
}
|
|
@@ -51,7 +70,7 @@ export function removeNativeCreatedFileIfStillPinned(params) {
|
|
|
51
70
|
parentPathStat.dev === parentFdStat.dev &&
|
|
52
71
|
parentPathStat.ino === parentFdStat.ino &&
|
|
53
72
|
!target.isSymbolicLink() &&
|
|
54
|
-
|
|
73
|
+
sameFileIdentityForCleanup(target, params.created)) {
|
|
55
74
|
fsSync.rmSync(targetPath);
|
|
56
75
|
}
|
|
57
76
|
}
|
|
@@ -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"}
|