@openclaw/fs-safe 0.5.2 → 0.5.3
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 +20 -0
- package/README.md +3 -2
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +12 -1
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +4 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +6 -3
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +43 -36
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +8 -0
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +11 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +1 -0
- package/dist/archive-zip-entry.d.ts +6 -1
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +11 -1
- package/dist/archive-zip-integrity.d.ts +2 -1
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +12 -2
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +6 -5
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +31 -16
- 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/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +21 -4
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +45 -4
- package/dist/read-error.d.ts +2 -0
- package/dist/read-error.d.ts.map +1 -0
- package/dist/read-error.js +11 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +10 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +5 -1
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +9 -8
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +10 -1
- package/docs/advanced.md +6 -0
- package/docs/archive.md +2 -2
- package/docs/errors.md +5 -3
- package/docs/file-store.md +4 -0
- package/docs/secret-file.md +4 -0
- package/docs/temp.md +5 -0
- package/docs/types.md +2 -2
- package/package.json +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.3 - 2026-08-08
|
|
4
|
+
|
|
5
|
+
### Security and Correctness
|
|
6
|
+
|
|
7
|
+
- Reject NTFS alternate data stream archive entry names on Windows before extraction, keep JavaScript and native TAR/ZIP policy aligned, and fix one-code-unit native rename and hardlink metadata buffers.
|
|
8
|
+
- Reject synchronous secret reads when the path is retargeted after the preview check, matching the asynchronous reader's `path-mismatch` contract instead of returning bytes from the replacement file.
|
|
9
|
+
- Preserve dangling symlinks when trash moves cross filesystems instead of failing while following their missing targets.
|
|
10
|
+
- Reject non-canonical FileStore keys and malformed archive names before filesystem access, keep JavaScript/native TAR and ZIP rejection semantics aligned (including full-width base-256 sizes and empty ZIP files), and add deterministic property-based regression coverage for path aliasing, parser boundaries, collisions, truncation, and extraction limits.
|
|
11
|
+
|
|
12
|
+
### Compatibility
|
|
13
|
+
|
|
14
|
+
- Report filesystem I/O failures from secret, `FileStore`, and temp-workspace twin readers as the new operational `read-failed` code with the original error in `cause`, replacing synchronous secret `invalid-path`, asynchronous secret and synchronous store `path-mismatch`, and raw Node errors; consumers matching the old wrapper or `EIO`-style top-level code should match `read-failed` and inspect `cause.code` instead.
|
|
15
|
+
- Preserve semantic path and validation codes in synchronous `FileStore` and temp-workspace reads: missing temp leaves now report `not-found`, stable directories report `not-file`, and hardlinks and symlinks report `hardlink` and `symlink`, replacing `path-mismatch` and fabricated raw `ENOENT` respectively to match their asynchronous twins; consumers treating either old result as absence or identity drift should match the specific path-state code instead.
|
|
16
|
+
- Report an existing non-directory ancestor as `not-file` from both `assertNoSymlinkParents()` variants, replacing asynchronous success and the synchronous helper's platform-dependent success or raw `ENOTDIR` under default `allowMissing`; callers relying on that acceptance should ensure every existing prefix component is a directory or handle `not-file`.
|
|
17
|
+
|
|
18
|
+
### Docs and Tooling
|
|
19
|
+
|
|
20
|
+
- Measure coverage once per operating system and merge the platform reports before enforcing thresholds, so coverage reflects existing cross-platform execution rather than implying new test coverage.
|
|
21
|
+
- Refresh Vite and its Rolldown toolchain, and declare the native build CLI's Emscripten runtime peer explicitly.
|
|
22
|
+
|
|
3
23
|
## 0.5.2 - 2026-08-02
|
|
4
24
|
|
|
5
25
|
### Security and Correctness
|
package/README.md
CHANGED
|
@@ -522,10 +522,11 @@ if (err instanceof FsSafeError) {
|
|
|
522
522
|
```
|
|
523
523
|
|
|
524
524
|
Routine filesystem outcomes such as `not-found`, `not-empty`, and
|
|
525
|
-
`not-removable
|
|
525
|
+
`not-removable`, plus runtime failures such as `read-failed`, are operational;
|
|
526
|
+
they do not indicate that a filesystem
|
|
526
527
|
boundary policy was violated.
|
|
527
528
|
|
|
528
|
-
Current `FsSafeErrorCode` values are `already-exists`, `denied-path`, `device-path`, `hardlink`, `helper-failed`, `helper-unavailable`, `invalid-path`, `insecure-permissions`, `not-empty`, `not-file`, `not-found`, `not-owned`, `not-removable`, `outside-workspace`, `path-alias`, `path-mismatch`, `permission-unverified`, `secret-exists`, `store-reentrant-update`, `symlink`, `timeout`, `too-large`, and `unsupported-platform`.
|
|
529
|
+
Current `FsSafeErrorCode` values are `already-exists`, `denied-path`, `device-path`, `hardlink`, `helper-failed`, `helper-unavailable`, `invalid-path`, `insecure-permissions`, `not-empty`, `not-file`, `not-found`, `not-owned`, `not-removable`, `outside-workspace`, `path-alias`, `path-mismatch`, `permission-unverified`, `read-failed`, `secret-exists`, `store-reentrant-update`, `symlink`, `timeout`, `too-large`, and `unsupported-platform`.
|
|
529
530
|
|
|
530
531
|
## Safety model
|
|
531
532
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-entry.d.ts","sourceRoot":"","sources":["../src/archive-entry.ts"],"names":[],"mappings":"AAKA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC,IAAI,
|
|
1
|
+
{"version":3,"file":"archive-entry.d.ts","sourceRoot":"","sources":["../src/archive-entry.ts"],"names":[],"mappings":"AAKA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC,IAAI,CA2DN;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc1F;AAED,wBAAgB,8BAA8B,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAgBlG;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAWT"}
|
package/dist/archive-entry.js
CHANGED
|
@@ -20,7 +20,15 @@ export function validateArchiveEntryPath(entryPath, params) {
|
|
|
20
20
|
if (entryPath.includes("\0")) {
|
|
21
21
|
throw new ArchiveSecurityError("entry-path", `archive entry contains a NUL byte: ${formatErrorDetail(entryPath)}`);
|
|
22
22
|
}
|
|
23
|
-
const
|
|
23
|
+
const slashNormalized = normalizeArchiveEntryPath(entryPath);
|
|
24
|
+
if (process.platform === "win32" &&
|
|
25
|
+
slashNormalized.split("/").some((segment) => segment.includes(":"))) {
|
|
26
|
+
throw new ArchiveSecurityError("entry-path", `archive entry uses a Windows alternate data stream path: ${formatErrorDetail(entryPath)}`);
|
|
27
|
+
}
|
|
28
|
+
const normalized = path.posix.normalize(slashNormalized);
|
|
29
|
+
if (normalized.split("/").some((segment) => Math.max(Buffer.byteLength(segment.normalize("NFC")), Buffer.byteLength(segment.normalize("NFD"))) > 255)) {
|
|
30
|
+
throw new ArchiveSecurityError("entry-path", `archive entry has an overlong path component: ${formatErrorDetail(entryPath)}`);
|
|
31
|
+
}
|
|
24
32
|
const escapeLabel = params?.escapeLabel ?? "destination";
|
|
25
33
|
if (normalized === ".." || normalized.startsWith("../")) {
|
|
26
34
|
throw new ArchiveSecurityError("entry-path", `archive entry escapes ${escapeLabel}: ${formatErrorDetail(entryPath)}`);
|
|
@@ -28,6 +36,9 @@ export function validateArchiveEntryPath(entryPath, params) {
|
|
|
28
36
|
if (path.posix.isAbsolute(normalized) || normalized.startsWith("//")) {
|
|
29
37
|
throw new ArchiveSecurityError("entry-path", `archive entry is absolute: ${formatErrorDetail(entryPath)}`);
|
|
30
38
|
}
|
|
39
|
+
if (slashNormalized.split("/").includes("..")) {
|
|
40
|
+
throw new ArchiveSecurityError("entry-path", `archive entry contains a parent segment: ${formatErrorDetail(entryPath)}`);
|
|
41
|
+
}
|
|
31
42
|
}
|
|
32
43
|
export function stripArchivePath(entryPath, stripComponents) {
|
|
33
44
|
const raw = normalizeArchiveEntryPath(entryPath);
|
package/dist/archive-errors.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export declare class ArchiveFormatError extends Error {
|
|
|
8
8
|
readonly code: ArchiveFormatErrorCode;
|
|
9
9
|
constructor(message: string, options?: ErrorOptions);
|
|
10
10
|
}
|
|
11
|
+
export declare function isArchiveFormatErrorMessage(message: string): boolean;
|
|
11
12
|
//# sourceMappingURL=archive-errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-errors.d.ts","sourceRoot":"","sources":["../src/archive-errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAE9D,MAAM,MAAM,wBAAwB,GAChC,2BAA2B,GAC3B,qBAAqB,GACrB,+BAA+B,GAC/B,gBAAgB,GAChB,YAAY,GACZ,YAAY,CAAC;AAEjB,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IAExC,YAAY,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAIlF;CACF;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IAEtC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAIlD;CACF"}
|
|
1
|
+
{"version":3,"file":"archive-errors.d.ts","sourceRoot":"","sources":["../src/archive-errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAE9D,MAAM,MAAM,wBAAwB,GAChC,2BAA2B,GAC3B,qBAAqB,GACrB,+BAA+B,GAC/B,gBAAgB,GAChB,YAAY,GACZ,YAAY,CAAC;AAEjB,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IAExC,YAAY,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAIlF;CACF;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IAEtC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAIlD;CACF;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAKpE"}
|
package/dist/archive-errors.js
CHANGED
|
@@ -14,3 +14,7 @@ export class ArchiveFormatError extends Error {
|
|
|
14
14
|
this.code = "archive-header-invalid";
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
export function isArchiveFormatErrorMessage(message) {
|
|
18
|
+
return (message.includes("archive-header-invalid") ||
|
|
19
|
+
message.includes("archive entry size did not match its manifest"));
|
|
20
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAOL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAuBjD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;CAClD,GAAG,OAAO,CAAC,IAAI,CAAC,CAwHhB"}
|
package/dist/archive-native.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { constants as fsConstants } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
|
|
3
|
+
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
4
4
|
import { formatErrorDetail } from "./error-detail.js";
|
|
5
5
|
import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
6
6
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
@@ -20,9 +20,12 @@ function throwMappedNativeError(error) {
|
|
|
20
20
|
if (error.message.includes(code))
|
|
21
21
|
throw new ArchiveLimitError(code);
|
|
22
22
|
}
|
|
23
|
-
if (error.message
|
|
23
|
+
if (isArchiveFormatErrorMessage(error.message)) {
|
|
24
24
|
throw new ArchiveFormatError(error.message, { cause: error });
|
|
25
25
|
}
|
|
26
|
+
if (error.code === "InvalidArg") {
|
|
27
|
+
throw new ArchiveFormatError(`invalid archive: ${error.message}`, { cause: error });
|
|
28
|
+
}
|
|
26
29
|
}
|
|
27
30
|
throw error;
|
|
28
31
|
}
|
|
@@ -98,7 +101,7 @@ export async function extractNativeArchive(params) {
|
|
|
98
101
|
(typeof fsConstants.O_DIRECTORY === "number" ? fsConstants.O_DIRECTORY : 0));
|
|
99
102
|
try {
|
|
100
103
|
params.deadline.check();
|
|
101
|
-
await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan, limits.maxMetaEntryBytes, params.deadline.signal);
|
|
104
|
+
await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan, limits.maxMetaEntryBytes, params.deadline.signal).catch(throwMappedNativeError);
|
|
102
105
|
}
|
|
103
106
|
finally {
|
|
104
107
|
await directory.close().catch(() => undefined);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqMzE,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAmFjB"}
|
package/dist/archive-read.js
CHANGED
|
@@ -2,16 +2,16 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { Readable } from "node:stream";
|
|
4
4
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
5
|
-
import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
|
|
5
|
+
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
6
6
|
import { formatErrorDetail } from "./error-detail.js";
|
|
7
7
|
import { normalizeArchiveEntryPath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
8
8
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
9
9
|
import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
|
|
10
10
|
import { readTarEntryInfo } from "./archive-tar.js";
|
|
11
11
|
import { preflightTarMetadata } from "./archive-tar-meta.js";
|
|
12
|
-
import { importOptionalTar } from "./archive-tar-runtime.js";
|
|
12
|
+
import { importOptionalTar, normalizeTarParserError } from "./archive-tar-runtime.js";
|
|
13
13
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
14
|
-
import { createZipIntegrityTransform } from "./archive-zip-integrity.js";
|
|
14
|
+
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
15
15
|
import { FsSafeError } from "./errors.js";
|
|
16
16
|
import { sameFileIdentity } from "./file-identity.js";
|
|
17
17
|
import { getNativeBinding } from "./native.js";
|
|
@@ -107,7 +107,9 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
|
|
|
107
107
|
const stream = typeof entry.nodeStream === "function"
|
|
108
108
|
? entry.nodeStream()
|
|
109
109
|
: Readable.from(await entry.async("nodebuffer"));
|
|
110
|
-
|
|
110
|
+
const integrity = createZipIntegrityTransform(entry);
|
|
111
|
+
stream.once("error", (error) => integrity.destroy(normalizeZipIntegrityError(error)));
|
|
112
|
+
return await readStreamBounded(stream.pipe(integrity), maxBytes);
|
|
111
113
|
}
|
|
112
114
|
async function readTarEntry(archivePath, entryPath, maxBytes) {
|
|
113
115
|
const tar = await importOptionalTar();
|
|
@@ -118,37 +120,42 @@ async function readTarEntry(archivePath, entryPath, maxBytes) {
|
|
|
118
120
|
let matched;
|
|
119
121
|
let entryError;
|
|
120
122
|
const seenPaths = new Set();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
123
|
+
try {
|
|
124
|
+
await tar.t({
|
|
125
|
+
file: archivePath,
|
|
126
|
+
strict: true,
|
|
127
|
+
maxMetaEntrySize: DEFAULT_MAX_META_ENTRY_BYTES,
|
|
128
|
+
onReadEntry(entry) {
|
|
129
|
+
const info = readTarEntryInfo(entry);
|
|
130
|
+
validateArchiveEntryPath(info.path, { escapeLabel: "archive root" });
|
|
131
|
+
const normalized = normalizeArchiveEntryPath(info.path).replace(/^\.\//, "");
|
|
132
|
+
if (seenPaths.has(normalized)) {
|
|
133
|
+
entryError ??= new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
134
|
+
entry.resume();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
seenPaths.add(normalized);
|
|
138
|
+
if (normalized !== entryPath) {
|
|
139
|
+
entry.resume();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (info.type !== "File" && info.type !== "OldFile" && info.type !== "ContiguousFile") {
|
|
143
|
+
entryError ??= new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
144
|
+
entry.resume();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (info.size > maxBytes) {
|
|
148
|
+
entryError ??= new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
149
|
+
entry.resume();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
matched = readStreamBounded(entry, maxBytes);
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
throw normalizeTarParserError(error);
|
|
158
|
+
}
|
|
152
159
|
if (entryError) {
|
|
153
160
|
throw entryError;
|
|
154
161
|
}
|
|
@@ -199,7 +206,7 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
199
206
|
error.message.includes(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT)) {
|
|
200
207
|
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
201
208
|
}
|
|
202
|
-
if (error instanceof Error && error.message
|
|
209
|
+
if (error instanceof Error && isArchiveFormatErrorMessage(error.message)) {
|
|
203
210
|
throw new ArchiveFormatError(error.message, { cause: error });
|
|
204
211
|
}
|
|
205
212
|
throw error;
|
|
@@ -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":"AAqJA,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
|
package/dist/archive-tar-meta.js
CHANGED
|
@@ -46,6 +46,14 @@ class TarMetadataMeter extends Transform {
|
|
|
46
46
|
this.state = { kind: "header" };
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
+
const nameEnd = this.block.subarray(0, 100).indexOf(0);
|
|
50
|
+
const name = this.block.subarray(0, nameEnd < 0 ? 100 : nameEnd);
|
|
51
|
+
if (name.length === 0) {
|
|
52
|
+
throw this.invalid("entry path is empty");
|
|
53
|
+
}
|
|
54
|
+
if (this.block[156] !== 0x35 && name.at(-1) === 0x2f) {
|
|
55
|
+
throw this.invalid("non-directory entry path ends with a separator");
|
|
56
|
+
}
|
|
49
57
|
const size = this.parseSize();
|
|
50
58
|
const type = this.block[156];
|
|
51
59
|
if ([0x78, 0x67, 0x4c, 0x4b, 0x58].includes(type ?? -1) && size > this.maxMetaEntryBytes) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar-runtime.d.ts","sourceRoot":"","sources":["../src/archive-tar-runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-tar-runtime.d.ts","sourceRoot":"","sources":["../src/archive-tar-runtime.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,GAAG;IAC9C,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC1B,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IAChF,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IACzE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC;IACnE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC;IAChE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,SAAS,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,KAAK,OAAO,EAAE;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAC;IAC/E,CAAC,CAAC,OAAO,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,aAAa,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC;QACd,aAAa,EAAE,KAAK,CAAC;QACrB,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,MAAM,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QACrE,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;KAClD,GAAG,SAAS,CAAC;IACd,CAAC,CAAC,OAAO,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG;YAAE,MAAM,IAAI,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;KACvE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAEF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAS5D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS/D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ArchiveFormatError } from "./archive-errors.js";
|
|
1
2
|
export async function importOptionalTar() {
|
|
2
3
|
try {
|
|
3
4
|
return await import("tar");
|
|
@@ -6,3 +7,13 @@ export async function importOptionalTar() {
|
|
|
6
7
|
throw new Error('Optional archive dependency "tar" is not installed. Install it to use TAR archive helpers from @openclaw/fs-safe/archive.', { cause });
|
|
7
8
|
}
|
|
8
9
|
}
|
|
10
|
+
export function normalizeTarParserError(error) {
|
|
11
|
+
const code = error?.code;
|
|
12
|
+
if (typeof code !== "string" || !code.startsWith("TAR_")) {
|
|
13
|
+
return error;
|
|
14
|
+
}
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
return new ArchiveFormatError(`invalid TAR archive: ${message}`, {
|
|
17
|
+
cause: error instanceof Error ? error : undefined,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAI7B,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAWvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAyB7D;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAI7B,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAWvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAyB7D;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAgDnC"}
|
package/dist/archive-tar.js
CHANGED
|
@@ -67,6 +67,7 @@ export function createTarEntryPreflightChecker(params) {
|
|
|
67
67
|
if (BLOCKED_TAR_ENTRY_TYPES.has(entry.type)) {
|
|
68
68
|
throw new ArchiveSecurityError("entry-link", `tar entry is a link: ${formatErrorDetail(entry.path)}`);
|
|
69
69
|
}
|
|
70
|
+
budget.startEntry();
|
|
70
71
|
budget.addEntrySize(entry.size);
|
|
71
72
|
return true;
|
|
72
73
|
};
|
|
@@ -6,10 +6,15 @@ export type ZipEntry = {
|
|
|
6
6
|
_data?: {
|
|
7
7
|
crc32?: number;
|
|
8
8
|
uncompressedSize?: number;
|
|
9
|
-
}
|
|
9
|
+
} | PromiseLike<unknown>;
|
|
10
10
|
nodeStream?: () => NodeJS.ReadableStream;
|
|
11
11
|
async: (type: "nodebuffer") => Promise<Buffer>;
|
|
12
12
|
};
|
|
13
|
+
export declare function zipEntryIntegrityMetadata(entry: ZipEntry): {
|
|
14
|
+
crc32?: number;
|
|
15
|
+
uncompressedSize?: number;
|
|
16
|
+
} | undefined;
|
|
17
|
+
export declare function hasDeferredEmptyZipData(entry: ZipEntry): boolean;
|
|
13
18
|
export declare function isZipSymlinkEntry(entry: ZipEntry): boolean;
|
|
14
19
|
export declare function zipEntryMode(entry: ZipEntry, policy: ArchiveEntryModePolicy | undefined): number;
|
|
15
20
|
export declare function zipEntryDeclaredSize(entry: ZipEntry): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-entry.d.ts","sourceRoot":"","sources":["../src/archive-zip-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-zip-entry.d.ts","sourceRoot":"","sources":["../src/archive-zip-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7E,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,cAAc,CAAC;IACzC,KAAK,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChD,CAAC;AAEF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,QAAQ,GACd;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAI3D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAGhE;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAK1D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,MAAM,CAMR;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE5D"}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { resolveArchiveEntryMode, } from "./archive-policy.js";
|
|
2
|
+
export function zipEntryIntegrityMetadata(entry) {
|
|
3
|
+
const data = entry._data;
|
|
4
|
+
if (!data || "then" in data)
|
|
5
|
+
return undefined;
|
|
6
|
+
return data;
|
|
7
|
+
}
|
|
8
|
+
export function hasDeferredEmptyZipData(entry) {
|
|
9
|
+
const data = entry._data;
|
|
10
|
+
return Boolean(data && "then" in data);
|
|
11
|
+
}
|
|
2
12
|
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
3
13
|
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
4
14
|
export function isZipSymlinkEntry(entry) {
|
|
@@ -13,5 +23,5 @@ export function zipEntryMode(entry, policy) {
|
|
|
13
23
|
});
|
|
14
24
|
}
|
|
15
25
|
export function zipEntryDeclaredSize(entry) {
|
|
16
|
-
return Math.max(0, Math.floor(entry
|
|
26
|
+
return Math.max(0, Math.floor(zipEntryIntegrityMetadata(entry)?.uncompressedSize ?? 0));
|
|
17
27
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
|
-
import type
|
|
2
|
+
import { type ZipEntry } from "./archive-zip-entry.js";
|
|
3
|
+
export declare function normalizeZipIntegrityError(error: unknown): Error;
|
|
3
4
|
export declare function createZipIntegrityTransform(entry: ZipEntry): Transform;
|
|
4
5
|
//# sourceMappingURL=archive-zip-integrity.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-integrity.d.ts","sourceRoot":"","sources":["../src/archive-zip-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"archive-zip-integrity.d.ts","sourceRoot":"","sources":["../src/archive-zip-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;AAkBhC,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAQhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,CAgCtE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
2
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
3
|
+
import { hasDeferredEmptyZipData, zipEntryIntegrityMetadata, } from "./archive-zip-entry.js";
|
|
3
4
|
const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
4
5
|
let value = index;
|
|
5
6
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
@@ -14,9 +15,18 @@ function updateCrc32(previous, buffer) {
|
|
|
14
15
|
}
|
|
15
16
|
return (crc ^ -1) >>> 0;
|
|
16
17
|
}
|
|
18
|
+
export function normalizeZipIntegrityError(error) {
|
|
19
|
+
if (error instanceof Error &&
|
|
20
|
+
error.message.includes("uncompressed data size mismatch")) {
|
|
21
|
+
return new ArchiveFormatError(`invalid ZIP entry data: ${error.message}`, { cause: error });
|
|
22
|
+
}
|
|
23
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
24
|
+
}
|
|
17
25
|
export function createZipIntegrityTransform(entry) {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
26
|
+
const metadata = zipEntryIntegrityMetadata(entry);
|
|
27
|
+
const deferredEmpty = hasDeferredEmptyZipData(entry);
|
|
28
|
+
const expectedCrc32 = deferredEmpty ? 0 : metadata?.crc32;
|
|
29
|
+
const expectedSize = deferredEmpty ? 0 : metadata?.uncompressedSize;
|
|
20
30
|
if (typeof expectedCrc32 !== "number" ||
|
|
21
31
|
!Number.isInteger(expectedCrc32) ||
|
|
22
32
|
typeof expectedSize !== "number" ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAiLF,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAU5F;AAED,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAiLF,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAU5F;AAED,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CA0B9B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, resolveExtractLimits, } from "./archive-limits.js";
|
|
2
|
-
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
2
|
+
import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
|
|
3
3
|
const ZIP_EOCD_SIGNATURE = 0x06054b50;
|
|
4
4
|
const ZIP64_EOCD_SIGNATURE = 0x06064b50;
|
|
5
5
|
const ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50;
|
|
@@ -149,7 +149,13 @@ export async function loadZipArchiveWithPreflight(buffer, limits) {
|
|
|
149
149
|
assertArchiveEntryCountWithinLimit(entryCount, resolvedLimits);
|
|
150
150
|
}
|
|
151
151
|
const JSZip = await importOptionalJsZip();
|
|
152
|
-
|
|
152
|
+
let archive;
|
|
153
|
+
try {
|
|
154
|
+
archive = await JSZip.loadAsync(buffer);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
throw new ArchiveFormatError(`invalid ZIP archive: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
|
|
158
|
+
}
|
|
153
159
|
if (entryCount !== null && Object.keys(archive.files).length !== entryCount) {
|
|
154
160
|
throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
|
|
155
161
|
}
|
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":"AA6DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAoNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkKjF"}
|
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, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
6
|
+
import { createArchiveOutputPathTracker, normalizeArchiveEntryPath, 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";
|
|
@@ -11,14 +11,14 @@ import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepar
|
|
|
11
11
|
import { createTarEntryPreflightChecker, readTarEntryInfo, } from "./archive-tar.js";
|
|
12
12
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
13
13
|
import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
|
|
14
|
-
import { createZipIntegrityTransform } from "./archive-zip-integrity.js";
|
|
14
|
+
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
15
15
|
import { FsSafeError } from "./errors.js";
|
|
16
16
|
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
17
17
|
import { extractNativeArchive } from "./archive-native.js";
|
|
18
18
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
19
19
|
import { getNativeBinding } from "./native.js";
|
|
20
20
|
import { resolveArchiveEntryMode, shouldExtractArchiveEntry, } from "./archive-policy.js";
|
|
21
|
-
import { importOptionalTar } from "./archive-tar-runtime.js";
|
|
21
|
+
import { importOptionalTar, normalizeTarParserError } from "./archive-tar-runtime.js";
|
|
22
22
|
import { preflightTarMetadata } from "./archive-tar-meta.js";
|
|
23
23
|
import { writeSiblingTempFile } from "./sibling-temp.js";
|
|
24
24
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
@@ -85,7 +85,7 @@ async function writeZipFileEntry(params) {
|
|
|
85
85
|
await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), createZipIntegrityTransform(params.entry), writable, { signal: params.deadline.signal });
|
|
86
86
|
}
|
|
87
87
|
catch (err) {
|
|
88
|
-
throw createPipelineTimeoutError(err, params.deadline);
|
|
88
|
+
throw normalizeZipIntegrityError(createPipelineTimeoutError(err, params.deadline));
|
|
89
89
|
}
|
|
90
90
|
params.deadline.check();
|
|
91
91
|
if (!handleClosedByStream) {
|
|
@@ -266,6 +266,7 @@ export async function extractArchive(params) {
|
|
|
266
266
|
const info = readTarEntryInfo(entry);
|
|
267
267
|
const accepted = checkTarEntrySafety(info);
|
|
268
268
|
if (accepted) {
|
|
269
|
+
entry.path = normalizeArchiveEntryPath(info.path);
|
|
269
270
|
const relPath = stripArchivePath(info.path, Math.max(0, Math.floor(params.stripComponents ?? 0)));
|
|
270
271
|
if (relPath) {
|
|
271
272
|
acceptedEntries.push({
|
|
@@ -309,7 +310,7 @@ export async function extractArchive(params) {
|
|
|
309
310
|
});
|
|
310
311
|
}
|
|
311
312
|
catch (error) {
|
|
312
|
-
throw createPipelineTimeoutError(error, deadline);
|
|
313
|
+
throw normalizeTarParserError(createPipelineTimeoutError(error, deadline));
|
|
313
314
|
}
|
|
314
315
|
for (const accepted of acceptedEntries) {
|
|
315
316
|
const outputPath = resolveArchiveOutputPath({
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "helper-failed" | "helper-unavailable" | "invalid-path" | "insecure-permissions" | "not-empty" | "not-file" | "not-found" | "not-owned" | "not-removable" | "outside-workspace" | "path-alias" | "path-mismatch" | "permission-unverified" | "secret-exists" | "store-reentrant-update" | "symlink" | "timeout" | "too-large" | "unsupported-platform";
|
|
1
|
+
export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "helper-failed" | "helper-unavailable" | "invalid-path" | "insecure-permissions" | "not-empty" | "not-file" | "not-found" | "not-owned" | "not-removable" | "outside-workspace" | "path-alias" | "path-mismatch" | "permission-unverified" | "read-failed" | "secret-exists" | "store-reentrant-update" | "symlink" | "timeout" | "too-large" | "unsupported-platform";
|
|
2
2
|
export type FsSafeErrorCategory = "policy" | "operational";
|
|
3
3
|
export type FsSafeErrorDetails = Readonly<Record<string, unknown>>;
|
|
4
4
|
export declare function categorizeFsSafeError(code: FsSafeErrorCode): FsSafeErrorCategory;
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,gBAAgB,GAChB,aAAa,GACb,aAAa,GACb,UAAU,GACV,eAAe,GACf,oBAAoB,GACpB,cAAc,GACd,sBAAsB,GACtB,WAAW,GACX,UAAU,GACV,WAAW,GACX,WAAW,GACX,eAAe,GACf,mBAAmB,GACnB,YAAY,GACZ,eAAe,GACf,uBAAuB,GACvB,eAAe,GACf,wBAAwB,GACxB,SAAS,GACT,SAAS,GACT,WAAW,GACX,sBAAsB,CAAC;AAE3B,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,aAAa,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,gBAAgB,GAChB,aAAa,GACb,aAAa,GACb,UAAU,GACV,eAAe,GACf,oBAAoB,GACpB,cAAc,GACd,sBAAsB,GACtB,WAAW,GACX,UAAU,GACV,WAAW,GACX,WAAW,GACX,eAAe,GACf,mBAAmB,GACnB,YAAY,GACZ,eAAe,GACf,uBAAuB,GACvB,aAAa,GACb,eAAe,GACf,wBAAwB,GACxB,SAAS,GACT,SAAS,GACT,WAAW,GACX,sBAAsB,CAAC;AAE3B,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,aAAa,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAcnE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,mBAAmB,CAEhF;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAEtC,YACE,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAAO,EAOhE;CACF"}
|
package/dist/errors.js
CHANGED
package/dist/file-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAOnE,OAAO,EAAmB,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
|