@openclaw/fs-safe 0.5.6 → 0.7.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 +89 -0
- package/README.md +23 -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-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-kind.js +2 -2
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +31 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +88 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +135 -19
- 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 +4 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- 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 +63 -42
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +26 -9
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +27 -8
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +15 -3
- 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 +22 -6
- 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.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- 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-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +26 -37
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +118 -98
- 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 +49 -120
- 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 +51 -72
- 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 +42 -29
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +48 -27
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +29 -7
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- 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/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- 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 +19 -2
- package/docs/archive.md +250 -35
- package/docs/atomic.md +11 -2
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +18 -5
- package/docs/errors.md +16 -1
- package/docs/file-store.md +2 -0
- package/docs/index.md +3 -1
- package/docs/install.md +20 -8
- package/docs/json.md +8 -4
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +30 -8
- package/docs/native.md +73 -17
- package/docs/output.md +10 -0
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +3 -1
- package/docs/secret-file.md +12 -0
- package/docs/secure-file.md +21 -3
- package/docs/security-model.md +33 -1
- package/docs/sidecar-lock.md +15 -1
- package/docs/staged-file.md +178 -0
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- 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
package/dist/archive-staging.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import fsSync from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
4
|
+
import { ownExtractionDestinationMutation, } from "./archive-deadline.js";
|
|
5
5
|
import { ArchiveSecurityError, } from "./archive-errors.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { formatErrorDetail } from "./error-detail.js";
|
|
8
8
|
import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
|
|
9
9
|
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
10
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
11
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
11
12
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
12
13
|
const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
|
|
13
14
|
const ARCHIVE_STAGING_MODE = 0o700;
|
|
15
|
+
function checkExtractionDeadline(deadline) {
|
|
16
|
+
deadline?.check();
|
|
17
|
+
}
|
|
14
18
|
export { ArchiveSecurityError } from "./archive-errors.js";
|
|
15
19
|
function symlinkTraversalError(originalPath) {
|
|
16
20
|
return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${formatErrorDetail(originalPath)}`);
|
|
@@ -105,55 +109,79 @@ async function mkdirArchiveOutput(params) {
|
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
export async function prepareArchiveOutputPath(params) {
|
|
112
|
+
checkExtractionDeadline(params.deadline);
|
|
108
113
|
const targetRoot = await root(params.destinationRealDir);
|
|
114
|
+
checkExtractionDeadline(params.deadline);
|
|
109
115
|
const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
|
|
116
|
+
checkExtractionDeadline(params.deadline);
|
|
110
117
|
const relPath = params.relPath.split(path.sep).join(path.posix.sep);
|
|
111
118
|
await assertNoSymlinkTraversal({
|
|
112
119
|
rootDir: params.destinationDir,
|
|
113
120
|
relPath,
|
|
114
121
|
originalPath: params.originalPath,
|
|
115
122
|
});
|
|
123
|
+
checkExtractionDeadline(params.deadline);
|
|
116
124
|
if (params.isDirectory) {
|
|
117
125
|
await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", params.outPath);
|
|
118
|
-
|
|
119
|
-
await
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
checkExtractionDeadline(params.deadline);
|
|
127
|
+
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
128
|
+
await assertDirectoryIdentityGuard(destinationGuard);
|
|
129
|
+
checkExtractionDeadline(params.deadline);
|
|
130
|
+
await mkdirArchiveOutput({
|
|
131
|
+
targetRoot,
|
|
132
|
+
relativePath: relPath,
|
|
133
|
+
originalPath: params.originalPath,
|
|
134
|
+
});
|
|
135
|
+
checkExtractionDeadline(params.deadline);
|
|
136
|
+
await assertDirectoryIdentityGuard(destinationGuard);
|
|
137
|
+
checkExtractionDeadline(params.deadline);
|
|
138
|
+
await assertResolvedInsideDestination({
|
|
139
|
+
destinationRealDir: params.destinationRealDir,
|
|
140
|
+
targetPath: params.outPath,
|
|
141
|
+
originalPath: params.originalPath,
|
|
142
|
+
});
|
|
143
|
+
checkExtractionDeadline(params.deadline);
|
|
125
144
|
});
|
|
126
145
|
return;
|
|
127
146
|
}
|
|
128
147
|
const parentRel = path.posix.dirname(relPath);
|
|
129
148
|
if (parentRel !== ".") {
|
|
130
149
|
await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", path.dirname(params.outPath));
|
|
131
|
-
|
|
132
|
-
await
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
checkExtractionDeadline(params.deadline);
|
|
151
|
+
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
152
|
+
await assertDirectoryIdentityGuard(destinationGuard);
|
|
153
|
+
checkExtractionDeadline(params.deadline);
|
|
154
|
+
await mkdirArchiveOutput({
|
|
155
|
+
targetRoot,
|
|
156
|
+
relativePath: parentRel,
|
|
157
|
+
originalPath: params.originalPath,
|
|
158
|
+
});
|
|
159
|
+
checkExtractionDeadline(params.deadline);
|
|
160
|
+
await assertDirectoryIdentityGuard(destinationGuard);
|
|
161
|
+
checkExtractionDeadline(params.deadline);
|
|
136
162
|
});
|
|
137
|
-
await assertDirectoryIdentityGuard(destinationGuard);
|
|
138
163
|
}
|
|
139
164
|
await assertResolvedInsideDestination({
|
|
140
165
|
destinationRealDir: params.destinationRealDir,
|
|
141
166
|
targetPath: path.dirname(params.outPath),
|
|
142
167
|
originalPath: params.originalPath,
|
|
143
168
|
});
|
|
169
|
+
checkExtractionDeadline(params.deadline);
|
|
144
170
|
}
|
|
145
171
|
async function chmodInsideDestinationBestEffort(params) {
|
|
146
172
|
await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", params.destinationPath);
|
|
173
|
+
checkExtractionDeadline(params.deadline);
|
|
147
174
|
const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
|
|
175
|
+
checkExtractionDeadline(params.deadline);
|
|
148
176
|
await assertDirectoryIdentityGuard(destinationGuard);
|
|
149
|
-
|
|
150
|
-
? fsSync.constants.O_NOFOLLOW
|
|
151
|
-
: 0;
|
|
177
|
+
checkExtractionDeadline(params.deadline);
|
|
152
178
|
const handle = await fs
|
|
153
|
-
.open(params.destinationPath,
|
|
179
|
+
.open(params.destinationPath, resolveReadOpenFlags())
|
|
154
180
|
.catch(() => null);
|
|
181
|
+
checkExtractionDeadline(params.deadline);
|
|
155
182
|
if (!handle) {
|
|
156
183
|
const stat = await fs.lstat(params.destinationPath).catch(() => null);
|
|
184
|
+
checkExtractionDeadline(params.deadline);
|
|
157
185
|
if (stat?.isSymbolicLink()) {
|
|
158
186
|
throw symlinkTraversalError(params.originalPath);
|
|
159
187
|
}
|
|
@@ -161,35 +189,45 @@ async function chmodInsideDestinationBestEffort(params) {
|
|
|
161
189
|
}
|
|
162
190
|
try {
|
|
163
191
|
const stat = await handle.stat();
|
|
192
|
+
checkExtractionDeadline(params.deadline);
|
|
164
193
|
if (!stat.isDirectory() && !stat.isFile()) {
|
|
165
194
|
return;
|
|
166
195
|
}
|
|
167
196
|
const realPath = await resolveOpenedFileRealPathForHandle(handle, params.destinationPath);
|
|
197
|
+
checkExtractionDeadline(params.deadline);
|
|
168
198
|
if (!isPathInside(params.destinationRealDir, realPath)) {
|
|
169
199
|
throw symlinkTraversalError(params.originalPath);
|
|
170
200
|
}
|
|
171
|
-
await
|
|
172
|
-
|
|
201
|
+
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
202
|
+
await handle.chmod(params.mode).catch(() => undefined);
|
|
203
|
+
checkExtractionDeadline(params.deadline);
|
|
204
|
+
await assertDirectoryIdentityGuard(destinationGuard);
|
|
205
|
+
checkExtractionDeadline(params.deadline);
|
|
206
|
+
});
|
|
173
207
|
}
|
|
174
208
|
finally {
|
|
175
209
|
await handle.close().catch(() => undefined);
|
|
176
210
|
}
|
|
177
211
|
}
|
|
178
212
|
async function applyStagedEntryMode(params) {
|
|
213
|
+
checkExtractionDeadline(params.deadline);
|
|
179
214
|
const destinationPath = path.join(params.destinationRealDir, params.relPath);
|
|
180
215
|
await assertResolvedInsideDestination({
|
|
181
216
|
destinationRealDir: params.destinationRealDir,
|
|
182
217
|
targetPath: destinationPath,
|
|
183
218
|
originalPath: params.originalPath,
|
|
184
219
|
});
|
|
220
|
+
checkExtractionDeadline(params.deadline);
|
|
185
221
|
if (params.mode !== 0) {
|
|
186
222
|
await chmodInsideDestinationBestEffort({
|
|
187
223
|
destinationRealDir: params.destinationRealDir,
|
|
188
224
|
destinationPath,
|
|
189
225
|
mode: params.mode,
|
|
190
226
|
originalPath: params.originalPath,
|
|
227
|
+
deadline: params.deadline,
|
|
191
228
|
});
|
|
192
229
|
}
|
|
230
|
+
checkExtractionDeadline(params.deadline);
|
|
193
231
|
}
|
|
194
232
|
async function assertExtractedFileHasNoHardlinkAlias(params) {
|
|
195
233
|
const destinationPath = path.join(params.destinationRealDir, params.relPath);
|
|
@@ -296,6 +334,7 @@ export async function mergeExtractedTreeIntoDestination(params) {
|
|
|
296
334
|
outPath: destinationPath,
|
|
297
335
|
originalPath,
|
|
298
336
|
isDirectory: true,
|
|
337
|
+
deadline: params.deadline,
|
|
299
338
|
});
|
|
300
339
|
await walk(sourcePath);
|
|
301
340
|
await applyStagedEntryMode({
|
|
@@ -303,6 +342,7 @@ export async function mergeExtractedTreeIntoDestination(params) {
|
|
|
303
342
|
relPath,
|
|
304
343
|
mode: sourceStat.mode & 0o777,
|
|
305
344
|
originalPath,
|
|
345
|
+
deadline: params.deadline,
|
|
306
346
|
});
|
|
307
347
|
continue;
|
|
308
348
|
}
|
|
@@ -316,31 +356,39 @@ export async function mergeExtractedTreeIntoDestination(params) {
|
|
|
316
356
|
outPath: destinationPath,
|
|
317
357
|
originalPath,
|
|
318
358
|
isDirectory: false,
|
|
359
|
+
deadline: params.deadline,
|
|
319
360
|
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
});
|
|
339
|
-
if (err instanceof FsSafeError && (err.code === "hardlink" || err.code === "path-alias")) {
|
|
340
|
-
throw symlinkTraversalError(originalPath);
|
|
361
|
+
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
362
|
+
try {
|
|
363
|
+
await targetRoot.copyIn(relPath, sourcePath, { mkdir: true });
|
|
364
|
+
checkExtractionDeadline(params.deadline);
|
|
365
|
+
await assertExtractedFileHasNoHardlinkAlias({
|
|
366
|
+
destinationRealDir: params.destinationRealDir,
|
|
367
|
+
relPath,
|
|
368
|
+
originalPath,
|
|
369
|
+
});
|
|
370
|
+
checkExtractionDeadline(params.deadline);
|
|
371
|
+
await applyStagedEntryMode({
|
|
372
|
+
destinationRealDir: params.destinationRealDir,
|
|
373
|
+
relPath,
|
|
374
|
+
mode: sourceStat.mode & 0o777,
|
|
375
|
+
originalPath,
|
|
376
|
+
deadline: params.deadline,
|
|
377
|
+
});
|
|
378
|
+
checkExtractionDeadline(params.deadline);
|
|
341
379
|
}
|
|
342
|
-
|
|
343
|
-
|
|
380
|
+
catch (err) {
|
|
381
|
+
await removeExtractedDestinationFile({
|
|
382
|
+
destinationRealDir: params.destinationRealDir,
|
|
383
|
+
relPath,
|
|
384
|
+
});
|
|
385
|
+
if (err instanceof FsSafeError &&
|
|
386
|
+
(err.code === "hardlink" || err.code === "path-alias")) {
|
|
387
|
+
throw symlinkTraversalError(originalPath);
|
|
388
|
+
}
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
344
392
|
}
|
|
345
393
|
};
|
|
346
394
|
await walk(params.sourceDir);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TarEntryInfo } from "./archive-tar.js";
|
|
2
|
+
export declare function rawTarMember(header: Buffer, size: number, effectivePath?: string): TarEntryInfo;
|
|
3
|
+
export declare function createTarAdmissionPlan(manifest: readonly TarEntryInfo[], check: (entry: TarEntryInfo) => boolean, strip: number): {
|
|
4
|
+
consume(entry: TarEntryInfo): string | null;
|
|
5
|
+
finish(): void;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=archive-tar-admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-tar-admission.d.ts","sourceRoot":"","sources":["../src/archive-tar-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAYrD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,CAS/F;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,EACvC,KAAK,EAAE,MAAM,GACZ;IAAE,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,IAAI,IAAI,CAAA;CAAE,CAmBjE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ArchiveFormatError } from "./archive-errors.js";
|
|
2
|
+
import { stripArchivePath, validateArchiveEntryPath } from "./archive-entry.js";
|
|
3
|
+
import { readTarHeaderPaths } from "./archive-tar-header.js";
|
|
4
|
+
// node-tar's normalFsTypes/ReadEntry switch. All other non-metadata types
|
|
5
|
+
// bypass both its filter and entry event; they still belong to our manifest.
|
|
6
|
+
// Header.decode normalizes NUL to "0" before choosing the emitted File type.
|
|
7
|
+
const visibleTypes = new Map([
|
|
8
|
+
[0, "File"], [0x30, "File"], [0x31, "Link"], [0x32, "SymbolicLink"],
|
|
9
|
+
[0x33, "CharacterDevice"], [0x34, "BlockDevice"], [0x35, "Directory"],
|
|
10
|
+
[0x36, "FIFO"], [0x37, "ContiguousFile"], [0x44, "GNUDumpDir"],
|
|
11
|
+
]);
|
|
12
|
+
export function rawTarMember(header, size, effectivePath) {
|
|
13
|
+
const { name, prefix } = readTarHeaderPaths(header);
|
|
14
|
+
validateArchiveEntryPath(name);
|
|
15
|
+
validateArchiveEntryPath(prefix);
|
|
16
|
+
const rawPath = prefix ? `${prefix}/${name}` : name;
|
|
17
|
+
validateArchiveEntryPath(rawPath);
|
|
18
|
+
const path = effectivePath ?? rawPath;
|
|
19
|
+
validateArchiveEntryPath(path);
|
|
20
|
+
return { path, size, type: visibleTypes.get(header[156]) ?? "Unsupported" };
|
|
21
|
+
}
|
|
22
|
+
export function createTarAdmissionPlan(manifest, check, strip) {
|
|
23
|
+
const visible = [];
|
|
24
|
+
for (const entry of manifest) {
|
|
25
|
+
const accepted = check(entry);
|
|
26
|
+
if (entry.type !== "Unsupported") {
|
|
27
|
+
visible.push({ entry, output: accepted ? stripArchivePath(entry.path, strip) : null });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let index = 0;
|
|
31
|
+
const mismatch = () => new ArchiveFormatError("invalid TAR header: parser disagrees with raw admission");
|
|
32
|
+
return {
|
|
33
|
+
consume(actual) {
|
|
34
|
+
const expected = visible[index++];
|
|
35
|
+
if (!expected || expected.entry.type !== actual.type || expected.entry.size !== actual.size ||
|
|
36
|
+
stripArchivePath(expected.entry.path, 0) !== stripArchivePath(actual.path, 0))
|
|
37
|
+
throw mismatch();
|
|
38
|
+
return expected.output;
|
|
39
|
+
},
|
|
40
|
+
finish() { if (index !== visible.length)
|
|
41
|
+
throw mismatch(); },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-tar-gnu.d.ts","sourceRoot":"","sources":["../src/archive-tar-gnu.ts"],"names":[],"mappings":"AAKA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,CAazE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ArchiveFormatError } from "./archive-errors.js";
|
|
2
|
+
import { validateArchiveEntryPath } from "./archive-entry.js";
|
|
3
|
+
// Keep the byte grammar aligned with native/src/tar_meter.rs. Parsers differ
|
|
4
|
+
// on embedded NULs and malformed UTF-8, so validate before either sees a name.
|
|
5
|
+
export function validateGnuMetadata(body, type) {
|
|
6
|
+
const value = body.at(-1) === 0 ? body.subarray(0, -1) : body;
|
|
7
|
+
if (!value.length || value.includes(0)) {
|
|
8
|
+
throw new ArchiveFormatError("invalid GNU metadata: empty name or embedded NUL");
|
|
9
|
+
}
|
|
10
|
+
let name;
|
|
11
|
+
try {
|
|
12
|
+
name = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(value);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new ArchiveFormatError("invalid GNU metadata: name is not valid UTF-8");
|
|
16
|
+
}
|
|
17
|
+
if (type === "L")
|
|
18
|
+
validateArchiveEntryPath(name);
|
|
19
|
+
return name;
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function validateTarChecksum(header: Buffer): void;
|
|
2
|
+
export declare function readTarHeaderPaths(header: Buffer): {
|
|
3
|
+
name: string;
|
|
4
|
+
prefix: string;
|
|
5
|
+
linkname: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function validateTarHeader(header: Buffer): void;
|
|
8
|
+
//# sourceMappingURL=archive-tar-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-tar-header.d.ts","sourceRoot":"","sources":["../src/archive-tar-header.ts"],"names":[],"mappings":"AAGA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAWxD;AAcD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CASrG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAUtD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
|
|
2
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
3
|
+
export function validateTarChecksum(header) {
|
|
4
|
+
const field = header.subarray(148, 156);
|
|
5
|
+
const end = field.indexOf(0);
|
|
6
|
+
const digits = field.subarray(0, end < 0 ? field.length : end).toString("latin1").replace(/^ +| +$/g, "");
|
|
7
|
+
const sum = header.reduce((total, byte, index) => total + (index >= 148 && index < 156 ? 32 : byte), 0);
|
|
8
|
+
// Require an in-field delimiter: node-tar otherwise reads into the typeflag.
|
|
9
|
+
if (!/^[0-7]+$/.test(digits) || ![0, 32].includes(field[7]) ||
|
|
10
|
+
(end >= 0 && field.subarray(end).some((byte) => byte !== 0 && byte !== 32)) ||
|
|
11
|
+
Number.parseInt(digits, 8) !== sum) {
|
|
12
|
+
throw new ArchiveFormatError("invalid TAR header: checksum failure");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function fixedField(field) {
|
|
16
|
+
const zero = field.indexOf(0);
|
|
17
|
+
if (zero >= 0 && field.subarray(zero).some((byte) => byte !== 0)) {
|
|
18
|
+
throw new ArchiveSecurityError("entry-path", "tar entry path contains bytes after NUL");
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return utf8.decode(field.subarray(0, zero < 0 ? field.length : zero));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new ArchiveSecurityError("entry-path", "tar entry path is not valid UTF-8");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function readTarHeaderPaths(header) {
|
|
28
|
+
// node-tar's star layout uses a 130-byte prefix followed by atime/ctime.
|
|
29
|
+
const prefixEnd = header[475] === 0 ? 475 : 500;
|
|
30
|
+
return {
|
|
31
|
+
name: fixedField(header.subarray(0, 100)),
|
|
32
|
+
prefix: header.subarray(257, 265).equals(Buffer.from("ustar\0" + "00"))
|
|
33
|
+
? fixedField(header.subarray(345, prefixEnd)) : "",
|
|
34
|
+
linkname: fixedField(header.subarray(157, 257)),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function validateTarHeader(header) {
|
|
38
|
+
validateTarChecksum(header);
|
|
39
|
+
const { linkname } = readTarHeaderPaths(header);
|
|
40
|
+
const isLink = header[156] === 0x31 || header[156] === 0x32;
|
|
41
|
+
if (isLink && !linkname) {
|
|
42
|
+
throw new ArchiveFormatError("invalid TAR header: linkname required on a link header");
|
|
43
|
+
}
|
|
44
|
+
if (!isLink && linkname) {
|
|
45
|
+
throw new ArchiveFormatError("invalid TAR header: linkname forbidden on a non-link header");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,6 +1,34 @@
|
|
|
1
|
+
import { Transform } from "node:stream";
|
|
2
|
+
import { type TarMeterLimits } from "./archive-limits.js";
|
|
3
|
+
import type { TarEntryInfo } from "./archive-tar.js";
|
|
4
|
+
export declare class TarMetadataMeter extends Transform {
|
|
5
|
+
private readonly limits;
|
|
6
|
+
private readonly onMember?;
|
|
7
|
+
private readonly block;
|
|
8
|
+
private blockLength;
|
|
9
|
+
private state;
|
|
10
|
+
private pendingPax;
|
|
11
|
+
private readonly pendingGnu;
|
|
12
|
+
private pendingGnuPath;
|
|
13
|
+
private zeroBlocks;
|
|
14
|
+
private entries;
|
|
15
|
+
private remainingDecodedBytes;
|
|
16
|
+
private manifestBytes;
|
|
17
|
+
constructor(limits: TarMeterLimits, onMember?: ((entry: TarEntryInfo) => void) | undefined);
|
|
18
|
+
private countMember;
|
|
19
|
+
private invalid;
|
|
20
|
+
private paddedSize;
|
|
21
|
+
private parseSize;
|
|
22
|
+
private finishHeader;
|
|
23
|
+
private finishSparseHeader;
|
|
24
|
+
private meter;
|
|
25
|
+
_transform(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null, data?: Buffer) => void): void;
|
|
26
|
+
_flush(callback: (error?: Error | null) => void): void;
|
|
27
|
+
}
|
|
1
28
|
export declare function preflightTarMetadata(params: {
|
|
2
29
|
archivePath: string;
|
|
3
|
-
|
|
30
|
+
limits: TarMeterLimits;
|
|
4
31
|
signal?: AbortSignal;
|
|
32
|
+
onMember?: (entry: TarEntryInfo) => void;
|
|
5
33
|
}): Promise<void>;
|
|
6
34
|
//# sourceMappingURL=archive-tar-meta.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar-meta.d.ts","sourceRoot":"","sources":["../src/archive-tar-meta.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-tar-meta.d.ts","sourceRoot":"","sources":["../src/archive-tar-meta.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAY,MAAM,aAAa,CAAC;AAIlD,OAAO,EAAqE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI7H,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAWrD,qBAAa,gBAAiB,SAAQ,SAAS;IAYjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAkB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAX/E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IACnD,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,aAAa,CAAK;IAE1B,YAA6B,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,aAAA,EAM7G;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,YAAY;IA6EpB,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,KAAK;IAyCJ,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAc3H;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAM9D;CACF;AAaD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC1C,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
|