@openclaw/fs-safe 0.8.1 → 0.8.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 +2 -2
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -2
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +5 -0
- package/dist/archive-gzip-tail.d.ts +18 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -0
- package/dist/archive-gzip-tail.js +74 -0
- package/dist/archive-kind.d.ts +1 -0
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +6 -0
- package/dist/archive-limits.d.ts +0 -1
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +0 -3
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +5 -8
- package/dist/archive-parser-errors.d.ts +2 -0
- package/dist/archive-parser-errors.d.ts.map +1 -0
- package/dist/archive-parser-errors.js +12 -0
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +25 -73
- package/dist/archive-tar-stream.d.ts +18 -0
- package/dist/archive-tar-stream.d.ts.map +1 -0
- package/dist/archive-tar-stream.js +95 -0
- package/dist/archive-tar-wasm.d.ts +18 -0
- package/dist/archive-tar-wasm.d.ts.map +1 -0
- package/dist/archive-tar-wasm.js +99 -0
- package/dist/archive-tar.d.ts +0 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +0 -22
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +44 -114
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/atomic.js +1 -1
- package/dist/bounded-read-stream.d.ts +1 -5
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +5 -13
- package/dist/byte-budget.d.ts +0 -1
- package/dist/byte-budget.d.ts.map +1 -1
- package/dist/byte-budget.js +1 -1
- package/dist/device-path.d.ts +1 -0
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -2
- package/dist/directory-mode-owner.d.ts.map +1 -1
- package/dist/directory-mode-owner.js +5 -0
- package/dist/error-detail.d.ts +1 -0
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +6 -0
- package/dist/file-store-boundary.d.ts +1 -1
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +2 -13
- package/dist/file-store.js +1 -1
- package/dist/file-sync.d.ts +5 -0
- package/dist/file-sync.d.ts.map +1 -0
- package/dist/file-sync.js +19 -0
- package/dist/guarded-mutation.d.ts +3 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +5 -1
- package/dist/home-dir.d.ts +0 -10
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +0 -28
- package/dist/json-durable-queue-ownership.d.ts +1 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +139 -18
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +5 -10
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +1 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +2 -3
- package/dist/move-path-cleanup.d.ts +1 -1
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +4 -2
- package/dist/move-path.d.ts +9 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +53 -16
- package/dist/native-operations.d.ts +0 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -10
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +5 -4
- package/dist/native-staged-file.d.ts +8 -1
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +30 -9
- package/dist/opened-realpath.d.ts +12 -1
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +8 -7
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +26 -2
- package/dist/path-policy.js +1 -1
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -0
- package/dist/pinned-write.d.ts +1 -2
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +2 -11
- package/dist/private-temp-workspace.js +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +2 -7
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +5 -17
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +2 -7
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +21 -8
- package/dist/root-errors.d.ts +1 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +9 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +17 -28
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +12 -33
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +49 -94
- package/dist/root-write-mode.d.ts +6 -0
- package/dist/root-write-mode.d.ts.map +1 -0
- package/dist/root-write-mode.js +60 -0
- package/dist/root-write-verification.js +2 -2
- package/dist/safe-path-segment.d.ts +1 -0
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +4 -9
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +5 -10
- package/dist/secret-read-policy.d.ts +4 -0
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +12 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +2 -7
- package/dist/sidecar-lock-policy.d.ts +0 -1
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +0 -4
- package/dist/string-coerce.d.ts +0 -9
- package/dist/string-coerce.d.ts.map +1 -1
- package/dist/string-coerce.js +0 -56
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +1 -12
- package/docs/archive.md +66 -61
- package/docs/atomic.md +53 -19
- package/docs/contributing.md +35 -1
- package/docs/install.md +1 -1
- package/docs/native-helper.md +5 -0
- package/docs/native.md +16 -10
- package/docs/secret-file.md +5 -0
- package/docs/staged-file.md +6 -2
- package/docs/writing.md +5 -0
- package/package.json +19 -16
- package/dist/archive-tar-admission.d.ts +0 -7
- package/dist/archive-tar-admission.d.ts.map +0 -1
- package/dist/archive-tar-admission.js +0 -43
- package/dist/archive-tar-gnu.d.ts +0 -2
- package/dist/archive-tar-gnu.d.ts.map +0 -1
- package/dist/archive-tar-gnu.js +0 -20
- package/dist/archive-tar-header.d.ts +0 -8
- package/dist/archive-tar-header.d.ts.map +0 -1
- package/dist/archive-tar-header.js +0 -47
- package/dist/archive-tar-meta.d.ts +0 -34
- package/dist/archive-tar-meta.d.ts.map +0 -1
- package/dist/archive-tar-meta.js +0 -277
- package/dist/archive-tar-pax.d.ts +0 -8
- package/dist/archive-tar-pax.d.ts.map +0 -1
- package/dist/archive-tar-pax.js +0 -100
- package/dist/archive-tar-runtime.d.ts +0 -49
- package/dist/archive-tar-runtime.d.ts.map +0 -1
- package/dist/archive-tar-runtime.js +0 -22
- package/dist/fsync.d.ts +0 -2
- package/dist/fsync.d.ts.map +0 -1
- package/dist/fsync.js +0 -1
- package/dist/json-durable-queue-retirement.d.ts +0 -9
- package/dist/json-durable-queue-retirement.d.ts.map +0 -1
- package/dist/json-durable-queue-retirement.js +0 -126
- package/dist/json-durable-queue-transfer-lock.d.ts +0 -2
- package/dist/json-durable-queue-transfer-lock.d.ts.map +0 -1
- package/dist/json-durable-queue-transfer-lock.js +0 -19
- package/dist/mode.d.ts +0 -2
- package/dist/mode.d.ts.map +0 -1
- package/dist/mode.js +0 -3
- package/dist/output-sibling.d.ts +0 -8
- package/dist/output-sibling.d.ts.map +0 -1
- package/dist/output-sibling.js +0 -30
- package/dist/read-error.d.ts +0 -2
- package/dist/read-error.d.ts.map +0 -1
- package/dist/read-error.js +0 -11
- package/dist/short-path.d.ts +0 -2
- package/dist/short-path.d.ts.map +0 -1
- package/dist/short-path.js +0 -7
- package/dist/staged-file.d.ts +0 -10
- package/dist/staged-file.d.ts.map +0 -1
- package/dist/staged-file.js +0 -15
package/docs/contributing.md
CHANGED
|
@@ -19,7 +19,16 @@ manager version declared in `package.json`.
|
|
|
19
19
|
pnpm build
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Runs
|
|
22
|
+
Runs TypeScript compilation and builds the portable Rust TAR parser for
|
|
23
|
+
`wasm32-unknown-unknown`. Contributors need Rust (the native crate's declared
|
|
24
|
+
minimum or newer) and `rustup target add wasm32-unknown-unknown`; Alpine's
|
|
25
|
+
packaged toolchain uses `rust-wasm`. `pnpm archive:wasm` rebuilds just the parser.
|
|
26
|
+
The import-free asset lands at `dist/archive-parser.wasm`; source tests and
|
|
27
|
+
compiled consumers both resolve that generated artifact. Run `pnpm build`
|
|
28
|
+
before source tests in a fresh checkout. Do not commit `dist/` or built WASM.
|
|
29
|
+
Consumers receive the asset in the npm package and need no compiler.
|
|
30
|
+
|
|
31
|
+
Output lands in `dist/`. The package's `prepack` hook re-runs the build before publishing — manual `pnpm build` is only required when you want to inspect the output or run a freshly-built copy locally.
|
|
23
32
|
|
|
24
33
|
## Test
|
|
25
34
|
|
|
@@ -59,8 +68,33 @@ pnpm check
|
|
|
59
68
|
This runs the filesystem boundary checks, build, tests, and package
|
|
60
69
|
tarball/import validation.
|
|
61
70
|
|
|
71
|
+
### Real TAR producers
|
|
72
|
+
|
|
73
|
+
After installing the freshly packed root (and optionally its freshly built host
|
|
74
|
+
binding) in a disposable consumer, run:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pnpm archive:producer-smoke ./consumer off
|
|
78
|
+
pnpm archive:producer-smoke ./consumer require
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This uses a child bound to canonical cwd/device/inode running `/usr/bin/tar -czf - .`
|
|
82
|
+
with unchanged stdout, and npm tar, on synthetic Unicode/newline/long-name files,
|
|
83
|
+
then the installed package API for exact payload hashes and bounded reads.
|
|
84
|
+
It also rejects a valid PAX override attached to an invalid raw UTF-8 field.
|
|
85
|
+
The `require` command must resolve the freshly packed native binding; the
|
|
86
|
+
`off` command uses the installed WASM asset. No live user files are read.
|
|
87
|
+
|
|
62
88
|
### Native consumer installs
|
|
63
89
|
|
|
90
|
+
The CI Node 24 and native jobs also run `node scripts/device-path-proof.mjs off`
|
|
91
|
+
and `node scripts/device-path-proof.mjs require` against the built package.
|
|
92
|
+
This extracts real ZIP files and checks bounded member reads, preserving reserved
|
|
93
|
+
device-like names on POSIX while rejecting them and ignored-space aliases on
|
|
94
|
+
Windows. It also verifies ordinary secret reads and typed device-path rejection
|
|
95
|
+
without replacing filesystem functions. Run after `pnpm build`, and build the
|
|
96
|
+
host binding with `pnpm native:build` before the `require` case.
|
|
97
|
+
|
|
64
98
|
After `pnpm build` and a fresh `pnpm native:build`, run `pnpm package:smoke`.
|
|
65
99
|
It packs the real root and host binding, then runs root-only npm and the
|
|
66
100
|
declared pnpm version against a disposable loopback registry. The root's exact
|
package/docs/install.md
CHANGED
|
@@ -85,7 +85,7 @@ Use the main entry for the common surface, or the focused subpaths when you want
|
|
|
85
85
|
|
|
86
86
|
## Runtime dependencies
|
|
87
87
|
|
|
88
|
-
`@openclaw/fs-safe`
|
|
88
|
+
`@openclaw/fs-safe` bundles an import-free WASM build of its Rust TAR parser for guarded JavaScript TAR/gzip [archive extraction](archive.md), including installs with optional dependencies omitted. ZIP fallback uses lazily loaded optional `jszip` and reports a missing-dependency error without it. Public subpaths remain safe to import with all optional dependencies omitted, but imports do not prove native availability.
|
|
89
89
|
|
|
90
90
|
There are no peer dependencies. Exact-version optional packages carry the seven
|
|
91
91
|
native targets and npm-compatible OS, CPU, and Linux libc filters install only
|
package/docs/native-helper.md
CHANGED
|
@@ -29,6 +29,11 @@ The equivalent environment variables are `FS_SAFE_NATIVE_MODE` and `OPENCLAW_FS_
|
|
|
29
29
|
| `off` | Do not load a native package. Use the guarded JavaScript path deterministically. |
|
|
30
30
|
| `require` | Throw `FsSafeError("helper-unavailable")` instead of falling back when an operation needs the native binding and it cannot load. |
|
|
31
31
|
|
|
32
|
+
TAR/gzip in the guarded JavaScript path uses a bundled, import-free WASM build
|
|
33
|
+
of the same Rust parser used by native. `off` still disables native filesystem
|
|
34
|
+
code; it does not disable this portable parser. ZIP fallback still requires
|
|
35
|
+
optional `jszip`, and zstd/bzip2 remain native-only.
|
|
36
|
+
|
|
32
37
|
Configure the mode once during startup. Loading is lazy and cached; changing from `auto` to `require` after a failed load changes failure policy but does not repeatedly probe the binary.
|
|
33
38
|
|
|
34
39
|
[`tempWorkspace()` and its scoped/sync variants](temp.md#private-temp-workspaces)
|
package/docs/native.md
CHANGED
|
@@ -64,13 +64,15 @@ returns a bounded manifest. TypeScript applies the shared path, filter, strip,
|
|
|
64
64
|
mode, and byte policies and returns an index-bound extraction plan. Rust then
|
|
65
65
|
creates only those planned entries beneath a private staging descriptor.
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
The `fs-safe-archive-core` Rust workspace crate owns TAR framing, paths, types,
|
|
68
|
+
mode decoding, GNU metadata, and byte-counted local PAX records. The native
|
|
69
|
+
binding and bundled WASM module compile the same source. No `tar::Archive` or
|
|
70
|
+
Node TAR parser reinterprets admitted identities or sizes. Executors replay
|
|
71
|
+
admitted payload ranges after complete bounded admission; native writes retain
|
|
72
|
+
the platform's descriptor-relative primitives, while fallback writes retain
|
|
73
|
+
the guarded Node staging/publication boundary. ZIP behavior is unchanged.
|
|
74
|
+
`maxMetaEntryBytes` bounds bodies before allocation; unsupported global/old
|
|
75
|
+
metadata and sparse forms fail closed. See [bounded local PAX support](archive.md#bounded-local-pax-support).
|
|
74
76
|
|
|
75
77
|
Every raw pass receives only TypeScript's resolved `maxEntries`,
|
|
76
78
|
`maxMetaEntryBytes`, and `maxDecodedBytes`. Shared resolution caps metadata and
|
|
@@ -88,11 +90,15 @@ integer maximum. Every native pass receives that same cap and charges headers,
|
|
|
88
90
|
metadata, bodies, padding, EOF blocks, and trailing zeros. It rejects overflow
|
|
89
91
|
with `archive-decoded-size-exceeds-limit`; no ratio policy is implied.
|
|
90
92
|
Extraction and entry reads drain the metered reader through physical EOF after
|
|
91
|
-
|
|
93
|
+
admitted-range replay. Trailing framing or decoded-limit failures propagate before
|
|
92
94
|
directory modes are finalized, staging is published, or selected bytes return.
|
|
95
|
+
Native gzip uses the existing flate2 member decoder with an explicit bounded
|
|
96
|
+
member/padding transition; JavaScript retains Node gunzip and validates its
|
|
97
|
+
unconsumed compressed suffix. Only all-zero physical padding after a complete
|
|
98
|
+
validated trailer is accepted, still within the original archive-byte budget.
|
|
93
99
|
Native reads stop at framing boundaries so a rejected header does not request
|
|
94
100
|
its body from the decoder; codec buffering can still read ahead internally.
|
|
95
|
-
Inspection finishes the complete bounded framing pass before
|
|
101
|
+
Inspection finishes the complete bounded framing pass before returning its manifest. Directory
|
|
96
102
|
and link bodies, missing two-block EOF, and nonzero trailers reject on both
|
|
97
103
|
backends, as detailed in [raw TAR framing](archive.md#raw-tar-framing). Raw and
|
|
98
104
|
padded sizes above JavaScript's safe-integer maximum reject as invalid framing
|
|
@@ -155,7 +161,7 @@ remain TypeScript-owned. What changes is the syscall strength or availability:
|
|
|
155
161
|
| Capability | Native path | Guarded JavaScript path |
|
|
156
162
|
|---|---|---|
|
|
157
163
|
| Root-relative opens/mutations | Descriptor-relative beneath operations. Pinned writes create parents and publish both replacement and no-replace targets relative to open directory descriptors. Linux reports `kernel-atomic`; macOS and Windows report `best-effort`. macOS uses `O_RESOLVE_BENEATH` when available plus an `F_GETPATH` detector, while Windows rejects reparse traversal in the object-manager call. | Reports `best-effort`: component-wise alias checks, no-follow opens where Node exposes them, private temp/rename, and post-operation identity verification. A same-privilege peer can replace a writable parent after a guard assertion but before Node resolves the pathname mutation; the mutation may land outside the intended root before the post-check detects it. |
|
|
158
|
-
| ZIP/TAR/gzip | Rust streaming decode and fd-relative output creation. | JSZip
|
|
164
|
+
| ZIP/TAR/gzip | Rust streaming decode and fd-relative output creation. | Optional JSZip or bundled WASM TAR into guarded private staging, then the same guarded merge policy. |
|
|
159
165
|
| Zstd/bzip2 TAR | Supported. | Unsupported; typed `helper-unavailable`. |
|
|
160
166
|
| Publication copy | Clone, Linux `copy_file_range`, async native SHA-256. | Exclusive `wx` byte loop and Node SHA-256 with the same content/identity fences. |
|
|
161
167
|
| `rename-noreplace` | Atomic platform no-replace rename. | Unsupported; no emulation by check-then-rename. |
|
package/docs/secret-file.md
CHANGED
|
@@ -91,6 +91,11 @@ credential must also fail on broad permissions or unexpected ownership.
|
|
|
91
91
|
the same pinned-handle validation, byte cap, trimming, error codes, and strict
|
|
92
92
|
versus missing-is-undefined naming semantics.
|
|
93
93
|
|
|
94
|
+
Both readers reject known unsafe device and process-fd paths with `device-path`
|
|
95
|
+
before inspection, and check the resolved target before opening it. This includes
|
|
96
|
+
Windows reserved device names and ignored-space aliases such as `nul .txt`.
|
|
97
|
+
Optional readers propagate this error instead of treating the secret as missing.
|
|
98
|
+
|
|
94
99
|
Both sync and async readers compare lossless bigint identities from the preview,
|
|
95
100
|
opened descriptor, resolved target, and current input path before reading. POSIX
|
|
96
101
|
opens are nonblocking, so a raced FIFO is rejected by descriptor type instead of
|
package/docs/staged-file.md
CHANGED
|
@@ -66,8 +66,12 @@ Strings are UTF-8. `mode` is the requested **published** mode and defaults to
|
|
|
66
66
|
`0600`; exact final modes, including `000`, are supported. The unpublished file
|
|
67
67
|
stays at `0600` throughout preparation and any awaited application checks.
|
|
68
68
|
After rename succeeds and the published entry passes identity validation, the
|
|
69
|
-
owner applies the requested mode through its retained file descriptor
|
|
70
|
-
|
|
69
|
+
owner applies the requested mode through its retained file descriptor. Content
|
|
70
|
+
was synchronized during preparation; publication always synchronizes the parent.
|
|
71
|
+
Modes retaining owner read/write skip the extra mode-only file sync, so a crash
|
|
72
|
+
may leave the tighter staged `0600` instead of the wider requested mode. Modes
|
|
73
|
+
removing owner read or write, and corrections of an observed wider staged mode,
|
|
74
|
+
retain the post-chmod file sync. No parent is created or chmodded.
|
|
71
75
|
Creation uses an exclusive, no-follow, close-on-exec open of a generated direct
|
|
72
76
|
child name. Writes use that descriptor. Inspection uses non-following metadata
|
|
73
77
|
operations, never a potentially blocking reopen of the leaf.
|
package/docs/writing.md
CHANGED
|
@@ -84,6 +84,11 @@ final verification uses a descriptor retained by the writer rather than reopenin
|
|
|
84
84
|
the published file. The requested mode is not relaxed for verification.
|
|
85
85
|
Publication verification compares exact bigint descriptor and pathname identities,
|
|
86
86
|
including large file indexes that cannot be represented by a JavaScript number.
|
|
87
|
+
Native publication syncs content before rename and always syncs the parent directory.
|
|
88
|
+
Modes that retain owner read/write skip the extra mode-only file sync: after a
|
|
89
|
+
crash, the file may retain staged mode `0o600` instead of the wider requested mode.
|
|
90
|
+
Modes that remove owner read or write, and corrections of observed wider staging
|
|
91
|
+
permissions, keep the post-chmod file sync.
|
|
87
92
|
Later reads still obey OS permissions, and access checks on a pre-existing
|
|
88
93
|
destination are unchanged. The explicit FUSE compatibility policy still requires
|
|
89
94
|
a readable destination to prove matching content when rename changes its identity.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"provenance": true
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
+
"dist/archive-parser.wasm",
|
|
27
28
|
"dist/**/*.js",
|
|
28
29
|
"dist/**/*.d.ts",
|
|
29
30
|
"dist/**/*.d.ts.map",
|
|
@@ -126,7 +127,7 @@
|
|
|
126
127
|
"scripts": {
|
|
127
128
|
"benchmark": "node scripts/benchmark.mjs",
|
|
128
129
|
"benchmark:publish": "pnpm build && node scripts/bench-publish.mjs",
|
|
129
|
-
"build": "node -
|
|
130
|
+
"build": "node scripts/prepack-build.mjs",
|
|
130
131
|
"lint:file-size": "node scripts/check-file-size.mjs",
|
|
131
132
|
"lint:fs-boundary": "node scripts/check-fs-boundary-primitives.mjs",
|
|
132
133
|
"prepack": "node scripts/prepack-build.mjs",
|
|
@@ -139,7 +140,7 @@
|
|
|
139
140
|
"docs:check": "node scripts/check-doc-examples.mjs",
|
|
140
141
|
"docs:site": "node scripts/build-docs-site.mjs",
|
|
141
142
|
"native:build": "pnpm --filter @openclaw/fs-safe-native-build build",
|
|
142
|
-
"native:test": "cargo test --
|
|
143
|
+
"native:test": "cargo test --workspace",
|
|
143
144
|
"pack:check": "pnpm build && node scripts/check-pack.mjs",
|
|
144
145
|
"public-api:update": "pnpm build && node scripts/check-pack.mjs --update-public-api",
|
|
145
146
|
"package:collect": "node scripts/check-release-packages.mjs --output release-artifacts",
|
|
@@ -150,32 +151,34 @@
|
|
|
150
151
|
"crabbox:hydrate": "crabbox actions hydrate",
|
|
151
152
|
"crabbox:run": "crabbox run",
|
|
152
153
|
"crabbox:stop": "crabbox stop",
|
|
153
|
-
"crabbox:warmup": "crabbox warmup"
|
|
154
|
+
"crabbox:warmup": "crabbox warmup",
|
|
155
|
+
"archive:wasm": "node scripts/build-archive-wasm.mjs",
|
|
156
|
+
"archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
|
|
154
157
|
},
|
|
155
158
|
"optionalDependencies": {
|
|
156
|
-
"@openclaw/fs-safe-darwin-arm64": "0.8.
|
|
157
|
-
"@openclaw/fs-safe-darwin-x64": "0.8.
|
|
158
|
-
"@openclaw/fs-safe-linux-arm64-gnu": "0.8.
|
|
159
|
-
"@openclaw/fs-safe-linux-arm64-musl": "0.8.
|
|
160
|
-
"@openclaw/fs-safe-linux-x64-gnu": "0.8.
|
|
161
|
-
"@openclaw/fs-safe-linux-x64-musl": "0.8.
|
|
162
|
-
"@openclaw/fs-safe-win32-x64-msvc": "0.8.
|
|
163
|
-
"jszip": "^3.10.1"
|
|
164
|
-
"tar": "7.5.22"
|
|
159
|
+
"@openclaw/fs-safe-darwin-arm64": "0.8.3",
|
|
160
|
+
"@openclaw/fs-safe-darwin-x64": "0.8.3",
|
|
161
|
+
"@openclaw/fs-safe-linux-arm64-gnu": "0.8.3",
|
|
162
|
+
"@openclaw/fs-safe-linux-arm64-musl": "0.8.3",
|
|
163
|
+
"@openclaw/fs-safe-linux-x64-gnu": "0.8.3",
|
|
164
|
+
"@openclaw/fs-safe-linux-x64-musl": "0.8.3",
|
|
165
|
+
"@openclaw/fs-safe-win32-x64-msvc": "0.8.3",
|
|
166
|
+
"jszip": "^3.10.1"
|
|
165
167
|
},
|
|
166
168
|
"devDependencies": {
|
|
167
169
|
"@emnapi/runtime": "2.0.0-alpha.4",
|
|
168
|
-
"@napi-rs/cli": "3.
|
|
170
|
+
"@napi-rs/cli": "3.9.0",
|
|
169
171
|
"@types/node": "^26.4.1",
|
|
170
|
-
"@vitest/coverage-v8": "
|
|
172
|
+
"@vitest/coverage-v8": "5.0.0",
|
|
171
173
|
"fast-check": "^4.9.0",
|
|
172
174
|
"istanbul-lib-coverage": "3.2.2",
|
|
173
175
|
"istanbul-lib-report": "3.0.1",
|
|
174
176
|
"istanbul-reports": "3.2.0",
|
|
175
177
|
"sigstore": "5.0.0",
|
|
178
|
+
"tar": "7.5.22",
|
|
176
179
|
"typescript": "^7.0.2",
|
|
177
180
|
"vite": "8.2.2",
|
|
178
|
-
"vitest": "^
|
|
181
|
+
"vitest": "^5.0.0"
|
|
179
182
|
},
|
|
180
183
|
"engines": {
|
|
181
184
|
"node": ">=22"
|
|
@@ -1,7 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,43 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/archive-tar-gnu.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,47 +0,0 @@
|
|
|
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,34 +0,0 @@
|
|
|
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
|
-
}
|
|
28
|
-
export declare function preflightTarMetadata(params: {
|
|
29
|
-
archivePath: string;
|
|
30
|
-
limits: TarMeterLimits;
|
|
31
|
-
signal?: AbortSignal;
|
|
32
|
-
onMember?: (entry: TarEntryInfo) => void;
|
|
33
|
-
}): Promise<void>;
|
|
34
|
-
//# sourceMappingURL=archive-tar-meta.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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,CAahB"}
|