@openclaw/fs-safe 0.5.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +16 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts +2 -0
- package/dist/archive-crc32.d.ts.map +1 -0
- package/dist/archive-crc32.js +14 -0
- package/dist/archive-kind.js +2 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +24 -5
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +53 -6
- package/dist/archive-tar-pax.d.ts +8 -0
- package/dist/archive-tar-pax.d.ts.map +1 -0
- package/dist/archive-tar-pax.js +100 -0
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-zip-admission.d.ts +7 -0
- package/dist/archive-zip-admission.d.ts.map +1 -0
- package/dist/archive-zip-admission.js +60 -0
- package/dist/archive-zip-count.d.ts +2 -0
- package/dist/archive-zip-count.d.ts.map +1 -0
- package/dist/archive-zip-count.js +140 -0
- package/dist/archive-zip-directory.d.ts +8 -0
- package/dist/archive-zip-directory.d.ts.map +1 -0
- package/dist/archive-zip-directory.js +224 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +1 -14
- package/dist/archive-zip-names.d.ts +12 -0
- package/dist/archive-zip-names.d.ts.map +1 -0
- package/dist/archive-zip-names.js +99 -0
- package/dist/archive-zip-preflight.d.ts +1 -1
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -145
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +32 -23
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +19 -7
- package/dist/native-binding.d.ts +3 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +4 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +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/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -1
- package/dist/permission-exec.d.ts +19 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +57 -11
- package/dist/permissions-public.d.ts +1 -1
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-windows.d.ts +3 -0
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +16 -5
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +19 -29
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +78 -72
- package/dist/root-paths.d.ts +11 -14
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +36 -27
- package/dist/root-write-verification.d.ts +11 -0
- package/dist/root-write-verification.d.ts.map +1 -0
- package/dist/root-write-verification.js +91 -0
- package/dist/secret-file.d.ts +1 -6
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +43 -117
- package/dist/secret-read-async.d.ts +1 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +45 -67
- package/dist/secret-read-policy.d.ts +13 -0
- package/dist/secret-read-policy.d.ts.map +1 -0
- package/dist/secret-read-policy.js +28 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +25 -16
- package/dist/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/windows-owner.d.ts +3 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +10 -2
- package/docs/advanced.md +11 -1
- package/docs/archive.md +62 -15
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +17 -4
- package/docs/errors.md +16 -1
- package/docs/index.md +3 -1
- package/docs/install.md +15 -7
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +15 -5
- package/docs/native.md +40 -11
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/root.md +1 -1
- package/docs/secret-file.md +8 -0
- package/docs/secure-file.md +17 -1
- package/docs/security-model.md +15 -1
- package/docs/sidecar-lock.md +11 -1
- package/docs/staged-file.md +178 -0
- package/docs/testing.md +1 -1
- package/docs/writing.md +19 -0
- package/package.json +15 -8
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-08-29
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- Install only the matching native platform package; deployments using native mode `require` or native-only features must keep optional dependencies enabled. See the [0.6 migration guide](https://fs-safe.io/migrating-to-0.6.html).
|
|
8
|
+
- Strengthen root, secure, secret, and pathname-hash identity checks with lossless bigint comparisons and fail-closed handling of unknown Windows identities.
|
|
9
|
+
- Add native-required Linux/macOS `stageFileInDirectory()` with retained-directory cleanup, private staging, identity-checked publication, and async disposal.
|
|
10
|
+
- Validate physical ZIP metadata and names before decoder normalization or collapse, align bounded TAR/PAX and stripped-path handling, and reject ZIP symlinks disguised as directories.
|
|
11
|
+
|
|
12
|
+
### Security and Correctness
|
|
13
|
+
|
|
14
|
+
- Validate physical ZIP local/central and Unicode name metadata before decoding, rejecting traversal, hidden duplicates, and conflicting interpretations consistently in extraction and bounded reads.
|
|
15
|
+
|
|
16
|
+
- Verify pathname SHA-256 hashing with lossless pre-open, descriptor, and current-path identities; fail closed on unknown Windows identities after one bounded retry without reopening the file.
|
|
17
|
+
- Preserve underlying command diagnostics (command, timing, timeout flag, exit code/signal, and sanitized stderr) and cause on Windows ACL `permission-unverified` errors without changing verification semantics.
|
|
18
|
+
- Verify secure and secret read identities with lossless bigint stats, reject replaced paths and retargeted aliases, and fail closed on unknown Windows identities after one bounded re-inspection; preserve the secure reader's numeric `Stats` receipt and permission options without letting them bypass identity checks.
|
|
19
|
+
- Apply the same exact identity checks to guarded root reads, rejecting parent-directory replacements even when distinct Windows file IDs round to the same number; retain numeric read receipts and keep Windows write reopens anchored to the writer's exact retained identity.
|
|
20
|
+
- Verify `Root.write()` and `create()` through retained descriptors and exact bigint identities for restrictive final modes, retaining guarded Windows path opens when pathname identity is unavailable; preserve explicit mode zero in `copyIn()`.
|
|
21
|
+
- Preserve filesystem failures from `ensureDirectoryWithinRoot()` and `pathScope().ensureDir()` in an optional operational `FsSafeError` diagnostic with the original cause and bounded, escaped display text, instead of misreporting them as containment violations; keep nonthrowing string results and directory safety checks.
|
|
22
|
+
- Accept bounded local PAX paths, sizes, and descriptive metadata, including inert binary macOS provenance xattrs, consistently in JavaScript and native TAR extraction/reads; reject ambiguous records, extension chains, and sparse semantics while retaining byte/count limits and guarded staging. Return TAR read traversal failures through the public promise instead of escaping the parser callback.
|
|
23
|
+
- Add native-required Linux/macOS `stageFileInDirectory()` under `advanced` with retained-directory abort cleanup, private `0600` staging until publication is identity-checked, exact identity checks, explicit publication/cleanup receipts, and async disposal; share that ownership with POSIX native streaming writes so parent moves no longer strand their unpublished temps.
|
|
24
|
+
- Share native writer admission and direct-child cleanup mechanisms; preserve combined POSIX coordinator operation/disposal failures with `SuppressedError` while keeping staged preparation/cleanup receipts and Windows close policy unchanged.
|
|
25
|
+
- Fail closed when synchronous sidecar compromise checks hit I/O errors and invoke `onCompromised` once instead of throwing from the interval. Thanks @SebTardif.
|
|
26
|
+
- Reject ZIP symlink-mode entries with a trailing slash or DOS directory bit before creating output, aligning JavaScript extraction with native link policy while preserving explicit filtering. Thanks @Yigtwxx.
|
|
27
|
+
- Make the validated stripped path authoritative during JavaScript TAR extraction, fixing `ENOENT` and native path disagreement for dot or empty components, including local PAX paths, while preserving pre-strip filter inputs. Thanks @Yigtwxx.
|
|
28
|
+
- Apply process-wide retry, timeout, and stale-policy defaults to synchronous file locks while preserving per-call overrides and caller-approved guarded recovery. Thanks @Yigtwxx.
|
|
29
|
+
|
|
30
|
+
### Docs and Tooling
|
|
31
|
+
|
|
32
|
+
- Publish native bindings as platform-filtered optional packages and load only the matching package, avoiding installation of binaries for six unrelated targets; verify root-only npm/pnpm resolution and document omitted-optionals limits and native-only recovery guidance. Thanks @RomneyDa.
|
|
33
|
+
- Raise the Crabbox AWS root volume to 400 GiB to meet the runner image's snapshot minimum and avoid allocation failures before checks run.
|
|
34
|
+
- Restore six missing documentation navigation entries, remove the dangling page, correct the lock-config link, and reject missing, nonexistent, or duplicate registrations before replacing site output. Thanks @Yigtwxx.
|
|
35
|
+
- Fix the standalone native smoke script to use the numeric descriptor returned by `openBeneath()` for identity checks and cleanup.
|
|
36
|
+
- Accept npm 11 array and npm 12 package-name-keyed pack results while validating the intended package and its file metadata.
|
|
37
|
+
|
|
38
|
+
### Dependencies and maintenance
|
|
39
|
+
|
|
40
|
+
- Refresh the Node and Rust dependency graphs, align development and CI on pnpm 11.24.0, and update the CodeQL, npm, and cargo-zigbuild pins while preserving the two-day dependency cooldown.
|
|
41
|
+
|
|
3
42
|
## 0.5.6 - 2026-08-14
|
|
4
43
|
|
|
5
44
|
### Security and Correctness
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Full docs and reference at **[fs-safe.io](https://fs-safe.io)**.
|
|
|
26
26
|
|
|
27
27
|
## Contents
|
|
28
28
|
|
|
29
|
-
[Why this exists](#why-this-exists) · [Not a sandbox](#not-a-sandbox) · [Install](#install) · [0.
|
|
29
|
+
[Why this exists](#why-this-exists) · [Not a sandbox](#not-a-sandbox) · [Install](#install) · [0.6 migration](docs/migrating-to-0.6.md) · [Python migration](#migrating-from-the-python-helper) · [Quick start](#quick-start) · [Reading](#reading) · [Subpaths](#subpaths) · [Failure semantics](#failure-semantics-in-the-name) · [Directory durability](#directory-durability) · [Atomic writes](#atomic-writes) · [External outputs](#external-outputs) · [Stores](#stores) · [Secure absolute reads](#secure-absolute-file-reads) · [Walking](#directory-walking) · [Archive extraction](#archive-extraction) · [Path scopes](#advanced-path-scopes) · [Errors](#errors) · [Safety model](#safety-model) · [Limitations](#limitations)
|
|
30
30
|
|
|
31
31
|
## Why this exists
|
|
32
32
|
|
|
@@ -57,10 +57,10 @@ This is a **library-level guardrail**, not OS-level isolation. It does not repla
|
|
|
57
57
|
pnpm add @openclaw/fs-safe
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Node 22 or newer. Core root/path/json/temp helpers avoid framework dependencies.
|
|
60
|
+
Node 22 or newer. Core root/path/json/temp helpers avoid framework dependencies. With all optional dependencies omitted, public subpaths remain safe to import and non-archive fallback-capable operations work in `auto` or `off`. Native-only features remain unavailable, and operations needing the binding in `require` mode fail with `helper-unavailable`. JavaScript ZIP/TAR fallback also needs the optional `jszip`/`tar` codecs. See the [0.6 migration guide](docs/migrating-to-0.6.md).
|
|
61
61
|
|
|
62
|
-
The package
|
|
63
|
-
|
|
62
|
+
The package installs one prebuilt native binding for the current supported target. It
|
|
63
|
+
supplies fd-relative and atomic no-replace primitives that Node does not expose
|
|
64
64
|
directly. Configure the lazy loader before first use when you need a strict
|
|
65
65
|
environment policy:
|
|
66
66
|
|
|
@@ -80,11 +80,11 @@ replace a writable parent between its identity check and Node's pathname
|
|
|
80
80
|
mutation can redirect that mutation outside the root before the post-check
|
|
81
81
|
reports the escape. Use `require` when hostile concurrent mutation is in scope.
|
|
82
82
|
|
|
83
|
-
Equivalent env var: `FS_SAFE_NATIVE_MODE=auto|off|require`.
|
|
84
|
-
ship
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
Equivalent env var: `FS_SAFE_NATIVE_MODE=auto|off|require`. The seven bindings
|
|
84
|
+
ship as exact-version optional packages filtered by OS, CPU, and Linux libc, so
|
|
85
|
+
a normal install receives only its matching binary. There are no postinstall
|
|
86
|
+
steps, runtime downloads, or consumer Rust builds. On a platform without a
|
|
87
|
+
published binding, or when optional dependencies are omitted, `auto` silently retains lexical and canonical root
|
|
88
88
|
checks, no-follow opens, guarded temp+rename writes, and post-write identity
|
|
89
89
|
verification. See the [native
|
|
90
90
|
helper policy](docs/native-helper.md) for the exact boundary and deployment
|
|
@@ -97,7 +97,7 @@ and guarded JavaScript results are best-effort. See the [security model](docs/se
|
|
|
97
97
|
|
|
98
98
|
## Migrating from the Python helper
|
|
99
99
|
|
|
100
|
-
Version 0.5 replaces the persistent Python worker with
|
|
100
|
+
Version 0.5 replaces the persistent Python worker with prebuilt native
|
|
101
101
|
bindings. The modes map directly: `configureFsSafePython({ mode: "auto" })`
|
|
102
102
|
becomes `configureFsSafeNative({ mode: "auto" })`, and likewise for `off` and
|
|
103
103
|
`require`. Replace `FS_SAFE_PYTHON_MODE` with `FS_SAFE_NATIVE_MODE`; remove
|
|
@@ -284,6 +284,12 @@ publication policy, creation callback, and platform contract.
|
|
|
284
284
|
|
|
285
285
|
## Atomic writes
|
|
286
286
|
|
|
287
|
+
For preparation that must survive a parent rename until abort cleanup, use
|
|
288
|
+
[`stageFileInDirectory()`](docs/staged-file.md) from `advanced`. It retains the
|
|
289
|
+
original directory on Linux/macOS and requires native support for this operation.
|
|
290
|
+
It offers atomic replace/no-replace publication, not expected-inode replacement
|
|
291
|
+
or a crash-durability promise; application checks and coordination remain yours.
|
|
292
|
+
|
|
287
293
|
`replaceFileAtomic()` writes a sibling temp file, applies its exact mode through the still-open descriptor, optionally fsyncs it, and renames it over the destination. It never follows the published destination path to set file permissions. Mode preservation, pinned-destination hardlink rejection, rename retry / copy fallback on `EPERM`, bounded original-content restoration after a torn fallback, parent-directory fsync, and a `beforeRename` hook for backup or observer flows are all opt-in. `movePathWithCopyFallback()` stages cross-device moves before commit and removes only the copied source entries, so concurrent source additions or replacements are preserved.
|
|
288
294
|
|
|
289
295
|
```ts
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createAsyncLock } from "./async-lock.js";
|
|
2
|
+
export { stageFileInDirectory, type StagedFile, type StagedFileReceipt, type PublishedFileReceipt, type StagedFilePublication, type StagedFileCleanupReceipt, type StagedFileFailureDetails, } from "./staged-file.js";
|
|
2
3
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
3
4
|
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, type UnsafeDeviceReadPathMatch, type UnsafeDeviceReadPathOptions, type UnsafeDeviceReadPathReason, } from "./device-path.js";
|
|
4
5
|
export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, type AbsolutePathSymlinkPolicy, type EnsureAbsoluteDirectoryOptions, type EnsureAbsoluteDirectoryResult, type ResolvedAbsolutePath, type ResolvedWritableAbsolutePath, } from "./absolute-path.js";
|
|
@@ -21,5 +22,5 @@ export { resolveHomeRelativePath } from "./home-dir.js";
|
|
|
21
22
|
export { appendRegularFile, appendRegularFileSync, readRegularFile, readRegularFileSync, resolveRegularFileAppendFlags, statRegularFile, statRegularFileSync, type AppendRegularFileOptions, type RegularFileStatResult, } from "./regular-file.js";
|
|
22
23
|
export { buildRandomTempFilePath, sanitizeTempFileName, type TempFile, tempFile, withTempFile, } from "./temp-target.js";
|
|
23
24
|
export { writeSiblingTempFile, writeViaSiblingTempPath, type WriteSiblingTempFileOptions, type WriteSiblingTempFileResult, } from "./sibling-temp.js";
|
|
24
|
-
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, type IcaclsResetCommandOptions, type PermissionExec, type WindowsAclEntry, type WindowsAclSummary, } from "./permissions.js";
|
|
25
|
+
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, type IcaclsResetCommandOptions, type PermissionCommandFailure, type PermissionExec, type WindowsAclEntry, type WindowsAclSummary, } from "./permissions.js";
|
|
25
26
|
//# sourceMappingURL=advanced.d.ts.map
|
package/dist/advanced.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
package/dist/advanced.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// public subpaths; prefer root/json/store/temp/archive unless you are building a
|
|
3
3
|
// higher-level primitive.
|
|
4
4
|
export { createAsyncLock } from "./async-lock.js";
|
|
5
|
+
export { stageFileInDirectory, } from "./staged-file.js";
|
|
5
6
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
6
7
|
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, } from "./device-path.js";
|
|
7
8
|
export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, } from "./absolute-path.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-crc32.d.ts","sourceRoot":"","sources":["../src/archive-crc32.ts"],"names":[],"mappings":"AAQA,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMpE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
2
|
+
let value = index;
|
|
3
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
4
|
+
value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
5
|
+
}
|
|
6
|
+
return value >>> 0;
|
|
7
|
+
});
|
|
8
|
+
export function updateCrc32(previous, buffer) {
|
|
9
|
+
let crc = previous ^ -1;
|
|
10
|
+
for (const byte of buffer) {
|
|
11
|
+
crc = (crc >>> 8) ^ (CRC32_TABLE[(crc ^ byte) & 0xff] ?? 0);
|
|
12
|
+
}
|
|
13
|
+
return (crc ^ -1) >>> 0;
|
|
14
|
+
}
|
package/dist/archive-kind.js
CHANGED
|
@@ -10,8 +10,8 @@ const NATIVE_TAR_SUFFIXES = [
|
|
|
10
10
|
];
|
|
11
11
|
function requireNativeArchiveKind(kind) {
|
|
12
12
|
if (!getNativeBinding()) {
|
|
13
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require
|
|
14
|
-
"
|
|
13
|
+
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
14
|
+
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
15
15
|
}
|
|
16
16
|
return kind;
|
|
17
17
|
}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAwBjD,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,CA8HhB"}
|
package/dist/archive-native.js
CHANGED
|
@@ -7,6 +7,7 @@ import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
|
7
7
|
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, resolveExtractLimits, } from "./archive-limits.js";
|
|
8
8
|
import { resolveArchiveEntryMode, shouldExtractArchiveEntry } from "./archive-policy.js";
|
|
9
9
|
import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
10
|
+
import { admitZipFile } from "./archive-zip-admission.js";
|
|
10
11
|
function policyKind(kind) {
|
|
11
12
|
if (kind === "file" || kind === "directory")
|
|
12
13
|
return kind;
|
|
@@ -37,6 +38,9 @@ export async function extractNativeArchive(params) {
|
|
|
37
38
|
deadline: params.deadline,
|
|
38
39
|
});
|
|
39
40
|
try {
|
|
41
|
+
const physicalCount = params.kind === "zip"
|
|
42
|
+
? await admitZipFile(stagedArchive.path, limits, params.deadline)
|
|
43
|
+
: undefined;
|
|
40
44
|
const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
|
|
41
45
|
await withStagedArchiveDestination({
|
|
42
46
|
destinationRealDir,
|
|
@@ -47,6 +51,9 @@ export async function extractNativeArchive(params) {
|
|
|
47
51
|
.catch(throwMappedNativeError);
|
|
48
52
|
params.deadline.check();
|
|
49
53
|
assertArchiveEntryCountWithinLimit(manifest.length, limits);
|
|
54
|
+
if (physicalCount !== undefined && manifest.length !== physicalCount) {
|
|
55
|
+
throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
|
|
56
|
+
}
|
|
50
57
|
const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
|
|
51
58
|
const budget = createByteBudgetTracker(limits);
|
|
52
59
|
const trackOutputPath = createArchiveOutputPathTracker();
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AA8MzE,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,CAuFjB"}
|
package/dist/archive-read.js
CHANGED
|
@@ -15,6 +15,8 @@ import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./arch
|
|
|
15
15
|
import { FsSafeError } from "./errors.js";
|
|
16
16
|
import { sameFileIdentity } from "./file-identity.js";
|
|
17
17
|
import { getNativeBinding } from "./native.js";
|
|
18
|
+
import { admitZipBuffer } from "./archive-zip-admission.js";
|
|
19
|
+
import { resolveExtractLimits } from "./archive-limits.js";
|
|
18
20
|
import { tempFile } from "./temp-target.js";
|
|
19
21
|
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
20
22
|
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
@@ -127,7 +129,15 @@ async function readTarEntry(archivePath, entryPath, maxBytes) {
|
|
|
127
129
|
maxMetaEntrySize: DEFAULT_MAX_META_ENTRY_BYTES,
|
|
128
130
|
onReadEntry(entry) {
|
|
129
131
|
const info = readTarEntryInfo(entry);
|
|
130
|
-
|
|
132
|
+
try {
|
|
133
|
+
validateArchiveEntryPath(info.path, { escapeLabel: "archive root" });
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
// Throws escaping node-tar's callback do not reject its promise.
|
|
137
|
+
entryError ??= error instanceof Error ? error : new Error(String(error));
|
|
138
|
+
entry.resume();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
131
141
|
const normalized = normalizeArchiveEntryPath(info.path).replace(/^\.\//, "");
|
|
132
142
|
if (seenPaths.has(normalized)) {
|
|
133
143
|
entryError ??= new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
@@ -175,12 +185,18 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
175
185
|
const requestedEntry = normalizedRequestedEntry(entryPath);
|
|
176
186
|
const staged = await stageArchiveInput(archivePath);
|
|
177
187
|
try {
|
|
188
|
+
const physicalCount = kind === "zip"
|
|
189
|
+
? admitZipBuffer(staged.buffer, resolveExtractLimits())
|
|
190
|
+
: undefined;
|
|
178
191
|
const native = getNativeBinding();
|
|
179
192
|
if (native) {
|
|
180
193
|
try {
|
|
181
194
|
const signal = new AbortController().signal;
|
|
182
195
|
const manifest = await native.inspectArchiveNative(staged.path, kind, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, signal);
|
|
183
196
|
let rawEntryPath;
|
|
197
|
+
if (physicalCount !== undefined && manifest.length !== physicalCount) {
|
|
198
|
+
throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
|
|
199
|
+
}
|
|
184
200
|
const seenPaths = new Set();
|
|
185
201
|
for (const entry of manifest) {
|
|
186
202
|
validateArchiveEntryPath(entry.path, { escapeLabel: "archive root" });
|
|
@@ -202,9 +218,11 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
202
218
|
return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, signal);
|
|
203
219
|
}
|
|
204
220
|
catch (error) {
|
|
205
|
-
if (error instanceof Error
|
|
206
|
-
|
|
207
|
-
|
|
221
|
+
if (error instanceof Error) {
|
|
222
|
+
for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
|
|
223
|
+
if (error.message.includes(code))
|
|
224
|
+
throw new ArchiveLimitError(code);
|
|
225
|
+
}
|
|
208
226
|
}
|
|
209
227
|
if (error instanceof Error && isArchiveFormatErrorMessage(error.message)) {
|
|
210
228
|
throw new ArchiveFormatError(error.message, { cause: error });
|
|
@@ -213,7 +231,8 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
213
231
|
}
|
|
214
232
|
}
|
|
215
233
|
if (kind === "tar-zstd" || kind === "tar-bzip2") {
|
|
216
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require
|
|
234
|
+
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
235
|
+
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
217
236
|
}
|
|
218
237
|
return kind === "zip"
|
|
219
238
|
? await readZipEntry(staged.buffer, requestedEntry, options.maxBytes)
|
|
@@ -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":"AA8LA,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
|
@@ -4,11 +4,14 @@ import { pipeline } from "node:stream/promises";
|
|
|
4
4
|
import { createGunzip } from "node:zlib";
|
|
5
5
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
6
6
|
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError } from "./archive-limits.js";
|
|
7
|
+
import { parseLocalPax, paxMemberSize } from "./archive-tar-pax.js";
|
|
7
8
|
class TarMetadataMeter extends Transform {
|
|
8
9
|
maxMetaEntryBytes;
|
|
9
10
|
block = Buffer.alloc(512);
|
|
10
11
|
blockLength = 0;
|
|
11
12
|
state = { kind: "header" };
|
|
13
|
+
pendingPax;
|
|
14
|
+
pendingGnu = false;
|
|
12
15
|
constructor(maxMetaEntryBytes) {
|
|
13
16
|
super();
|
|
14
17
|
this.maxMetaEntryBytes = maxMetaEntryBytes;
|
|
@@ -32,7 +35,10 @@ class TarMetadataMeter extends Transform {
|
|
|
32
35
|
return Number(value);
|
|
33
36
|
}
|
|
34
37
|
const zero = field.indexOf(0);
|
|
35
|
-
const
|
|
38
|
+
const bytes = field.subarray(0, zero < 0 ? field.length : zero);
|
|
39
|
+
if (bytes.some((byte) => byte > 0x7f))
|
|
40
|
+
throw this.invalid("size is not ASCII octal");
|
|
41
|
+
const text = bytes.toString("ascii").trim();
|
|
36
42
|
if (!text || !/^[0-7]+$/.test(text))
|
|
37
43
|
throw this.invalid("size is not valid octal");
|
|
38
44
|
const value = Number.parseInt(text, 8);
|
|
@@ -42,6 +48,9 @@ class TarMetadataMeter extends Transform {
|
|
|
42
48
|
}
|
|
43
49
|
finishHeader() {
|
|
44
50
|
if (this.block.every((byte) => byte === 0)) {
|
|
51
|
+
if (this.pendingPax)
|
|
52
|
+
throw this.invalid("dangling PAX metadata");
|
|
53
|
+
this.pendingGnu = false;
|
|
45
54
|
this.blockLength = 0;
|
|
46
55
|
this.state = { kind: "header" };
|
|
47
56
|
return;
|
|
@@ -54,13 +63,37 @@ class TarMetadataMeter extends Transform {
|
|
|
54
63
|
if (this.block[156] !== 0x35 && name.at(-1) === 0x2f) {
|
|
55
64
|
throw this.invalid("non-directory entry path ends with a separator");
|
|
56
65
|
}
|
|
57
|
-
|
|
66
|
+
let size = this.parseSize();
|
|
58
67
|
const type = this.block[156];
|
|
59
|
-
if ([0x78, 0x67, 0x4c, 0x4b, 0x58].includes(type
|
|
68
|
+
if ([0x78, 0x67, 0x4c, 0x4b, 0x58, 0x4e].includes(type) && size > this.maxMetaEntryBytes) {
|
|
60
69
|
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.META_ENTRY_SIZE_EXCEEDS_LIMIT);
|
|
61
70
|
}
|
|
62
|
-
if (type ===
|
|
63
|
-
throw this.invalid("PAX
|
|
71
|
+
if (type === 0x67 || type === 0x58 || type === 0x4e) {
|
|
72
|
+
throw this.invalid("global/old PAX and old GNU metadata are not supported");
|
|
73
|
+
}
|
|
74
|
+
if (type === 0x78) {
|
|
75
|
+
if (this.pendingPax || this.pendingGnu || size === 0)
|
|
76
|
+
throw this.invalid("empty, repeated or mixed PAX metadata");
|
|
77
|
+
const magic = this.block.subarray(257, 265).toString("latin1");
|
|
78
|
+
if (magic !== "ustar\0" + "00" && magic !== "ustar \0")
|
|
79
|
+
throw this.invalid("unrecognized PAX header format");
|
|
80
|
+
const padded = Math.ceil(size / 512) * 512;
|
|
81
|
+
if (!Number.isSafeInteger(padded))
|
|
82
|
+
throw this.invalid("entry padding exceeds the safe integer range");
|
|
83
|
+
this.state = { kind: "pax", body: Buffer.alloc(size), used: 0, padding: padded - size };
|
|
84
|
+
this.blockLength = 0;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Sparse headers retain their metadata-limit-before-format-error ordering.
|
|
88
|
+
if (this.pendingPax && type !== 0x53) {
|
|
89
|
+
size = paxMemberSize(this.pendingPax, type, size, this.block);
|
|
90
|
+
this.pendingPax = undefined;
|
|
91
|
+
}
|
|
92
|
+
if (type === 0x4c || type === 0x4b) {
|
|
93
|
+
this.pendingGnu = true;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.pendingGnu = false;
|
|
64
97
|
}
|
|
65
98
|
const padded = Math.ceil(size / 512) * 512;
|
|
66
99
|
if (!Number.isSafeInteger(padded))
|
|
@@ -98,6 +131,18 @@ class TarMetadataMeter extends Transform {
|
|
|
98
131
|
meter(chunk) {
|
|
99
132
|
let offset = 0;
|
|
100
133
|
while (offset < chunk.length) {
|
|
134
|
+
if (this.state.kind === "pax") {
|
|
135
|
+
const state = this.state;
|
|
136
|
+
const take = Math.min(state.body.length - state.used, chunk.length - offset);
|
|
137
|
+
chunk.copy(state.body, state.used, offset, offset + take);
|
|
138
|
+
state.used += take;
|
|
139
|
+
offset += take;
|
|
140
|
+
if (state.used === state.body.length) {
|
|
141
|
+
this.pendingPax = parseLocalPax(state.body);
|
|
142
|
+
this.state = state.padding === 0 ? { kind: "header" } : { kind: "data", remaining: state.padding };
|
|
143
|
+
}
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
101
146
|
if (this.state.kind === "data") {
|
|
102
147
|
const take = Math.min(this.state.remaining, chunk.length - offset);
|
|
103
148
|
offset += take;
|
|
@@ -127,7 +172,9 @@ class TarMetadataMeter extends Transform {
|
|
|
127
172
|
}
|
|
128
173
|
}
|
|
129
174
|
_flush(callback) {
|
|
130
|
-
if (this.
|
|
175
|
+
if (this.pendingPax)
|
|
176
|
+
callback(this.invalid("dangling PAX metadata"));
|
|
177
|
+
else if (this.state.kind === "header" && this.blockLength === 0)
|
|
131
178
|
callback();
|
|
132
179
|
else
|
|
133
180
|
callback(this.invalid(this.state.kind === "header" ? "truncated TAR header" : "truncated TAR entry"));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type LocalPax = {
|
|
2
|
+
path?: string;
|
|
3
|
+
linkpath?: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function parseLocalPax(body: Buffer): LocalPax;
|
|
7
|
+
export declare function paxMemberSize(pax: LocalPax, type: number, rawSize: number, header: Buffer): number;
|
|
8
|
+
//# sourceMappingURL=archive-tar-pax.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-tar-pax.d.ts","sourceRoot":"","sources":["../src/archive-tar-pax.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAqB3E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAwCpD;AAQD,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAclG"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ArchiveFormatError } from "./archive-errors.js";
|
|
2
|
+
function invalid() {
|
|
3
|
+
throw new ArchiveFormatError("invalid TAR header: unsupported or malformed PAX metadata");
|
|
4
|
+
}
|
|
5
|
+
function decimal(bytes) {
|
|
6
|
+
const text = bytes.toString("latin1");
|
|
7
|
+
if (!/^(0|[1-9][0-9]*)$/.test(text))
|
|
8
|
+
invalid();
|
|
9
|
+
const value = Number(text);
|
|
10
|
+
if (!Number.isSafeInteger(value))
|
|
11
|
+
invalid();
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function ascii(bytes) {
|
|
15
|
+
if (bytes.length === 0 || bytes.some((byte) => byte < 0x20 || byte > 0x7e))
|
|
16
|
+
invalid();
|
|
17
|
+
return bytes.toString("ascii");
|
|
18
|
+
}
|
|
19
|
+
// Keep this grammar aligned with native/src/tar_pax.rs. Downstream parsers
|
|
20
|
+
// disagree on duplicates, coercions and UTF-8 chunk boundaries.
|
|
21
|
+
export function parseLocalPax(body) {
|
|
22
|
+
if (body.length === 0)
|
|
23
|
+
invalid();
|
|
24
|
+
const result = {};
|
|
25
|
+
const keys = new Set();
|
|
26
|
+
let offset = 0;
|
|
27
|
+
while (offset < body.length) {
|
|
28
|
+
const space = body.indexOf(0x20, offset);
|
|
29
|
+
if (space < 0)
|
|
30
|
+
invalid();
|
|
31
|
+
const length = decimal(body.subarray(offset, space));
|
|
32
|
+
if (length > body.length - offset || length <= space - offset + 3)
|
|
33
|
+
invalid();
|
|
34
|
+
const end = offset + length;
|
|
35
|
+
if (body[end - 1] !== 0x0a)
|
|
36
|
+
invalid();
|
|
37
|
+
const record = body.subarray(space + 1, end - 1);
|
|
38
|
+
if (record.includes(0x0a))
|
|
39
|
+
invalid();
|
|
40
|
+
const equals = record.indexOf(0x3d);
|
|
41
|
+
if (equals <= 0)
|
|
42
|
+
invalid();
|
|
43
|
+
const key = ascii(record.subarray(0, equals));
|
|
44
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key) || keys.has(key))
|
|
45
|
+
invalid();
|
|
46
|
+
keys.add(key);
|
|
47
|
+
const value = record.subarray(equals + 1);
|
|
48
|
+
if (/^(LIBARCHIVE|SCHILY)\.xattr\..+$/.test(key)) {
|
|
49
|
+
// Inert bytes, never restored. Newlines would corrupt Rust's subsequent
|
|
50
|
+
// record lookup; NUL and non-UTF8 bytes in these values are harmless.
|
|
51
|
+
}
|
|
52
|
+
else if (key === "path" || key === "linkpath") {
|
|
53
|
+
result[key] = ascii(value);
|
|
54
|
+
}
|
|
55
|
+
else if (key === "size") {
|
|
56
|
+
result.size = decimal(value);
|
|
57
|
+
}
|
|
58
|
+
else if (key === "uid" || key === "gid") {
|
|
59
|
+
decimal(value);
|
|
60
|
+
}
|
|
61
|
+
else if (key === "uname" || key === "gname") {
|
|
62
|
+
ascii(value);
|
|
63
|
+
}
|
|
64
|
+
else if (key === "mtime" || key === "atime" || key === "ctime") {
|
|
65
|
+
const text = ascii(value);
|
|
66
|
+
if (!/^-?(0|[1-9][0-9]*)(\.[0-9]+)?$/.test(text) || Math.abs(Number(text)) > 8_640_000_000_000)
|
|
67
|
+
invalid();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
invalid();
|
|
71
|
+
}
|
|
72
|
+
offset = end;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function rawText(field) {
|
|
77
|
+
const zero = field.indexOf(0);
|
|
78
|
+
const value = field.subarray(0, zero < 0 ? field.length : zero);
|
|
79
|
+
return value.length === 0 ? "" : ascii(value);
|
|
80
|
+
}
|
|
81
|
+
export function paxMemberSize(pax, type, rawSize, header) {
|
|
82
|
+
if (![0, 0x30, 0x31, 0x32, 0x35, 0x37].includes(type))
|
|
83
|
+
invalid();
|
|
84
|
+
const rawName = rawText(header.subarray(0, 100));
|
|
85
|
+
const rawLink = rawText(header.subarray(157, 257));
|
|
86
|
+
if (header.subarray(257, 265).equals(Buffer.from("ustar\0" + "00"))) {
|
|
87
|
+
rawText(header.subarray(345, 500));
|
|
88
|
+
}
|
|
89
|
+
const isLink = type === 0x31 || type === 0x32;
|
|
90
|
+
if (isLink !== (rawLink.length > 0))
|
|
91
|
+
invalid();
|
|
92
|
+
const size = pax.size ?? rawSize;
|
|
93
|
+
if ([0x31, 0x32, 0x35].includes(type) && (rawSize !== 0 || size !== 0))
|
|
94
|
+
invalid();
|
|
95
|
+
if (type !== 0x35 && (pax.path?.endsWith("/") || pax.path?.endsWith("\\") || rawName.endsWith("\\")))
|
|
96
|
+
invalid();
|
|
97
|
+
if (pax.linkpath !== undefined && !isLink)
|
|
98
|
+
invalid();
|
|
99
|
+
return size;
|
|
100
|
+
}
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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,OAAO,EAAE,IAAI,CAAC;QACd,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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExtractionDeadline } from "./archive-deadline.js";
|
|
2
|
+
import { type ResolvedArchiveExtractLimits } from "./archive-limits.js";
|
|
3
|
+
/** Admit physical records before a decoder can normalize or collapse names. */
|
|
4
|
+
export declare function admitZipBuffer(input: Uint8Array, limits: ResolvedArchiveExtractLimits): number;
|
|
5
|
+
/** The extraction input is already private and staged; read only bounded metadata. */
|
|
6
|
+
export declare function admitZipFile(archivePath: string, limits: ResolvedArchiveExtractLimits, deadline: ExtractionDeadline): Promise<number>;
|
|
7
|
+
//# sourceMappingURL=archive-zip-admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-zip-admission.d.ts","sourceRoot":"","sources":["../src/archive-zip-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAA+C,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAYrH,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,GAAG,MAAM,CAU9F;AAED,sFAAsF;AACtF,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+B3I"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError } from "./archive-limits.js";
|
|
4
|
+
import { openRootFile } from "./root-file.js";
|
|
5
|
+
import { scanZipDirectory } from "./archive-zip-directory.js";
|
|
6
|
+
import { zipFormat } from "./archive-zip-names.js";
|
|
7
|
+
function checkSize(size, limits) {
|
|
8
|
+
if (size > limits.maxArchiveBytes) {
|
|
9
|
+
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
|
|
10
|
+
}
|
|
11
|
+
if (!Number.isSafeInteger(size) || size < 0)
|
|
12
|
+
zipFormat("invalid archive size");
|
|
13
|
+
}
|
|
14
|
+
/** Admit physical records before a decoder can normalize or collapse names. */
|
|
15
|
+
export function admitZipBuffer(input, limits) {
|
|
16
|
+
checkSize(input.byteLength, limits);
|
|
17
|
+
const buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
18
|
+
const scan = scanZipDirectory(buffer.length, limits);
|
|
19
|
+
let step = scan.next();
|
|
20
|
+
while (!step.done) {
|
|
21
|
+
const { offset, length } = step.value;
|
|
22
|
+
step = scan.next(buffer.subarray(offset, offset + length));
|
|
23
|
+
}
|
|
24
|
+
return step.value;
|
|
25
|
+
}
|
|
26
|
+
/** The extraction input is already private and staged; read only bounded metadata. */
|
|
27
|
+
export async function admitZipFile(archivePath, limits, deadline) {
|
|
28
|
+
deadline.check();
|
|
29
|
+
const opened = await openRootFile({
|
|
30
|
+
absolutePath: archivePath, rootPath: path.dirname(archivePath),
|
|
31
|
+
boundaryLabel: "staged ZIP archive", rejectSymlinks: true, rejectHardlinks: true,
|
|
32
|
+
});
|
|
33
|
+
if (!opened.ok)
|
|
34
|
+
throw opened.error ?? new Error("cannot open staged ZIP archive");
|
|
35
|
+
try {
|
|
36
|
+
checkSize(opened.stat.size, limits);
|
|
37
|
+
const scan = scanZipDirectory(opened.stat.size, limits);
|
|
38
|
+
let step = scan.next();
|
|
39
|
+
while (!step.done) {
|
|
40
|
+
const { offset, length } = step.value;
|
|
41
|
+
const bytes = Buffer.alloc(length);
|
|
42
|
+
let received = 0;
|
|
43
|
+
while (received < length) {
|
|
44
|
+
deadline.check();
|
|
45
|
+
const count = await new Promise((resolve, reject) => {
|
|
46
|
+
fs.read(opened.fd, bytes, received, length - received, offset + received, (error, count) => error ? reject(error) : resolve(count));
|
|
47
|
+
});
|
|
48
|
+
deadline.check();
|
|
49
|
+
if (!count)
|
|
50
|
+
zipFormat("truncated staged record");
|
|
51
|
+
received += count;
|
|
52
|
+
}
|
|
53
|
+
step = scan.next(bytes);
|
|
54
|
+
}
|
|
55
|
+
return step.value;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
fs.closeSync(opened.fd);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-zip-count.d.ts","sourceRoot":"","sources":["../src/archive-zip-count.ts"],"names":[],"mappings":"AA4KA,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAU5F"}
|