@openclaw/fs-safe 0.4.6 → 0.5.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 +69 -0
- package/README.md +110 -41
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +5 -4
- package/dist/archive-errors.d.ts +11 -0
- package/dist/archive-errors.d.ts.map +1 -0
- package/dist/archive-errors.js +16 -0
- package/dist/archive-input.d.ts +12 -0
- package/dist/archive-input.d.ts.map +1 -0
- package/dist/archive-input.js +72 -0
- package/dist/archive-kind.d.ts +1 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +18 -0
- package/dist/archive-limits.d.ts +10 -0
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +18 -0
- package/dist/archive-native.d.ts +18 -0
- package/dist/archive-native.d.ts.map +1 -0
- package/dist/archive-native.js +115 -0
- package/dist/archive-options.d.ts +21 -0
- package/dist/archive-options.d.ts.map +1 -0
- package/dist/archive-options.js +1 -0
- package/dist/archive-policy.d.ts +20 -0
- package/dist/archive-policy.d.ts.map +1 -0
- package/dist/archive-policy.js +29 -0
- package/dist/archive-read.d.ts +6 -0
- package/dist/archive-read.d.ts.map +1 -0
- package/dist/archive-read.js +200 -0
- package/dist/archive-staging.d.ts +2 -5
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +19 -10
- package/dist/archive-tar-meta.d.ts +6 -0
- package/dist/archive-tar-meta.d.ts.map +1 -0
- package/dist/archive-tar-meta.js +143 -0
- package/dist/archive-tar-runtime.d.ts +43 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -0
- package/dist/archive-tar-runtime.js +8 -0
- package/dist/archive-tar.d.ts +5 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +24 -6
- package/dist/archive-zip-entry.d.ts +15 -0
- package/dist/archive-zip-entry.d.ts.map +1 -0
- package/dist/archive-zip-entry.js +17 -0
- package/dist/archive.d.ts +7 -17
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +111 -108
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/containment.d.ts +2 -0
- package/dist/containment.d.ts.map +1 -0
- package/dist/containment.js +1 -0
- package/dist/directory-durability.d.ts +44 -0
- package/dist/directory-durability.d.ts.map +1 -0
- package/dist/directory-durability.js +299 -0
- package/dist/durability.d.ts +4 -0
- package/dist/durability.d.ts.map +1 -0
- package/dist/durability.js +3 -0
- package/dist/errors.d.ts +4 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/file-hash.d.ts +10 -0
- package/dist/file-hash.d.ts.map +1 -0
- package/dist/file-hash.js +73 -0
- package/dist/file-lock-sync.d.ts +35 -0
- package/dist/file-lock-sync.d.ts.map +1 -0
- package/dist/file-lock-sync.js +233 -0
- package/dist/file-lock.d.ts +2 -0
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +4 -1
- package/dist/fsync.d.ts +1 -1
- package/dist/fsync.d.ts.map +1 -1
- package/dist/fsync.js +1 -21
- package/dist/guarded-mkdir.d.ts +7 -1
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +45 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +30 -15
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +101 -17
- 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
- package/dist/native-binding.d.ts +79 -0
- package/dist/native-binding.d.ts.map +1 -0
- package/dist/native-binding.js +1 -0
- package/dist/native-config.d.ts +17 -0
- package/dist/native-config.d.ts.map +1 -0
- package/dist/native-config.js +71 -0
- package/dist/native-operations.d.ts +21 -0
- package/dist/native-operations.d.ts.map +1 -0
- package/dist/native-operations.js +115 -0
- package/dist/native-pinned-write.d.ts +5 -0
- package/dist/native-pinned-write.d.ts.map +1 -0
- package/dist/native-pinned-write.js +115 -0
- package/dist/native.d.ts +14 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +204 -0
- package/dist/output-sibling.d.ts +8 -0
- package/dist/output-sibling.d.ts.map +1 -0
- package/dist/output-sibling.js +122 -0
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +32 -5
- package/dist/owner-dacl.d.ts +31 -0
- package/dist/owner-dacl.d.ts.map +1 -0
- package/dist/owner-dacl.js +27 -0
- package/dist/path-policy.js +1 -1
- package/dist/permissions-public.d.ts +2 -0
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-public.js +2 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +6 -0
- package/dist/pinned-operation.d.ts +2 -0
- package/dist/pinned-operation.d.ts.map +1 -0
- package/dist/pinned-operation.js +35 -0
- package/dist/pinned-write.d.ts +3 -16
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +20 -85
- package/dist/private-directory.d.ts +5 -0
- package/dist/private-directory.d.ts.map +1 -0
- package/dist/private-directory.js +13 -0
- package/dist/private-temp-workspace.d.ts +6 -2
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +40 -13
- package/dist/publish-file-failure.d.ts +27 -0
- package/dist/publish-file-failure.d.ts.map +1 -0
- package/dist/publish-file-failure.js +21 -0
- package/dist/publish-file.d.ts +21 -0
- package/dist/publish-file.d.ts.map +1 -0
- package/dist/publish-file.js +351 -0
- package/dist/read-opened-file.d.ts +3 -0
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +1 -0
- package/dist/replace-file-copy-fallback.d.ts +34 -0
- package/dist/replace-file-copy-fallback.d.ts.map +1 -0
- package/dist/replace-file-copy-fallback.js +339 -0
- package/dist/replace-file.d.ts +6 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +39 -90
- package/dist/root-context.d.ts +4 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +20 -1
- package/dist/root-impl.d.ts +5 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +128 -225
- package/dist/root-path-existing.d.ts +3 -0
- package/dist/root-path-existing.d.ts.map +1 -0
- package/dist/root-path-existing.js +69 -0
- package/dist/root-path.d.ts +1 -2
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +67 -81
- package/dist/root-walk.d.ts +41 -0
- package/dist/root-walk.d.ts.map +1 -0
- package/dist/root-walk.js +114 -0
- package/dist/root.d.ts +2 -0
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts +5 -2
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +20 -2
- package/dist/secret-read-async.d.ts +4 -0
- package/dist/secret-read-async.d.ts.map +1 -0
- package/dist/secret-read-async.js +95 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +2 -13
- package/dist/sidecar-lock-handle.d.ts +20 -0
- package/dist/sidecar-lock-handle.d.ts.map +1 -0
- package/dist/sidecar-lock-handle.js +28 -0
- package/dist/sidecar-lock-policy.d.ts +10 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -0
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts +22 -6
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +106 -20
- package/dist/sidecar-lock-types.d.ts +56 -0
- package/dist/sidecar-lock-types.d.ts.map +1 -0
- package/dist/sidecar-lock-types.js +1 -0
- package/dist/sidecar-lock.d.ts +2 -45
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +94 -55
- package/dist/temp-cleanup.d.ts +7 -1
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +35 -5
- package/dist/temp.d.ts +2 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +3 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/windows-permissions-native.d.ts +9 -0
- package/dist/windows-permissions-native.d.ts.map +1 -0
- package/dist/windows-permissions-native.js +31 -0
- package/docs/advanced.md +1 -1
- package/docs/archive.md +131 -13
- package/docs/atomic.md +30 -1
- package/docs/config.md +51 -21
- package/docs/durability.md +299 -0
- package/docs/errors.md +36 -6
- package/docs/filename.md +0 -0
- package/docs/index.md +11 -7
- package/docs/install.md +27 -26
- package/docs/json-store.md +18 -3
- package/docs/migrating-to-0.5.md +196 -0
- package/docs/native-helper.md +85 -0
- package/docs/native.md +134 -0
- package/docs/output.md +37 -11
- package/docs/path.md +1 -1
- package/docs/permissions.md +84 -1
- package/docs/quickstart.md +5 -2
- package/docs/reading.md +4 -4
- package/docs/root.md +31 -15
- package/docs/secret-file.md +50 -1
- package/docs/security-model.md +42 -8
- package/docs/sidecar-lock.md +92 -7
- package/docs/temp.md +24 -1
- package/docs/test-hooks.md +22 -2
- package/docs/testing.md +18 -7
- package/docs/types.md +3 -1
- package/docs/walk.md +55 -0
- package/docs/writing.md +3 -3
- package/package.json +12 -2
- package/dist/pinned-helper.d.ts +0 -9
- package/dist/pinned-helper.d.ts.map +0 -1
- package/dist/pinned-helper.js +0 -19
- package/dist/pinned-path.d.ts +0 -7
- package/dist/pinned-path.d.ts.map +0 -1
- package/dist/pinned-path.js +0 -21
- package/dist/pinned-python-config.d.ts +0 -9
- package/dist/pinned-python-config.d.ts.map +0 -1
- package/dist/pinned-python-config.js +0 -38
- package/dist/pinned-python.d.ts +0 -12
- package/dist/pinned-python.d.ts.map +0 -1
- package/dist/pinned-python.js +0 -688
- package/docs/python-helper.md +0 -103
package/dist/archive.js
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import { constants as fsConstants } from "node:fs";
|
|
1
|
+
import fsSync, { constants as fsConstants } from "node:fs";
|
|
2
2
|
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
6
|
import { resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
7
7
|
import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
|
|
8
|
-
import {
|
|
9
|
-
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, } from "./archive-limits.js";
|
|
8
|
+
import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, } from "./archive-limits.js";
|
|
10
9
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
11
10
|
import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
12
11
|
import { createTarEntryPreflightChecker, readTarEntryInfo, } from "./archive-tar.js";
|
|
13
12
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
14
|
-
import {
|
|
13
|
+
import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
|
|
14
|
+
import { FsSafeError } from "./errors.js";
|
|
15
|
+
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
16
|
+
import { extractNativeArchive } from "./archive-native.js";
|
|
17
|
+
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
18
|
+
import { getNativeBinding } from "./native.js";
|
|
19
|
+
import { resolveArchiveEntryMode, shouldExtractArchiveEntry, } from "./archive-policy.js";
|
|
20
|
+
import { importOptionalTar } from "./archive-tar-runtime.js";
|
|
21
|
+
import { preflightTarMetadata } from "./archive-tar-meta.js";
|
|
15
22
|
import { writeSiblingTempFile } from "./sibling-temp.js";
|
|
16
|
-
import { tempFile } from "./temp-target.js";
|
|
17
23
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
18
24
|
export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
|
|
19
|
-
export {
|
|
25
|
+
export { readArchiveEntry } from "./archive-read.js";
|
|
26
|
+
export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
|
|
27
|
+
export { ArchiveFormatError } from "./archive-errors.js";
|
|
20
28
|
export { ArchiveSecurityError } from "./archive-staging.js";
|
|
21
29
|
export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
22
30
|
export { createTarEntryPreflightChecker } from "./archive-tar.js";
|
|
@@ -26,91 +34,6 @@ const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
|
|
|
26
34
|
fsConstants.O_CREAT |
|
|
27
35
|
fsConstants.O_EXCL |
|
|
28
36
|
(SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
|
|
29
|
-
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
30
|
-
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
31
|
-
function isZipSymlinkEntry(entry) {
|
|
32
|
-
return (typeof entry.unixPermissions === "number" &&
|
|
33
|
-
(entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE);
|
|
34
|
-
}
|
|
35
|
-
function zipEntryFileMode(entry) {
|
|
36
|
-
if (typeof entry.unixPermissions !== "number") {
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
const mode = entry.unixPermissions & 0o777;
|
|
40
|
-
return mode === 0 ? undefined : mode;
|
|
41
|
-
}
|
|
42
|
-
async function cleanupStagedArchiveFile(staged) {
|
|
43
|
-
if (staged) {
|
|
44
|
-
await staged.cleanup().catch(() => undefined);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
async function closeFileHandle(handle) {
|
|
48
|
-
if (handle) {
|
|
49
|
-
await handle.close().catch(() => undefined);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async function stageArchiveFileForExtraction(params) {
|
|
53
|
-
params.deadline.check();
|
|
54
|
-
const sourcePath = path.resolve(params.archivePath);
|
|
55
|
-
const initialStat = await fs.lstat(sourcePath);
|
|
56
|
-
if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
|
|
57
|
-
throw new Error(`archive is not a regular file: ${params.archivePath}`);
|
|
58
|
-
}
|
|
59
|
-
if (initialStat.size > params.limits.maxArchiveBytes) {
|
|
60
|
-
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
|
|
61
|
-
}
|
|
62
|
-
const noFollow = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants ? fsConstants.O_NOFOLLOW : 0;
|
|
63
|
-
const handle = await fs.open(sourcePath, fsConstants.O_RDONLY | noFollow);
|
|
64
|
-
let staged;
|
|
65
|
-
let output;
|
|
66
|
-
try {
|
|
67
|
-
staged = await tempFile({
|
|
68
|
-
prefix: "fs-safe-archive-input",
|
|
69
|
-
fileName: path.basename(sourcePath),
|
|
70
|
-
});
|
|
71
|
-
const openedStat = await handle.stat();
|
|
72
|
-
const pathStat = await fs.lstat(sourcePath);
|
|
73
|
-
if (!openedStat.isFile() ||
|
|
74
|
-
pathStat.isSymbolicLink() ||
|
|
75
|
-
!pathStat.isFile() ||
|
|
76
|
-
!sameFileIdentity(initialStat, openedStat) ||
|
|
77
|
-
!sameFileIdentity(pathStat, openedStat)) {
|
|
78
|
-
throw new Error("archive changed during validation");
|
|
79
|
-
}
|
|
80
|
-
output = await fs.open(staged.path, OPEN_WRITE_CREATE_FLAGS, 0o600);
|
|
81
|
-
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
82
|
-
let written = 0;
|
|
83
|
-
while (true) {
|
|
84
|
-
params.deadline.check();
|
|
85
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
86
|
-
if (bytesRead === 0) {
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
written += bytesRead;
|
|
90
|
-
if (written > params.limits.maxArchiveBytes) {
|
|
91
|
-
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
|
|
92
|
-
}
|
|
93
|
-
await writeFileHandleFully({
|
|
94
|
-
handle: output,
|
|
95
|
-
buffer,
|
|
96
|
-
bytes: bytesRead,
|
|
97
|
-
deadline: params.deadline,
|
|
98
|
-
});
|
|
99
|
-
params.deadline.check();
|
|
100
|
-
}
|
|
101
|
-
await output.close();
|
|
102
|
-
output = undefined;
|
|
103
|
-
return staged;
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
await closeFileHandle(output);
|
|
107
|
-
await cleanupStagedArchiveFile(staged);
|
|
108
|
-
throw err;
|
|
109
|
-
}
|
|
110
|
-
finally {
|
|
111
|
-
await closeFileHandle(handle);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
37
|
async function readZipEntryStream(entry) {
|
|
115
38
|
if (typeof entry.nodeStream === "function") {
|
|
116
39
|
return entry.nodeStream();
|
|
@@ -150,7 +73,7 @@ async function writeZipFileEntry(params) {
|
|
|
150
73
|
dir: path.dirname(destinationPath),
|
|
151
74
|
tempPrefix: `.${path.basename(destinationPath)}.fs-safe-archive`,
|
|
152
75
|
chmodDir: false,
|
|
153
|
-
mode:
|
|
76
|
+
mode: params.mode,
|
|
154
77
|
writeTemp: async (tempPath) => {
|
|
155
78
|
tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o666);
|
|
156
79
|
const writable = tempHandle.createWriteStream();
|
|
@@ -218,6 +141,18 @@ async function extractZip(params) {
|
|
|
218
141
|
if (!output) {
|
|
219
142
|
continue;
|
|
220
143
|
}
|
|
144
|
+
assertArchiveEntryPathComponentsWithinLimit(output.relPath, limits);
|
|
145
|
+
const isSymlink = isZipSymlinkEntry(entry);
|
|
146
|
+
const entryKind = isSymlink ? "symlink" : entry.dir ? "directory" : "file";
|
|
147
|
+
const entrySize = zipEntryDeclaredSize(entry);
|
|
148
|
+
if (!shouldExtractArchiveEntry({
|
|
149
|
+
filter: params.entryFilter,
|
|
150
|
+
onFiltered: params.onFiltered,
|
|
151
|
+
entry: { path: entry.name, kind: entryKind, size: entrySize },
|
|
152
|
+
})) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const mode = zipEntryMode(entry, params.entryModes);
|
|
221
156
|
await prepareZipOutputPath({
|
|
222
157
|
destinationDir: stagingRealDir,
|
|
223
158
|
destinationRealDir: stagingRealDir,
|
|
@@ -227,16 +162,18 @@ async function extractZip(params) {
|
|
|
227
162
|
isDirectory: entry.dir,
|
|
228
163
|
});
|
|
229
164
|
if (entry.dir) {
|
|
165
|
+
await fs.chmod(output.outPath, mode);
|
|
230
166
|
continue;
|
|
231
167
|
}
|
|
232
|
-
if (
|
|
233
|
-
throw new
|
|
168
|
+
if (isSymlink) {
|
|
169
|
+
throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
|
|
234
170
|
}
|
|
235
171
|
await writeZipFileEntry({
|
|
236
172
|
entry,
|
|
237
173
|
outPath: output.outPath,
|
|
238
174
|
budget,
|
|
239
175
|
deadline: params.deadline,
|
|
176
|
+
mode,
|
|
240
177
|
});
|
|
241
178
|
}
|
|
242
179
|
params.deadline.check();
|
|
@@ -259,6 +196,25 @@ export async function extractArchive(params) {
|
|
|
259
196
|
throw new Error(`unsupported archive: ${params.archivePath}`);
|
|
260
197
|
}
|
|
261
198
|
const label = kind === "zip" ? "extract zip" : "extract tar";
|
|
199
|
+
const native = getNativeBinding();
|
|
200
|
+
if (native) {
|
|
201
|
+
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractNativeArchive({
|
|
202
|
+
binding: native,
|
|
203
|
+
archivePath: params.archivePath,
|
|
204
|
+
destDir: params.destDir,
|
|
205
|
+
kind,
|
|
206
|
+
stripComponents: params.stripComponents,
|
|
207
|
+
limits: params.limits,
|
|
208
|
+
deadline,
|
|
209
|
+
entryModes: params.entryModes,
|
|
210
|
+
entryFilter: params.entryFilter,
|
|
211
|
+
onFiltered: params.onFiltered,
|
|
212
|
+
}));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (kind === "tar-zstd" || kind === "tar-bzip2") {
|
|
216
|
+
throw new FsSafeError("helper-unavailable", `${kind} archives require a supported bundled native binding`);
|
|
217
|
+
}
|
|
262
218
|
if (kind === "tar") {
|
|
263
219
|
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
|
|
264
220
|
const tar = await importOptionalTar();
|
|
@@ -269,6 +225,12 @@ export async function extractArchive(params) {
|
|
|
269
225
|
deadline,
|
|
270
226
|
});
|
|
271
227
|
try {
|
|
228
|
+
await preflightTarMetadata({
|
|
229
|
+
archivePath: stagedArchive.path,
|
|
230
|
+
maxMetaEntryBytes: limits.maxMetaEntryBytes,
|
|
231
|
+
signal: deadline.signal,
|
|
232
|
+
});
|
|
233
|
+
deadline.check();
|
|
272
234
|
const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
|
|
273
235
|
await withStagedArchiveDestination({
|
|
274
236
|
destinationRealDir,
|
|
@@ -278,23 +240,56 @@ export async function extractArchive(params) {
|
|
|
278
240
|
rootDir: destinationRealDir,
|
|
279
241
|
stripComponents: params.stripComponents,
|
|
280
242
|
limits,
|
|
243
|
+
entryFilter: params.entryFilter,
|
|
244
|
+
onFiltered: params.onFiltered,
|
|
281
245
|
});
|
|
246
|
+
const acceptedEntries = [];
|
|
282
247
|
// A canonical cwd is not enough here: tar can still follow
|
|
283
248
|
// pre-existing child symlinks in the live destination tree.
|
|
284
249
|
// Extract into a private staging dir first, then merge through
|
|
285
250
|
// the same safe-open boundary checks used by direct file writes.
|
|
286
|
-
|
|
287
|
-
file: stagedArchive.path,
|
|
251
|
+
const extractor = tar.x({
|
|
288
252
|
cwd: stagingDir,
|
|
289
253
|
strip: Math.max(0, Math.floor(params.stripComponents ?? 0)),
|
|
290
254
|
gzip: params.tarGzip,
|
|
291
255
|
signal: deadline.signal,
|
|
292
256
|
preservePaths: false,
|
|
257
|
+
noChmod: true,
|
|
258
|
+
preserveOwner: false,
|
|
293
259
|
strict: true,
|
|
260
|
+
maxMetaEntrySize: limits.maxMetaEntryBytes,
|
|
261
|
+
filter(_entryPath, entry) {
|
|
262
|
+
try {
|
|
263
|
+
const info = readTarEntryInfo(entry);
|
|
264
|
+
const accepted = checkTarEntrySafety(info);
|
|
265
|
+
if (accepted) {
|
|
266
|
+
const relPath = stripArchivePath(info.path, Math.max(0, Math.floor(params.stripComponents ?? 0)));
|
|
267
|
+
if (relPath) {
|
|
268
|
+
acceptedEntries.push({
|
|
269
|
+
path: relPath,
|
|
270
|
+
mode: resolveArchiveEntryMode({
|
|
271
|
+
kind: info.type === "Directory" || info.type === "GNUDumpDir"
|
|
272
|
+
? "directory"
|
|
273
|
+
: "file",
|
|
274
|
+
archivedMode: info.mode,
|
|
275
|
+
policy: params.entryModes,
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return accepted;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
// Abort through the parser so pipeline tears down both the
|
|
284
|
+
// archive reader and unpacker instead of leaving a paused
|
|
285
|
+
// stream behind after a policy rejection.
|
|
286
|
+
this.abort(error instanceof Error ? error : new Error(String(error)));
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
294
290
|
onReadEntry(entry) {
|
|
295
291
|
try {
|
|
296
292
|
deadline.check();
|
|
297
|
-
checkTarEntrySafety(readTarEntryInfo(entry));
|
|
298
293
|
}
|
|
299
294
|
catch (err) {
|
|
300
295
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -305,6 +300,22 @@ export async function extractArchive(params) {
|
|
|
305
300
|
}
|
|
306
301
|
},
|
|
307
302
|
});
|
|
303
|
+
try {
|
|
304
|
+
await pipeline(fsSync.createReadStream(stagedArchive.path), extractor, {
|
|
305
|
+
signal: deadline.signal,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
throw createPipelineTimeoutError(error, deadline);
|
|
310
|
+
}
|
|
311
|
+
for (const accepted of acceptedEntries) {
|
|
312
|
+
const outputPath = resolveArchiveOutputPath({
|
|
313
|
+
rootDir: stagingDir,
|
|
314
|
+
relPath: accepted.path,
|
|
315
|
+
originalPath: accepted.path,
|
|
316
|
+
});
|
|
317
|
+
await fs.chmod(outputPath, accepted.mode);
|
|
318
|
+
}
|
|
308
319
|
deadline.check();
|
|
309
320
|
await mergeExtractedTreeIntoDestination({
|
|
310
321
|
sourceDir: stagingDir,
|
|
@@ -327,16 +338,8 @@ export async function extractArchive(params) {
|
|
|
327
338
|
stripComponents: params.stripComponents,
|
|
328
339
|
limits: params.limits,
|
|
329
340
|
deadline,
|
|
341
|
+
entryModes: params.entryModes,
|
|
342
|
+
entryFilter: params.entryFilter,
|
|
343
|
+
onFiltered: params.onFiltered,
|
|
330
344
|
}));
|
|
331
345
|
}
|
|
332
|
-
async function importOptionalTar() {
|
|
333
|
-
try {
|
|
334
|
-
return await import("tar");
|
|
335
|
-
}
|
|
336
|
-
catch (err) {
|
|
337
|
-
throw missingOptionalArchiveDependencyError("tar", err);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function missingOptionalArchiveDependencyError(packageName, cause) {
|
|
341
|
-
return new Error(`Optional archive dependency "${packageName}" is not installed. Install it to use TAR archive helpers from @openclaw/fs-safe/archive.`, { cause });
|
|
342
|
-
}
|
package/dist/atomic.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
1
|
+
export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicRestoreCleanup, type ReplaceFileAtomicRestoreFailureDetails, type ReplaceFileAtomicResult, type ReplaceFileCopyFallbackRestorePolicy, type ReplaceFileDestinationHardlinkPolicy, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
2
2
|
export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
|
|
3
3
|
export { replaceDirectoryAtomic, type ReplaceDirectoryAtomicOptions } from "./replace-directory.js";
|
|
4
4
|
export { movePathWithCopyFallback, type MovePathWithCopyFallbackOptions } from "./move-path.js";
|
package/dist/atomic.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACpC,KAAK,sCAAsC,EAC3C,KAAK,uBAAuB,EAC5B,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { configureFsSafePython,
|
|
1
|
+
export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, type FsSafePythonConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
|
|
2
2
|
export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
|
|
3
3
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { configureFsSafePython,
|
|
1
|
+
export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
|
|
2
2
|
export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"containment.d.ts","sourceRoot":"","sources":["../src/containment.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Stats } from "node:fs";
|
|
2
|
+
import { type FileIdentityStat } from "./file-identity.js";
|
|
3
|
+
export type DirectorySyncOutcome = {
|
|
4
|
+
status: "synced";
|
|
5
|
+
} | {
|
|
6
|
+
status: "unsupported";
|
|
7
|
+
code?: string;
|
|
8
|
+
};
|
|
9
|
+
export type DirectoryReceipt = {
|
|
10
|
+
path: string;
|
|
11
|
+
realPath: string;
|
|
12
|
+
identity: Stats;
|
|
13
|
+
};
|
|
14
|
+
export type DurableDirectoryReceipt = DirectoryReceipt & {
|
|
15
|
+
parentSync: DirectorySyncOutcome | {
|
|
16
|
+
status: "not-needed";
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type PinnedDirectory = {
|
|
20
|
+
readonly receipt: DirectoryReceipt;
|
|
21
|
+
assertCurrent(): Promise<void>;
|
|
22
|
+
sync(): Promise<DirectorySyncOutcome>;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
export type EnsureDurableDirectoryOptions = {
|
|
26
|
+
directoryPath: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
mode?: number;
|
|
29
|
+
expectedExistingIdentity?: FileIdentityStat;
|
|
30
|
+
create?: (directoryPath: string) => Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
export declare function pinDirectory(directory: string | DirectoryReceipt, options?: {
|
|
33
|
+
label?: string;
|
|
34
|
+
}): Promise<PinnedDirectory>;
|
|
35
|
+
export declare function syncDirectory(directory: string | DirectoryReceipt, options?: {
|
|
36
|
+
label?: string;
|
|
37
|
+
}): Promise<DirectorySyncOutcome>;
|
|
38
|
+
export declare function syncDirectorySync(directory: string | DirectoryReceipt, options?: {
|
|
39
|
+
label?: string;
|
|
40
|
+
}): DirectorySyncOutcome;
|
|
41
|
+
export declare function syncDirectoryBestEffort(directoryPath: string): Promise<void>;
|
|
42
|
+
export declare function syncDirectoryBestEffortSync(directoryPath: string): void;
|
|
43
|
+
export declare function ensureDurableDirectory(options: EnsureDurableDirectoryOptions): Promise<DurableDirectoryReceipt>;
|
|
44
|
+
//# sourceMappingURL=directory-durability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
|