@openclaw/fs-safe 0.15.0 → 0.16.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 +38 -0
- package/README.md +28 -6
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +6 -7
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +5 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +2 -5
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +11 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +2 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +3 -2
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +173 -28
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +19 -3
- package/dist/native-policy-parent-windows.d.ts.map +1 -1
- package/dist/native-policy-parent-windows.js +15 -6
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +86 -39
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +3 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/replace-file-temp-owner.d.ts +0 -7
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +3 -54
- package/dist/root-create-input.d.ts +2 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +13 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +28 -7
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +22 -13
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-write-publication.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -1
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +3 -1
- package/docs/archive.md +61 -37
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +2 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/install.md +31 -7
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +22 -9
- package/docs/native.md +38 -7
- package/docs/permissions.md +37 -14
- package/docs/root.md +34 -0
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +5 -4
- package/docs/staged-file.md +5 -0
- package/docs/writing.md +71 -5
- package/package.json +18 -15
package/docs/archive.md
CHANGED
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
`@openclaw/fs-safe/archive` extracts ZIP and TAR archives behind one API, with traversal checks, blocked-link-type rejection, and entry-count and byte budgets. When the native binding is available for the current platform, Rust streams ZIP, TAR, gzip, zstd, and bzip2 while TypeScript remains the sole policy owner; every accepted output is created fd-relative in a private staging root. Extraction then merges through the same safe-open boundary used by direct writes — a symlinked entry can't trick the merge into following an out-of-tree path.
|
|
4
4
|
|
|
5
5
|
TAR admission uses one Rust core compiled into both the native binding and a
|
|
6
|
-
bundled, import-free WebAssembly module.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
TAR
|
|
6
|
+
bundled, import-free WebAssembly module. In `off`, or `auto` when the native
|
|
7
|
+
binding is unavailable, extraction and bounded entry reads use that module for
|
|
8
|
+
plain TAR, gzip, zstd, and bzip2. Zstd and bzip2 use bundled WASM builds of the
|
|
9
|
+
same codec implementations used by native; gzip uses Node's built-in decoder.
|
|
10
|
+
These TAR routes work with all optional dependencies omitted and need no
|
|
11
|
+
runtime interpreter, download, install script, or consumer compiler toolchain.
|
|
12
|
+
ZIP fallback still requires optional `jszip`.
|
|
13
|
+
|
|
14
|
+
`auto` prefers an available native binding; a native operation failure is
|
|
15
|
+
terminal and never retries through WASM. `require` rejects a missing binding
|
|
16
|
+
with `FsSafeError("helper-unavailable")`, including for zstd/bzip2 suffix
|
|
17
|
+
resolution. The separate `inspectTarArchive()` API still accepts only plain TAR
|
|
18
|
+
and gzip.
|
|
11
19
|
|
|
12
20
|
```ts
|
|
13
21
|
import { extractArchive, resolveArchiveKind } from "@openclaw/fs-safe/archive";
|
|
@@ -188,7 +196,7 @@ collision checks, writes, and mode application agree.
|
|
|
188
196
|
|
|
189
197
|
An `entryFilter` sees the validated **canonical effective archive path before
|
|
190
198
|
stripping**, entry kind, and declared size. On every JavaScript and native
|
|
191
|
-
ZIP/TAR backend (including gzip and
|
|
199
|
+
ZIP/TAR backend (including gzip, zstd, and bzip2), backslashes become `/`,
|
|
192
200
|
empty and `.` components are removed, and trailing separators are removed from
|
|
193
201
|
directory paths. For example, `./pkg//state\cache/value` is presented as
|
|
194
202
|
`pkg/state/cache/value`, even with `stripComponents: 1`. Case and Unicode
|
|
@@ -368,14 +376,16 @@ Native gzip, zstd, and bzip2 readers check cancellation before refilling
|
|
|
368
376
|
compressed input and before each decoded read, including buffered output. These checks
|
|
369
377
|
apply to file extraction and in-memory member reads; they cannot interrupt an
|
|
370
378
|
already-running filesystem read or a decoder step using already-buffered input.
|
|
379
|
+
Portable zstd/bzip2 decoding checks cancellation between bounded codec steps and
|
|
380
|
+
periodically yields to the event loop, including while consuming output-free
|
|
381
|
+
members. An individual WASM call cannot be interrupted. Teardown joins the input,
|
|
382
|
+
parser, and any Node decoder streams before disposing their shared WASM state.
|
|
371
383
|
|
|
372
384
|
### Raw TAR framing
|
|
373
385
|
|
|
374
386
|
Extraction and bounded reads admit the complete decoded TAR stream through the
|
|
375
|
-
shared Rust core. This applies to plain TAR,
|
|
376
|
-
|
|
377
|
-
or fallback policy. The native and WASM builds enforce the same
|
|
378
|
-
framing rules:
|
|
387
|
+
shared Rust core. This applies to plain TAR, gzip, zstd, and bzip2 on native and
|
|
388
|
+
fallback paths. The native and WASM builds enforce the same framing rules:
|
|
379
389
|
|
|
380
390
|
- Every nonzero header must have a valid unsigned octal checksum, delimited
|
|
381
391
|
within its field. Checksum validation precedes metadata allocation and member
|
|
@@ -420,14 +430,24 @@ returning selected bytes. Unrequested, filtered, and stripped members cannot
|
|
|
420
430
|
bypass validation. Decompression remains streaming; no complete decoded archive
|
|
421
431
|
is retained in memory or written to a decoded spool.
|
|
422
432
|
|
|
423
|
-
The WASM transport has
|
|
424
|
-
and a 256 MiB maximum linear memory per isolated parser
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
bounded before allocation; allocation
|
|
428
|
-
bounds queued chunks, and completion
|
|
429
|
-
|
|
430
|
-
|
|
433
|
+
The WASM transport has fixed 64 KiB input/output windows, one pending member
|
|
434
|
+
event, and a 256 MiB maximum linear memory per isolated session. The parser and
|
|
435
|
+
portable zstd/bzip2 decoder share that session and memory ceiling. JavaScript
|
|
436
|
+
gzip decoding also emits chunks of at most 64 KiB for both staged files and
|
|
437
|
+
buffered inputs. Metadata is bounded before allocation; codec allocation
|
|
438
|
+
failure rejects. Stream backpressure bounds queued chunks, and completion or
|
|
439
|
+
error releases the session's parser and decoder state after stream teardown.
|
|
440
|
+
The manifest retains the existing charged budget below; linear memory is an
|
|
441
|
+
additional execution resource bound, not a new public limit option.
|
|
442
|
+
|
|
443
|
+
Portable zstd/bzip2 decoding consumes every concatenated member through physical
|
|
444
|
+
EOF and verifies container integrity, including available checksums. Zstd
|
|
445
|
+
skippable frames are consumed without becoming TAR data. Truncated members and
|
|
446
|
+
trailing non-container bytes reject with `ArchiveFormatError` before filters,
|
|
447
|
+
publication, or selected bytes are returned. Decoded TAR EOF and byte-budget
|
|
448
|
+
checks still apply across member boundaries; a second TAR after EOF is not
|
|
449
|
+
silently ignored. The gzip-only compressed-padding policy above does not extend
|
|
450
|
+
to zstd/bzip2 containers.
|
|
431
451
|
|
|
432
452
|
The raw meter enforces `maxEntries` before consuming each logical member's body,
|
|
433
453
|
including members later skipped by filtering or stripping. PAX/GNU metadata
|
|
@@ -524,7 +544,7 @@ parsers from disagreeing about a member's type.
|
|
|
524
544
|
`K` validates encoding and NUL structure without authorizing link creation.
|
|
525
545
|
Normal link/filter policy still governs the described member. Canonical
|
|
526
546
|
pre-strip filter paths, decoded-stream ceilings, and physical EOF checks apply
|
|
527
|
-
to plain/gzip TAR and
|
|
547
|
+
to plain/gzip TAR and zstd/bzip2 alike.
|
|
528
548
|
|
|
529
549
|
## `inspectTarArchive`
|
|
530
550
|
|
|
@@ -590,7 +610,7 @@ import { resolveArchiveKind, type ArchiveKind } from "@openclaw/fs-safe/archive"
|
|
|
590
610
|
|
|
591
611
|
const kind = resolveArchiveKind("upload.zip"); // "zip"
|
|
592
612
|
const tar = resolveArchiveKind("upload.tar.gz"); // "tar"
|
|
593
|
-
const zstd = resolveArchiveKind("upload.tar.zst"); // "tar-zstd"
|
|
613
|
+
const zstd = resolveArchiveKind("upload.tar.zst"); // "tar-zstd" in auto/off; require checks native
|
|
594
614
|
const unknown = resolveArchiveKind("upload.bin"); // null
|
|
595
615
|
```
|
|
596
616
|
|
|
@@ -598,17 +618,18 @@ Recognizes:
|
|
|
598
618
|
|
|
599
619
|
- `*.zip` → `"zip"`
|
|
600
620
|
- `*.tar`, `*.tar.gz`, `*.tgz` → `"tar"`
|
|
601
|
-
- `*.tar.zst`, `*.tar.zstd`, `*.tzst` → `"tar-zstd"`
|
|
602
|
-
- `*.tar.bz2`, `*.tbz2`, `*.tbz` → `"tar-bzip2"`
|
|
621
|
+
- `*.tar.zst`, `*.tar.zstd`, `*.tzst` → `"tar-zstd"`
|
|
622
|
+
- `*.tar.bz2`, `*.tbz2`, `*.tbz` → `"tar-bzip2"`
|
|
603
623
|
|
|
604
624
|
Returns `null` for unknown extensions; check the result before calling
|
|
605
|
-
`extractArchive` if the filename is caller-controlled.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
625
|
+
`extractArchive` if the filename is caller-controlled. Recognized zstd and bzip2
|
|
626
|
+
TAR extensions resolve in `auto` and `off` even without a native binding, using
|
|
627
|
+
the bundled codecs for subsequent extraction or reads. Explicit `require`
|
|
628
|
+
still checks native availability during suffix resolution and throws
|
|
629
|
+
`FsSafeError("helper-unavailable")` when the binding cannot load.
|
|
609
630
|
|
|
610
|
-
For a
|
|
611
|
-
the first archive call so a
|
|
631
|
+
For a deployment that requires native archive processing, configure native mode
|
|
632
|
+
before the first archive call so a missing binding fails at the boundary:
|
|
612
633
|
|
|
613
634
|
```ts
|
|
614
635
|
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
@@ -627,8 +648,10 @@ await extractArchive({
|
|
|
627
648
|
|
|
628
649
|
`readArchiveEntry(archivePath, entryPath, { maxBytes, kind? })` reads one
|
|
629
650
|
regular-file entry into a bounded `Buffer` without extracting a tree. It reads
|
|
630
|
-
the input through an identity-checked descriptor, rejects
|
|
631
|
-
|
|
651
|
+
the input through an identity-checked descriptor, rejects a requested link or
|
|
652
|
+
directory, and rejects duplicate entry names anywhere in the archive. Unrequested
|
|
653
|
+
links and directories do not prevent reading a regular file; no links are followed
|
|
654
|
+
or created. It verifies ZIP CRC and declared size,
|
|
632
655
|
and throws `ArchiveLimitError` if the requested entry's output exceeds
|
|
633
656
|
`maxBytes`. ZIP output within that cap must match the declared uncompressed
|
|
634
657
|
size exactly; either a shorter or longer payload throws
|
|
@@ -641,7 +664,9 @@ plus the 768 MiB decoded ceiling derived from default extracted/archive byte
|
|
|
641
664
|
limits. It does not apply payload budgets to unrequested members. ZIP
|
|
642
665
|
inputs retain the archive subpath's 256 MiB compressed-input ceiling.
|
|
643
666
|
With a native binding it uses the same Rust decoders as extraction, including
|
|
644
|
-
zstd and bzip2 TAR. Without native
|
|
667
|
+
zstd and bzip2 TAR. Without native, the guarded fallback uses bundled WASM for
|
|
668
|
+
TAR admission and zstd/bzip2 decoding, Node gunzip for gzip, and optional JSZip
|
|
669
|
+
for ZIP. Native `require` still rejects an unavailable binding.
|
|
645
670
|
Archive member reads retain their private in-memory input without a disk
|
|
646
671
|
snapshot. JavaScript ZIP member reads reuse their completed physical admission
|
|
647
672
|
when loading the decoder, which still checks its decoded names and entry count.
|
|
@@ -652,12 +677,11 @@ allocation without another copy where external buffers are supported.
|
|
|
652
677
|
Native TAR retains the fully admitted member offsets alongside the same input
|
|
653
678
|
allocation. Plain TAR copies only the selected payload range after full archive
|
|
654
679
|
validation. Gzip, zstd, and bzip2 replay bounded decompression and still validate
|
|
655
|
-
all framing, trailers, and physical padding before returning. The
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
require copies.
|
|
680
|
+
all framing, trailers, and physical padding before returning. The fallback also
|
|
681
|
+
retains admitted member offsets. After full admission, plain TAR copies the
|
|
682
|
+
selected range directly from its private snapshot; gzip, zstd, and bzip2 replay
|
|
683
|
+
bounded decompression through the same parser. WASM transport and selected
|
|
684
|
+
output use owned copies, so reusable codec windows cannot escape to callers.
|
|
661
685
|
Returned buffers own their bytes, so changing a result cannot modify an archive
|
|
662
686
|
reader or retain an unrelated part of the input through its backing ArrayBuffer.
|
|
663
687
|
|
package/docs/config.md
CHANGED
|
@@ -37,10 +37,14 @@ Set the process-global loading policy. Configure once at startup, before the fir
|
|
|
37
37
|
|
|
38
38
|
| Mode | Behavior |
|
|
39
39
|
|---|---|
|
|
40
|
-
| `auto` | Default. Prefer the platform binding and use
|
|
41
|
-
| `off` | Do not load the binding; use
|
|
40
|
+
| `auto` | Default. Prefer the platform binding and use supported fallbacks when it is unavailable. |
|
|
41
|
+
| `off` | Do not load the binding; use supported fallbacks and reject native-only operations. |
|
|
42
42
|
| `require` | Operations that need the binding raise `FsSafeError("helper-unavailable")` when it cannot load. |
|
|
43
43
|
|
|
44
|
+
Fallbacks include guarded JavaScript, the bundled TAR/gzip WASM parser, and the
|
|
45
|
+
[packaged Windows security scripts](install.md#windows-security-fallback).
|
|
46
|
+
Windows command fallbacks remain subject to normal system execution policy.
|
|
47
|
+
|
|
44
48
|
## `getFsSafeNativeConfig()`
|
|
45
49
|
|
|
46
50
|
```ts
|
package/docs/contributing.md
CHANGED
|
@@ -19,10 +19,50 @@ manager version declared in `package.json`.
|
|
|
19
19
|
pnpm build
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Runs TypeScript compilation and builds the portable Rust TAR parser
|
|
23
|
-
`wasm32-unknown-unknown`. Contributors need Rust (the
|
|
24
|
-
minimum or newer)
|
|
25
|
-
|
|
22
|
+
Runs TypeScript compilation and builds the portable Rust TAR parser and its
|
|
23
|
+
bzip2/zstd codecs for `wasm32-unknown-unknown`. Contributors need Rust (the
|
|
24
|
+
native crate's declared minimum or newer), `rustup target add
|
|
25
|
+
wasm32-unknown-unknown`, and LLVM's WebAssembly-capable `clang` and `llvm-ar`.
|
|
26
|
+
The system's native `ar` is not sufficient. `pnpm archive:wasm` rebuilds just
|
|
27
|
+
the portable module.
|
|
28
|
+
|
|
29
|
+
Linux, macOS, and Windows CI use the same pinned WASI SDK 34 LLVM toolchain;
|
|
30
|
+
Alpine uses its versioned LLVM 22 packages alongside `rust-wasm`. For local
|
|
31
|
+
builds, install LLVM through your package manager or use the official
|
|
32
|
+
[WASI SDK](https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-34).
|
|
33
|
+
On macOS, `brew install llvm` supplies the archiver missing from Apple's
|
|
34
|
+
Command Line Tools. On Windows, install the LLVM distribution with both
|
|
35
|
+
`clang.exe` and `llvm-ar.exe`. On Linux, install the matching `clang` and
|
|
36
|
+
`llvm` packages; a GCC-only build toolchain cannot compile these WASM codecs.
|
|
37
|
+
|
|
38
|
+
The build discovers tools on `PATH`, in `LLVM_PATH/bin`, in Homebrew's LLVM
|
|
39
|
+
prefixes, and in Windows' standard LLVM installation. It also checks the
|
|
40
|
+
versioned `clang-18` through `clang-21` and `llvm-ar-18` through `llvm-ar-21`
|
|
41
|
+
executables. To select another installation explicitly, set
|
|
42
|
+
`CC_wasm32_unknown_unknown` and `AR_wasm32_unknown_unknown` to its compiler
|
|
43
|
+
and archiver. The corresponding hyphenated target variables and cc-rs's
|
|
44
|
+
`TARGET_CC`/`TARGET_AR` or `CC`/`AR` overrides are also respected; an unusable
|
|
45
|
+
explicit override fails with a builder diagnostic instead of being ignored.
|
|
46
|
+
Windows build environment names are case-insensitive, including when worker
|
|
47
|
+
processes uppercase them. The build normalizes only its copied child environment.
|
|
48
|
+
Clang's implicit configuration is disabled for this target so the WASI SDK's
|
|
49
|
+
default libc/sysroot cannot leak into the import-free module. These settings
|
|
50
|
+
affect compilation only and do not become runtime dependencies.
|
|
51
|
+
|
|
52
|
+
The build disables release LTO only in the WASM Cargo subprocess. An observed
|
|
53
|
+
Rust 1.98.1 optimized-WASM-LTO allocation/free failure makes that necessary;
|
|
54
|
+
the native release profile stays unchanged. The WASM linker strips debug
|
|
55
|
+
sections to keep the bundled module small without stripping native binaries.
|
|
56
|
+
The build verifies zero host
|
|
57
|
+
imports and one unshared 32-bit memory with the existing 256 MiB maximum
|
|
58
|
+
before copying the artifact. Allocator regression tests build a separate
|
|
59
|
+
instrumented module with `pnpm archive:wasm:allocator-tests` under the Cargo
|
|
60
|
+
target directory. That module is never copied to `dist/` or packaged. `pnpm
|
|
61
|
+
check` and coverage collection build it explicitly before testing. After a
|
|
62
|
+
fresh checkout, run that command before `pnpm test`, `pnpm test:coverage`, or
|
|
63
|
+
focused `test/archive-codec-wasm-allocator.test.ts` runs; the tests fail if
|
|
64
|
+
their prerequisite artifact is missing.
|
|
65
|
+
|
|
26
66
|
The import-free asset lands at `dist/archive-parser.wasm`; source tests and
|
|
27
67
|
compiled consumers both resolve that generated artifact. Run `pnpm build`
|
|
28
68
|
before source tests in a fresh checkout. Do not commit `dist/` or built WASM.
|
package/docs/copy.md
CHANGED
|
@@ -78,6 +78,8 @@ XFS and ZFS preserve regular-file and directory modes, timestamps, extended attr
|
|
|
78
78
|
|
|
79
79
|
`readCloneFileMetadata(files)` asynchronously reads APFS data-stream identities and file metadata in one native batch. Results correspond to input order; missing or unsupported entries return `undefined`. The returned `CloneFileMetadata` includes clone ID, device/inode, size, mode, ownership, and timestamps. These are point-in-time observations, not authorization or proof that later reads remain unchanged. Consumers such as Git index adapters must validate their own content and timestamp invariants. The reader does not follow leaf symbolic links.
|
|
80
80
|
|
|
81
|
+
All input paths must be absolute and valid before native availability is checked. On platforms other than macOS, `auto` and `off` can return one `undefined` per path without the addon, matching native's unsupported result. On macOS, `off` or an unavailable addon still rejects with `helper-unavailable`; JavaScript cannot supply APFS clone IDs. Explicit `require` mode rejects an unavailable addon on every platform, including for an empty batch. Errors from a loaded native helper remain terminal.
|
|
82
|
+
|
|
81
83
|
## Borrowed FileHandle transfers
|
|
82
84
|
|
|
83
85
|
`copyFileHandle` from `@openclaw/fs-safe/advanced` copies bytes between two
|
package/docs/creation.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Exclusive leaf creation
|
|
2
|
+
|
|
3
|
+
The creation-policy work is tracked in [#482](https://github.com/openclaw/fs-safe/issues/482).
|
|
4
|
+
|
|
5
|
+
Use `createDirectory()`, `createDirectorySync()`, and `createFileSync()` from
|
|
6
|
+
`@openclaw/fs-safe/advanced` when an existing, trusted parent should receive
|
|
7
|
+
one new entry. These operations are exclusive and nonrecursive: an existing
|
|
8
|
+
entry throws `FsSafeError("already-exists")`, and missing parents are not
|
|
9
|
+
created. They do not repair or adopt an existing destination. `Root.mkdir()`
|
|
10
|
+
continues to own recursive, idempotent directory creation within a Root.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import {
|
|
14
|
+
createDirectorySync,
|
|
15
|
+
createFileSync,
|
|
16
|
+
} from "@openclaw/fs-safe/advanced";
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
|
|
19
|
+
createDirectorySync("/trusted/application/new-state", { private: true });
|
|
20
|
+
using file = createFileSync("/trusted/application/new-state/initial.db", {
|
|
21
|
+
private: true,
|
|
22
|
+
});
|
|
23
|
+
fs.fsyncSync(file.fd);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Both directory variants return `void`; `createFileSync()` returns an empty,
|
|
27
|
+
read/write descriptor owned by `OwnedFileDescriptorSync`, with
|
|
28
|
+
`{ fd, close(), [Symbol.dispose]() }`. Use the
|
|
29
|
+
owner to close it, rather than calling `fs.closeSync()` yourself. Closing and
|
|
30
|
+
disposal are idempotent; a failed close is not retried through a descriptor
|
|
31
|
+
number that might already have been reused. File creation does not write
|
|
32
|
+
payload data or request file or parent-directory synchronization.
|
|
33
|
+
|
|
34
|
+
## Permission options
|
|
35
|
+
|
|
36
|
+
`CreateDirectoryOptions` and `CreateFileOptions` both support `private?: boolean`
|
|
37
|
+
for private creation. `mode?: number` selects permission
|
|
38
|
+
bits; invalid values and private requests with group/world or special bits
|
|
39
|
+
reject before mutation. Restrictive owner-only modes remain restrictive.
|
|
40
|
+
Without `private`, ordinary Node defaults and the process umask apply. Private
|
|
41
|
+
POSIX creation requests `0700` for directories and `0600` for files by default;
|
|
42
|
+
the umask may restrict those permissions further. Existing directory privacy
|
|
43
|
+
checks never broaden permissions.
|
|
44
|
+
|
|
45
|
+
Native private file creation checks the retained descriptor's actual owner and
|
|
46
|
+
permissions before writing payload bytes, after producer and authority callbacks,
|
|
47
|
+
and at publication. A successful `chmod` is insufficient: filesystems that do not
|
|
48
|
+
enforce owner-only permissions reject before payload writes. The requested final
|
|
49
|
+
mode is verified too; a failure after publication preserves the completed file
|
|
50
|
+
and reports its published outcome.
|
|
51
|
+
|
|
52
|
+
On macOS (Darwin), private creation also requires an ACL-free result. The native
|
|
53
|
+
helper must provide `inspectDarwinAcl`; native `off`, a missing helper, or an
|
|
54
|
+
older helper without that capability rejects with `helper-unavailable` before
|
|
55
|
+
creating parents or staging entries. There is no system-command fallback for
|
|
56
|
+
Darwin private creation. Operations without `private: true` keep their existing
|
|
57
|
+
native-mode behavior.
|
|
58
|
+
|
|
59
|
+
Private Darwin directories must retain owner-read or owner-search permission
|
|
60
|
+
after applying the umask so their ACL can be inspected. Modes `0000` and `0200`
|
|
61
|
+
reject with `helper-unavailable` before directory creation. Existing private
|
|
62
|
+
directories with neither permission also reject with `helper-unavailable`;
|
|
63
|
+
permissions are never broadened to inspect them. Modes `0100`, `0300`, and `0400`
|
|
64
|
+
remain supported, as does the default `0700`. Private files with mode `0000`
|
|
65
|
+
remain supported because inspection uses the owned creation descriptor.
|
|
66
|
+
|
|
67
|
+
Before creation, the parent ACL is inspected for entries that could be inherited
|
|
68
|
+
by the new directory or file, as applicable. Relevant inheritable entries reject
|
|
69
|
+
creation. Noninheriting parent ACLs, such as the usual macOS home-directory
|
|
70
|
+
deny-delete entry, do not reject child creation. Created directories and files
|
|
71
|
+
are checked for owner-only permissions and no ACL before admitting the directory
|
|
72
|
+
or allowing payload writes. An existing directory requested with `private: true`
|
|
73
|
+
must also be owned by the current user, have owner-only permissions, and have no
|
|
74
|
+
ACL. These checks never clear an ACL after creation or repair an existing entry.
|
|
75
|
+
|
|
76
|
+
On Windows, mode bits alone do not establish privacy. Private creation uses a
|
|
77
|
+
protected current-user, LocalSystem and Administrators DACL. A private staging
|
|
78
|
+
directory supplies trusted-only inheritable permissions before Node creates
|
|
79
|
+
the file. The original Node descriptor stays pinned while its full Windows
|
|
80
|
+
identity is compared with a security handle before the file DACL is protected.
|
|
81
|
+
An already broadly accessible file is rejected, not repaired. Keep the trusted
|
|
82
|
+
parent ancestry and staging directory ACL protected from untrusted changes;
|
|
83
|
+
post-operation checks do not make pathname-based fallback operations atomic
|
|
84
|
+
against an adversary who can change that namespace.
|
|
85
|
+
|
|
86
|
+
The internal async writer awaits system commands, file opens and closes, and
|
|
87
|
+
security verification. It does not wrap the synchronous creator in a promise.
|
|
88
|
+
Short identity checks and the existing guarded link/unlink critical sections
|
|
89
|
+
remain synchronous.
|
|
90
|
+
|
|
91
|
+
Private Windows file publication retains the same inode and never overwrites
|
|
92
|
+
an existing destination. The current implementation requires hardlinks on the same
|
|
93
|
+
local filesystem. Unsupported filesystems reject with `helper-unavailable`;
|
|
94
|
+
there is no copy-to-destination fallback. On Windows the owner overlaps a
|
|
95
|
+
verified destination descriptor with the creation descriptor before removing
|
|
96
|
+
the temporary name. Requested read-only attributes are finalized through the
|
|
97
|
+
retained destination descriptor after that handoff.
|
|
98
|
+
|
|
99
|
+
On Windows, native `auto` uses available capabilities; native `off` and
|
|
100
|
+
missing-capability `auto` use the packaged system-command security bridge. Native `require`
|
|
101
|
+
rejects unavailable required capabilities instead of starting a command. The
|
|
102
|
+
private-file capability check runs before creating parents or staging entries;
|
|
103
|
+
directory-only operations require only their directory capabilities. The
|
|
104
|
+
[Windows security fallback prerequisites](install.md#windows-security-fallback)
|
|
105
|
+
apply. All command mutations report unconfirmed outcomes when their reply or
|
|
106
|
+
termination cannot establish completion.
|
|
107
|
+
|
|
108
|
+
## Authority and failure outcomes
|
|
109
|
+
|
|
110
|
+
`assertBeforeMutation?: () => void` is a synchronous current-authority check.
|
|
111
|
+
It runs after preparation and immediately before creation or publication;
|
|
112
|
+
thenables reject before mutation. Parent and file identity checks are repeated
|
|
113
|
+
after the callback. Final permission checks, descriptor settlement and cleanup
|
|
114
|
+
retain the operation's cleanup ownership after publication.
|
|
115
|
+
|
|
116
|
+
Failure does not always mean the final path is absent. Private-file errors
|
|
117
|
+
after publication or ambiguous publication preserve the destination and carry
|
|
118
|
+
`details.publication.status` (`published` or `indeterminate`), the target path and
|
|
119
|
+
staging cleanup outcome. Cleanup and close failures retain the original error
|
|
120
|
+
in the cause chain. When `stageDirectory` is present, `cleanup` describes that
|
|
121
|
+
stage's settlement; it does not mean the published destination was removed.
|
|
122
|
+
If a directory is created but its subsequent admission fails, the error records
|
|
123
|
+
its published path and preserves it. A private file whose staging directory
|
|
124
|
+
cannot be admitted reports `not-published` and identifies the preserved stage.
|
|
125
|
+
Observed replacement paths are preserved. Existing
|
|
126
|
+
`createPrivateDirectory()` retains its Windows-only compatibility contract;
|
|
127
|
+
new portable callers should use the ordinary creation operations with
|
|
128
|
+
`private: true`.
|
package/docs/durability.md
CHANGED
|
@@ -69,6 +69,26 @@ descriptor, pathname identity, and canonical path. `assertCurrent()` repeats
|
|
|
69
69
|
those checks. This prevents a pathname replacement from turning a later sync
|
|
70
70
|
into proof for a different directory.
|
|
71
71
|
|
|
72
|
+
Pathname and descriptor checks compare exact bigint device and inode values.
|
|
73
|
+
Each inspection allows one retry for unknown Windows identity components,
|
|
74
|
+
retaining known components and rejecting definite mismatches immediately.
|
|
75
|
+
Persistent unknown identity fails closed with `path-mismatch`, including on an
|
|
76
|
+
otherwise usable directory. A failed preflight identity check never syncs the
|
|
77
|
+
descriptor; a replacement discovered after sync still rejects the operation.
|
|
78
|
+
|
|
79
|
+
`DirectoryReceipt.identity` remains a numeric Node `Stats` object for metadata
|
|
80
|
+
compatibility, projected from the same exact observation as the private
|
|
81
|
+
identity. Library-created receipts and their identity objects retain a
|
|
82
|
+
private exact snapshot; mutating their public fields cannot change the
|
|
83
|
+
directory authorized by that snapshot. Each returned receipt owns a mutable
|
|
84
|
+
numeric metadata copy. Later admissions retain the original metadata snapshot
|
|
85
|
+
even if advisory fields such as mode or timestamps were edited; changed paths
|
|
86
|
+
or identity components reject. Pass the receipt or its original
|
|
87
|
+
identity object through to later operations to retain this evidence. A copied
|
|
88
|
+
or reconstructed numeric identity is accepted only when both components are
|
|
89
|
+
safe integers and, on Windows, nonzero. Rounded or unknown caller identities
|
|
90
|
+
fail with `path-mismatch` rather than authorizing a different directory.
|
|
91
|
+
|
|
72
92
|
Call `close()` in `finally`. Closing is idempotent; using a closed pin fails.
|
|
73
93
|
|
|
74
94
|
These checks intentionally reject a moved or replaced pathname. For one file's
|
|
@@ -94,6 +114,10 @@ accepted.
|
|
|
94
114
|
`expectedExistingIdentity` binds an existing target to an identity observed by
|
|
95
115
|
the caller before a separate permission or policy check. A missing or replaced
|
|
96
116
|
target fails with `FsSafeError("path-mismatch")`.
|
|
117
|
+
Use bigint `dev` and `ino` from `lstat(path, { bigint: true })` or
|
|
118
|
+
[`readDirectoryIdentity()`](directory-identity.md) for caller-owned observations
|
|
119
|
+
that may exceed the numeric safe-integer range. An original library receipt's
|
|
120
|
+
`identity` object also retains its private exact identity for this option.
|
|
97
121
|
|
|
98
122
|
## Exclusive file publication
|
|
99
123
|
|
package/docs/install.md
CHANGED
|
@@ -119,22 +119,46 @@ Use the main entry for the common surface, or the focused subpaths when you want
|
|
|
119
119
|
|
|
120
120
|
## Runtime dependencies
|
|
121
121
|
|
|
122
|
-
`@openclaw/fs-safe` bundles an import-free WASM build of its Rust TAR parser for guarded
|
|
122
|
+
`@openclaw/fs-safe` bundles an import-free WASM build of its Rust TAR parser and zstd/bzip2 codecs for guarded [archive extraction and bounded entry reads](archive.md). Plain TAR, gzip, zstd, and bzip2 work in `off` and missing-native `auto`, including installs with all optional dependencies omitted; gzip uses Node's built-in decoder. These archive fallbacks need no runtime interpreter, download, or consumer compiler. 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. Native `require` remains strict, and an available native operation's failure never triggers a WASM retry.
|
|
123
123
|
|
|
124
124
|
There are no peer dependencies. Exact-version optional packages carry the seven
|
|
125
125
|
native targets and npm-compatible OS, CPU, and Linux libc filters install only
|
|
126
|
-
the matching binary. Consumers do not run a
|
|
126
|
+
the matching binary. Consumers do not run a Rust build, download code at
|
|
127
127
|
runtime, or execute a postinstall step. Omitting optional dependencies keeps
|
|
128
|
-
|
|
128
|
+
fallback-capable operations working in `auto` or `off`. Native-only
|
|
129
129
|
features, including strict owned-tree temp cleanup, retained-directory staging,
|
|
130
|
-
atomic `rename-noreplace` (including the default no-clobber `Root.move()`),
|
|
131
|
-
|
|
132
|
-
unavailable. Operations without a safe fallback fail with `helper-unavailable`
|
|
130
|
+
and atomic `rename-noreplace` (including the default no-clobber `Root.move()`),
|
|
131
|
+
remain unavailable. Operations without a safe fallback fail with `helper-unavailable`
|
|
133
132
|
when the matching package is absent, incompatible, or disabled.
|
|
134
133
|
|
|
135
134
|
Upgrading an existing 0.5 consumer? Follow [Migrating to 0.6](migrating-to-0.6.md)
|
|
136
135
|
before deploying with native mode `require` or native-only features.
|
|
137
136
|
|
|
137
|
+
### Windows security fallback
|
|
138
|
+
|
|
139
|
+
Windows raw owner/DACL inspection, private-directory creation, and secure-file
|
|
140
|
+
reads work without the addon in `auto` or `off` mode when system Windows
|
|
141
|
+
PowerShell and its .NET `Add-Type` compilation support are available. The package
|
|
142
|
+
ships a readable, fixed `.ps1` driver and adjacent `.cs` source and invokes the
|
|
143
|
+
driver with Windows PowerShell `-File`. Paths are passed as data. The fallback
|
|
144
|
+
does not generate helper scripts at runtime or use an encoded launcher.
|
|
145
|
+
The driver addresses built-in commands by module name and limits module
|
|
146
|
+
discovery to PowerShell's bundled system modules, avoiding broad command
|
|
147
|
+
discovery scans during each helper startup.
|
|
148
|
+
|
|
149
|
+
Normal PowerShell execution policy and Microsoft Defender policy must permit
|
|
150
|
+
the packaged scripts, including their use of `Add-Type`. The package does not
|
|
151
|
+
bypass restrictions, change policies, or add exclusions. Unsupported or
|
|
152
|
+
disallowed command execution fails closed.
|
|
153
|
+
|
|
154
|
+
The fallback preserves private DACLs at creation and inspects the same open
|
|
155
|
+
handle that supplies secure-file bytes. Each capability emits a path-free
|
|
156
|
+
`FS_SAFE_NATIVE_FALLBACK` warning once per process; each call adds PowerShell
|
|
157
|
+
startup and compilation overhead. Execution or compilation failure also fails
|
|
158
|
+
closed. Native `require` still rejects a missing binding or capability, and an
|
|
159
|
+
available native operation's error never triggers a command retry. See
|
|
160
|
+
[Permissions](permissions.md) and [Secure file reads](secure-file.md).
|
|
161
|
+
|
|
138
162
|
## Native helper policy
|
|
139
163
|
|
|
140
164
|
The platform native binaries provide fd-relative open/link/mkdir primitives,
|
|
@@ -147,7 +171,7 @@ where a safe fallback exists. Native-only operations fail with
|
|
|
147
171
|
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
148
172
|
|
|
149
173
|
configureFsSafeNative({ mode: "auto" }); // default
|
|
150
|
-
configureFsSafeNative({ mode: "off" }); //
|
|
174
|
+
configureFsSafeNative({ mode: "off" }); // disable the addon; reject native-only operations
|
|
151
175
|
configureFsSafeNative({ mode: "require" }); // fail closed if unavailable
|
|
152
176
|
```
|
|
153
177
|
|
package/docs/migrating-to-0.5.md
CHANGED
|
@@ -88,8 +88,10 @@ await extractArchive({
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
Returning `"skip"` rejects the archive unless `onFiltered: "skip-entry"` is
|
|
91
|
-
explicit. Zstd and bzip2 TAR
|
|
92
|
-
|
|
91
|
+
explicit. Zstd and bzip2 TAR required native support in version 0.5; current
|
|
92
|
+
versions also use bundled WASM codecs in `off` or missing-native `auto`, through
|
|
93
|
+
the same guarded TAR pipeline. ZIP fallback still requires optional JSZip.
|
|
94
|
+
Catch `ArchiveLimitError` by its code, including
|
|
93
95
|
`archive-entry-path-components-exceeds-limit` for deep implicit-directory
|
|
94
96
|
attacks. See [Archive extraction](archive.md).
|
|
95
97
|
|
|
@@ -167,10 +169,17 @@ See [File locks](sidecar-lock.md), [Secret files](secret-file.md), and
|
|
|
167
169
|
|
|
168
170
|
## 7. Gate native-only features
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
Version 0.5 required native support for `createPrivateDirectory()`. Current
|
|
173
|
+
releases also support a packaged PowerShell script in native `auto` and `off`
|
|
174
|
+
modes, retaining its creation-time protected DACL. Check the
|
|
175
|
+
[Windows security fallback prerequisites](install.md#windows-security-fallback)
|
|
176
|
+
before relying on this route. The API remains Windows-only, explicit native
|
|
177
|
+
`require` rejects a missing binding or capability, and native operation failures
|
|
178
|
+
remain terminal.
|
|
179
|
+
`strategy: "rename-noreplace"` remains native-only. Current zstd/bzip2 extraction
|
|
180
|
+
and bounded reads have bundled WASM fallbacks, while explicit native `require`
|
|
181
|
+
remains strict. Test unavailable native-only operations instead of assuming
|
|
182
|
+
installation always succeeds.
|
|
174
183
|
|
|
175
184
|
## 8. Run both behavior families in CI
|
|
176
185
|
|
package/docs/migrating-to-0.6.md
CHANGED
|
@@ -24,10 +24,15 @@ uses native mode `require` or any native-only feature. Version 0.5 kept its
|
|
|
24
24
|
binding in the root tarball; version 0.6 intentionally does not.
|
|
25
25
|
|
|
26
26
|
Omitting optional dependencies remains supported for fallback-capable APIs in
|
|
27
|
-
`auto` mode. It disables native-only features such as
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
`auto` mode. It disables native-only features such as retained-directory staging
|
|
28
|
+
and atomic `rename-noreplace`. Native mode `require`
|
|
29
|
+
reports `helper-unavailable` when the matching package is absent or incompatible.
|
|
30
|
+
Zstd/bzip2 TAR extraction and bounded reads required the binding in version 0.6;
|
|
31
|
+
current versions also support bundled WASM codecs in `auto` and `off` through the
|
|
32
|
+
same guarded TAR pipeline. ZIP fallback still requires optional JSZip.
|
|
33
|
+
Windows private-directory creation required the binding in version 0.6; current
|
|
34
|
+
releases also support [packaged Windows security scripts](install.md#windows-security-fallback)
|
|
35
|
+
in `auto` and `off` mode while preserving the creation-time protected DACL.
|
|
31
36
|
|
|
32
37
|
## Deployment checklist
|
|
33
38
|
|
package/docs/native-helper.md
CHANGED
|
@@ -15,7 +15,7 @@ consumer Rust build.
|
|
|
15
15
|
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
16
16
|
|
|
17
17
|
configureFsSafeNative({ mode: "auto" }); // default
|
|
18
|
-
configureFsSafeNative({ mode: "off" }); //
|
|
18
|
+
configureFsSafeNative({ mode: "off" }); // disable the addon; reject native-only operations
|
|
19
19
|
configureFsSafeNative({ mode: "require" }); // fail closed when the binding is unavailable
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -25,14 +25,22 @@ The equivalent environment variables are `FS_SAFE_NATIVE_MODE` and `OPENCLAW_FS_
|
|
|
25
25
|
|
|
26
26
|
| Mode | Behavior |
|
|
27
27
|
|---|---|
|
|
28
|
-
| `auto` | Prefer native primitives when the current platform package loads; otherwise use
|
|
29
|
-
| `off` | Do not load a native package. Use
|
|
28
|
+
| `auto` | Prefer native primitives when the current platform package loads; otherwise use supported fallbacks and reject native-only operations. |
|
|
29
|
+
| `off` | Do not load a native package. Use supported fallbacks and reject native-only operations 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
|
|
33
|
-
of the same Rust parser
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
Plain TAR, gzip, zstd, and bzip2 extraction and bounded entry reads use a bundled,
|
|
33
|
+
import-free WASM build of the same Rust TAR parser when native support is absent
|
|
34
|
+
or disabled. Zstd/bzip2 codecs are bundled alongside the parser; gzip uses Node's
|
|
35
|
+
built-in decoder. These archive fallbacks require no runtime interpreter or
|
|
36
|
+
download. `off` disables the optional native filesystem helper, not the bundled
|
|
37
|
+
WASM. `auto` prefers native and does not retry a native operation failure through
|
|
38
|
+
WASM; `require` still rejects an unavailable native binding. ZIP fallback still
|
|
39
|
+
requires optional `jszip`. `inspectTarArchive()` remains limited to plain TAR
|
|
40
|
+
and gzip.
|
|
41
|
+
|
|
42
|
+
Windows security operations can use the package's readable PowerShell/C# scripts
|
|
43
|
+
in `auto` and `off`, subject to the [Windows security fallback prerequisites](install.md#windows-security-fallback).
|
|
36
44
|
|
|
37
45
|
On Bun macOS/Linux, the [runtime path adapter](install.md#bun-runtime) uses the
|
|
38
46
|
same Rust addon for system canonicalization in `auto` and `require`. No JIT is
|
|
@@ -109,8 +117,13 @@ there. Deeper names retain guarded parent traversal.
|
|
|
109
117
|
Native primitives back create-only and replacing pinned writes, no-clobber
|
|
110
118
|
`Root.move()`, async sidecar creation, guarded publication, archive acceleration,
|
|
111
119
|
and direct Windows ACL operations. Windows secure-file reads require
|
|
112
|
-
descriptor-bound owner/DACL facts
|
|
113
|
-
|
|
120
|
+
descriptor-bound owner/DACL facts. In native `auto` or `off` mode, a missing
|
|
121
|
+
binding or capability can use a packaged PowerShell script that inspects the
|
|
122
|
+
borrowed handle. Raw owner/DACL inspection and private-directory creation also support
|
|
123
|
+
this fallback, subject to the [Windows security fallback prerequisites](install.md#windows-security-fallback).
|
|
124
|
+
Each capability emits a path-free warning once per process and adds PowerShell
|
|
125
|
+
startup and compilation overhead per call. Native `require` rejects missing
|
|
126
|
+
capabilities, and native operation failures remain terminal. No-clobber moves fail with
|
|
114
127
|
`helper-unavailable` when descriptor-relative parent admission or the atomic
|
|
115
128
|
no-replace rename is unavailable; they never use a check followed by a replacing
|
|
116
129
|
rename. Equivalent JavaScript paths remain available for documented
|