@openclaw/fs-safe 0.10.0 → 0.12.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 +114 -0
- package/LICENSE +1 -0
- package/README.md +39 -6
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +3 -2
- package/dist/advanced.d.ts +5 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +5 -1
- package/dist/archive-durability.d.ts +6 -6
- package/dist/archive-durability.d.ts.map +1 -1
- package/dist/archive-durability.js +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +8 -8
- package/dist/archive-gzip-tail.d.ts +1 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +16 -9
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +4 -2
- package/dist/archive-merge.d.ts +5 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -12
- package/dist/archive-native.js +4 -4
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +54 -42
- package/dist/archive-staging.d.ts +6 -3
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +42 -22
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +7 -6
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +16 -13
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +48 -12
- package/dist/archive-zip-loader.d.ts +6 -0
- package/dist/archive-zip-loader.d.ts.map +1 -0
- package/dist/archive-zip-loader.js +38 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +26 -9
- package/dist/archive-zip-preflight.d.ts +2 -3
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +2 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +12 -10
- package/dist/bounded-read.d.ts +5 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +18 -11
- package/dist/copy-file-input.d.ts +0 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +4 -26
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +57 -26
- package/dist/copy.d.ts +1 -1
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +3 -1
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +5 -4
- package/dist/directory-guard.d.ts +11 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +53 -11
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +1 -1
- package/dist/file-handle-transfer.d.ts +14 -0
- package/dist/file-handle-transfer.d.ts.map +1 -0
- package/dist/file-handle-transfer.js +64 -0
- package/dist/file-hash.d.ts +3 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +99 -32
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -4
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +7 -5
- package/dist/file-store-path.d.ts +3 -0
- package/dist/file-store-path.d.ts.map +1 -0
- package/dist/file-store-path.js +27 -0
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +15 -5
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +56 -44
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +2 -18
- package/dist/filename.d.ts +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +32 -14
- package/dist/guarded-mkdir.d.ts +2 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +52 -16
- package/dist/guest-dispatch-python.d.ts +2 -0
- package/dist/guest-dispatch-python.d.ts.map +1 -0
- package/dist/guest-dispatch-python.js +117 -0
- package/dist/guest-native-python.d.ts +4 -0
- package/dist/guest-native-python.d.ts.map +1 -0
- package/dist/guest-native-python.js +135 -0
- package/dist/guest.d.ts +9 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +421 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +16 -2
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue-ownership.d.ts +2 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +47 -3
- package/dist/json-durable-queue-read.d.ts +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +59 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +43 -84
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +2 -1
- package/dist/move-path-stage.d.ts.map +1 -1
- package/dist/move-path-stage.js +2 -1
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +4 -3
- package/dist/mutation-authority.d.ts +1 -0
- package/dist/mutation-authority.d.ts.map +1 -1
- package/dist/mutation-authority.js +4 -4
- package/dist/native-binding.d.ts +14 -2
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +3 -2
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +2 -1
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +5 -4
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -0
- package/dist/overwrite-file-handle.d.ts +8 -0
- package/dist/overwrite-file-handle.d.ts.map +1 -0
- package/dist/overwrite-file-handle.js +42 -0
- package/dist/path-case.d.ts +7 -0
- package/dist/path-case.d.ts.map +1 -0
- package/dist/path-case.js +136 -0
- package/dist/path-scope-lexical.d.ts +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +27 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +22 -3
- package/dist/permissions-windows.d.ts +1 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +48 -6
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +3 -1
- package/dist/pinned-write.d.ts +2 -2
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +6 -4
- package/dist/realpath.d.ts +4 -0
- package/dist/realpath.d.ts.map +1 -0
- package/dist/realpath.js +43 -0
- package/dist/recursive-mkdir-path.d.ts +3 -0
- package/dist/recursive-mkdir-path.d.ts.map +1 -0
- package/dist/recursive-mkdir-path.js +8 -0
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +2 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +23 -31
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +7 -12
- package/dist/replace-file-mode.d.ts +3 -0
- package/dist/replace-file-mode.d.ts.map +1 -0
- package/dist/replace-file-mode.js +10 -0
- package/dist/replace-file.d.ts +1 -0
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +14 -8
- package/dist/root-boundary.d.ts +25 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +177 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +40 -13
- package/dist/root-create-input.d.ts +10 -0
- package/dist/root-create-input.d.ts.map +1 -0
- package/dist/root-create-input.js +80 -0
- package/dist/root-directory-list.d.ts +3 -1
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +31 -5
- package/dist/root-entries.d.ts +11 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +61 -0
- package/dist/root-errors.d.ts +5 -5
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +13 -12
- package/dist/root-impl.d.ts +13 -3
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +93 -70
- package/dist/root-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-options.d.ts +12 -1
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-existing.d.ts +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +14 -5
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +3 -2
- package/dist/root-path.d.ts +2 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +54 -26
- package/dist/root-paths.d.ts +2 -6
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +24 -32
- package/dist/root-remove.d.ts +5 -0
- package/dist/root-remove.d.ts.map +1 -0
- package/dist/root-remove.js +286 -0
- package/dist/root-symlink-policy.d.ts +2 -1
- package/dist/root-symlink-policy.d.ts.map +1 -1
- package/dist/root-symlink-policy.js +2 -2
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- package/dist/root-write-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +21 -7
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +12 -3
- package/dist/root.d.ts +2 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -1
- package/dist/secure-file-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +27 -7
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +2 -1
- package/dist/sibling-staged-file.d.ts +2 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +49 -8
- package/dist/sibling-temp.d.ts +2 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +8 -5
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +16 -5
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +17 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +4 -3
- package/dist/temp-target.d.ts +14 -12
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +15 -7
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +7 -5
- package/dist/unicode-path.d.ts +3 -0
- package/dist/unicode-path.d.ts.map +1 -0
- package/dist/unicode-path.js +13 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +14 -12
- package/dist/write-file-handle.d.ts +1 -0
- package/dist/write-file-handle.d.ts.map +1 -1
- package/dist/write-file-handle.js +3 -2
- package/docs/advanced.md +7 -1
- package/docs/archive.md +31 -5
- package/docs/atomic.md +17 -1
- package/docs/config.md +1 -0
- package/docs/contributing.md +33 -2
- package/docs/copy.md +75 -6
- package/docs/directory-identity.md +85 -0
- package/docs/durability.md +40 -3
- package/docs/entries.md +109 -0
- package/docs/errors.md +3 -3
- package/docs/file-store.md +21 -0
- package/docs/filename.md +9 -2
- package/docs/guest.md +146 -0
- package/docs/in-place-write.md +81 -0
- package/docs/index.md +2 -0
- package/docs/install-path.md +59 -13
- package/docs/install.md +34 -0
- package/docs/native-helper.md +14 -5
- package/docs/native.md +18 -2
- package/docs/output.md +32 -6
- package/docs/path-case.md +64 -0
- package/docs/path-scope.md +1 -1
- package/docs/path.md +1 -1
- package/docs/permissions.md +37 -3
- package/docs/public-api.md +36 -2
- package/docs/root.md +33 -3
- package/docs/secure-file.md +17 -14
- package/docs/security-model.md +1 -1
- package/docs/sidecar-lock.md +12 -3
- package/docs/store.md +22 -1
- package/docs/temp.md +39 -6
- package/docs/types.md +1 -1
- package/docs/writing.md +153 -3
- package/package.json +14 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.12.0 - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- **Faster archive workloads:** reuse validated ZIP names, batch native plain-TAR reads, and reduce gzip validation and payload-buffer allocations while preserving complete archive checks.
|
|
10
|
+
- **Less overhead in everyday filesystem work:** reduce repeated path resolution, directory-walk work, small-hash allocations, Linux copy buffers, and lock-manager setup.
|
|
11
|
+
- **Stronger Windows file boundaries:** verify secure-file permissions against the descriptor supplying the bytes, retain handles through private-directory creation and cleanup, and check directory identity when Root containment relies on case folding.
|
|
12
|
+
- **Safer queue recovery and cleanup:** prevent stale migrations from replacing newer queue entries, preserve files refreshed during expiry pruning, and keep existing destinations intact when guest symlink moves fail.
|
|
13
|
+
- **Versioned install identifiers:** add `safePathSegmentHashedV2`, which hashes every trimmed ID into a fixed lowercase directory segment and avoids the deterministic aliases of the legacy readable encoder.
|
|
14
|
+
|
|
15
|
+
### Compatibility and upgrade notes
|
|
16
|
+
|
|
17
|
+
- Windows `readSecureFile()` now requires the matching current native package for descriptor-bound owner and DACL checks. Missing or stale helpers, remote handles, denied access, and unsupported or incomplete permission facts fail closed before reading; there is no pathname ACL-command fallback. Standalone permission-reporting APIs retain their documented fallbacks.
|
|
18
|
+
- Windows private-directory creation rejects explicit `.` and `..` components and components ending in spaces or periods before filesystem operations. Directory association requires the complete file identity; unsupported identity classes fail closed.
|
|
19
|
+
- Windows native descriptor operations require the host runtime's paired libuv descriptor bridge. Missing or partial bridges reject with `ENOTSUP` instead of interpreting descriptors as raw handles or using another runtime's descriptor table.
|
|
20
|
+
- `safePathSegmentHashedV2` is opt-in. Legacy encoder outputs and the default encoder remain unchanged. Switching to V2 changes existing paths: use a new base directory or explicitly migrate after verifying recorded IDs, and do not silently fall back to legacy paths.
|
|
21
|
+
|
|
22
|
+
### Archive performance and reliability
|
|
23
|
+
|
|
24
|
+
- Reuse identical local and central ZIP name validation within one entry, including matching complete Unicode Path metadata. Differing fields and shared backing memory retain independent checks; CRC, traversal, collision, and interpretation validation remain intact.
|
|
25
|
+
- Reuse raw archive path segments during validation, preserving platform rules, normalized component limits, and error ordering.
|
|
26
|
+
- Speed up native plain-TAR inspection and extraction with bounded file read-ahead, retaining complete framing, payload, trailer, and cancellation checks at parser boundaries.
|
|
27
|
+
- Fill one owned result buffer when reading admitted gzip TAR payloads in JavaScript, reducing retained decoded buffers while preserving complete validation and independent returned bytes.
|
|
28
|
+
- Validate gzip container padding with bounded buffer comparisons and one lazily allocated reusable zero window, preserving complete suffix checks, short-read handling, and cancellation cadence.
|
|
29
|
+
- Destroy and join ZIP decoder sources before rejecting bounded member reads, preventing abandoned decoders and retained archive buffers after byte-limit failures while preserving error classifications on Node 22 and newer.
|
|
30
|
+
|
|
31
|
+
### Paths, reads, and copying
|
|
32
|
+
|
|
33
|
+
- Reuse normalized absolute POSIX roots within bulk lexical path resolution and avoid per-segment drive-letter validation allocations, preserving path spellings, validation order, and errors.
|
|
34
|
+
- Reuse resolved Windows drive paths and descendant comparisons, with existing namespace, UNC, and colon-bearing component handling retained. Classify Windows device names without intermediate segment arrays.
|
|
35
|
+
- Reuse lexical directory prefixes while walking, preserving traversal order, followed-link spelling, budgets, callback behavior, and directory checks.
|
|
36
|
+
- Size small JavaScript SHA-256 scratch buffers to the file and byte budget, growing when size hints are stale while preserving complete reads, overflow detection, cancellation, and borrowed-descriptor ownership.
|
|
37
|
+
- Complete positive short reads from virtual files that report size zero instead of returning a truncated prefix. Byte-limit overflow detection and borrowed-descriptor cursor behavior remain intact.
|
|
38
|
+
- Confirm EOF when Linux `copy_file_range` reports zero after partial progress; resume guarded byte copying when readable data remains instead of publishing a truncated copy.
|
|
39
|
+
- Size Linux native byte-copy buffers from existing source-size hints, bounded between 4 KiB and 1 MiB, retaining read-to-EOF behavior, sparse output, and borrowed descriptors.
|
|
40
|
+
|
|
41
|
+
### Windows security and generated names
|
|
42
|
+
|
|
43
|
+
- Bind secure-file owner and DACL verification to the open descriptor and compare its native volume serial and file-index projection with Node's bigint receipt before reading.
|
|
44
|
+
- Retain parent and created-directory handles through private-directory creation, protected-DACL validation, complete identity association, and failure cleanup, preserving concurrent pathname replacements.
|
|
45
|
+
- Require differently cased Root prefixes to match the Root directory's exact identity, then continue under its trusted spelling. Exact-case paths retain their lexical fast path.
|
|
46
|
+
- Prevent filename truncation and temp-file sanitization from producing Windows reserved-device aliases. Invalid completed sibling callback components reject before hooks or producers run.
|
|
47
|
+
|
|
48
|
+
### Queues, locks, and cleanup
|
|
49
|
+
|
|
50
|
+
- Bind durable-queue migrations to their pinned processing generation, rejecting stale callbacks after acknowledgement, quarantine, or replacement. Release the verified read pin at Windows publication so migrations can replace their target.
|
|
51
|
+
- Resync resumed processing claims under the transfer lock so conditional migrations cannot bypass a failed publication sync on retry.
|
|
52
|
+
- Recheck current file type and modification time immediately before expiry pruning removes a file, preserving fresh replacements and timestamp refreshes without requiring read access.
|
|
53
|
+
- Stage guest cross-device symlink moves privately before atomic replacement, preserving the existing destination and source link when creation or publication fails.
|
|
54
|
+
- Avoid rescanning held locks during repeated lock-manager construction, and initialize legacy reference counts during acquisition and release so nested handles retain the outer lock.
|
|
55
|
+
|
|
56
|
+
### Validation
|
|
57
|
+
|
|
58
|
+
- Expand representative method-audit coverage to 572 workloads across JavaScript/native modes on Linux, macOS, and Windows, including 1,000-entry walks, larger TAR inventories, and small synchronous hashes, with explicit platform exclusions.
|
|
59
|
+
- Allow focused method audits with explicit native mode and balanced A-B-B-A or B-A-A-B measurement ordering.
|
|
60
|
+
- Extend walker coverage for lexical aliases and callback mutation. Keep cancellation fixture setup outside the operation deadline and drain fixture writes before cleanup on slow or failing filesystems.
|
|
61
|
+
- Give the Windows slow physical-package-copy sidecar proof separate setup, operation, child, and teardown budgets with bounded failure diagnostics, preserving ordinary helper deadlines.
|
|
62
|
+
|
|
63
|
+
## 0.11.0 - 2026-09-14
|
|
64
|
+
|
|
65
|
+
### Highlights
|
|
66
|
+
|
|
67
|
+
- **Faster archive-heavy workloads:** reduce repeated ZIP admission, batch native ZIP metadata reads, and reuse TAR parser input and admitted payload ranges. Archive extraction and inspection also benefit from larger reusable staging and transfer buffers.
|
|
68
|
+
- **More capable filesystem roots:** iterate one directory with `Root.entries()`, remove trees with bounded or explicitly unlimited `Root.remove()`, and create complete files from async byte streams with `Root.create()`.
|
|
69
|
+
- **Reuse handles for copying, overwriting, and hashing:** add `copyFileHandle()`, `overwriteFileHandle()`, and `sha256FileSync()` while preserving caller-owned descriptors and file positions.
|
|
70
|
+
- **Faster Windows permissions and copying:** inspect inherited ACLs without PowerShell startup when the native binding is available, reduce copy allocations, and preserve large zero-filled ranges in empty destination files.
|
|
71
|
+
- **Safer publication and private files:** tighten hardlink, inode, permission, archive-destination, and lock validation; add private producer workspaces for callback writes and fix staging of long Unicode filenames.
|
|
72
|
+
|
|
73
|
+
### Compatibility and upgrade notes
|
|
74
|
+
|
|
75
|
+
- `readSecureFile()` now rejects hardlinked inputs and rechecks the opened file after reading. Synchronous store publication rejects substituted or unverifiable file identities, including opaque Windows identities.
|
|
76
|
+
- Invalid lock stale thresholds and compromise-check intervals now reject before acquisition. Asynchronous compromise checks no longer overlap or turn overflowing timer values into rapid polling loops.
|
|
77
|
+
- Inherited atomic-replacement modes include only ordinary rwx bits from an existing non-symlink regular file. JavaScript raw sidecars are created with mode `0o600`, so a permissive umask cannot expose their payloads.
|
|
78
|
+
- Bun gains additional POSIX path support through the existing Rust binding, including with JIT disabled. Native-off policy remains explicit; addon-free limitations are documented. Node.js remains supported from version 22.
|
|
79
|
+
|
|
80
|
+
### Directory and file workflows
|
|
81
|
+
|
|
82
|
+
- Add guarded, nonrecursive `Root.entries()` with child-symlink reporting, cancellation, entry limits, and bounded sorted-name collection. Traversal and link policy remain with the caller.
|
|
83
|
+
- Extend `Root.remove()` with recursive traversal, entry/depth budgets, sorted or filesystem order, cancellation, and missing-target handling. Callers may explicitly choose unlimited recursive-removal budgets; exact directory/leaf identity checks remain in place, and `force` continues sibling cleanup when a child directory disappears.
|
|
84
|
+
- Add a `Root.create()` overload for async byte iterables. Input consumption is bounded, cancellation settles pending work, and completed contents are published exclusively through the existing guarded writer.
|
|
85
|
+
- Add `copyFileHandle()` for bounded regular-file transfers and `overwriteFileHandle()` for in-place replacement with prefix-only rollback preparation, growth-before-overwrite ordering, and a once-only synchronous pre-write admission callback. Both preserve caller ownership and cursors.
|
|
86
|
+
- Add `sha256FileSync()` for bounded hashing of pathnames and borrowed descriptors, with exact pathname admission and no native-binding requirement.
|
|
87
|
+
- Add exact directory-identity observations and synchronous assertions for caller-owned staging and recovery, retaining bigint precision, optional canonical-path checks, and final-symlink rejection even with trailing separators.
|
|
88
|
+
- Add `probePathCaseInsensitiveSync()` for local ASCII-case observations, with a read-only option, owned temporary probes, and an explicit unknown result instead of operating-system guesses.
|
|
89
|
+
- Add opt-in `producerIsolation: "private-directory"` to sibling callback writes and external sibling outputs. Cleanup ownership is established before the producer runs, allowing partial failures to be cleaned without changing existing defaults or publication checks.
|
|
90
|
+
|
|
91
|
+
### Archive and path performance
|
|
92
|
+
|
|
93
|
+
- Avoid repeating physical ZIP admission during JavaScript member reads. Reduce filename-validation allocations across both backends, and group native ZIP metadata reads into bounded buffers while retaining complete record, CRC, limit, and cancellation checks.
|
|
94
|
+
- Copy each JavaScript TAR WASM input window once across member events. Read selected plain-TAR payloads directly from the fully admitted private snapshot instead of parsing it a second time.
|
|
95
|
+
- Batch private archive-input staging through reusable buffers capped at 512 KiB. Match file-backed JavaScript gzip output to the 64 KiB WASM input window for faster extraction and TAR inspection; retain short-I/O handling, framing validation, bounded growth probes, and settled cancellation.
|
|
96
|
+
- Reduce repeated POSIX path-scoping and Root work, reuse checked store keys, and avoid redundant name normalization. Root exclusion, canonical spelling, Unicode byte limits, collision rules, home expansion, and live filesystem identity checks are preserved.
|
|
97
|
+
- Complete short gzip-header reads before classifying staged archives, preventing valid gzip files from being mistaken for TAR.
|
|
98
|
+
- Fit callback staging names using their original UTF-8 length as well as NFC/NFD lengths. Valid destination names whose normalization is shorter no longer fail with `ENAMETOOLONG` when a staging prefix is added; the final target spelling is preserved.
|
|
99
|
+
|
|
100
|
+
### Copy performance and filesystem fidelity
|
|
101
|
+
|
|
102
|
+
- Batch large borrowed-handle and JavaScript Root transfers through reusable 512 KiB buffers, retaining byte limits, cancellation, and per-write authority checks. Small byte budgets also bound scratch allocation and the overflow probe.
|
|
103
|
+
- Batch atomic copy-fallback restore snapshots and synchronous source reads into reusable buffers, reducing reads and full-buffer copies while preserving restore budgets, short reads, identity checks, original modes, and rollback behavior.
|
|
104
|
+
- Share portable directory-copy workers across sibling directories, with bounded deferred completion, joined cancellation, and bottom-up timestamp restoration. Preserve fractional timestamps, including pre-1970 dates on Unix, to the precision supported by Node and the destination filesystem.
|
|
105
|
+
- Add Linux ZFS directory cloning through the bounded reflink traversal, with independent destinations, metadata preservation, and no byte-copy fallback when cloning is required. XFS clones now preserve user extended attributes on read-only files and directories, along with their modes and ACLs.
|
|
106
|
+
- Preserve large zero-filled ranges without allocating them during native Linux automatic directory byte copies and native Windows byte copies into empty files. Exact lengths, contents, cancellation settlement, and existing-target overwrite behavior remain intact.
|
|
107
|
+
- Confirm EOF when Linux copy offload initially reports zero bytes, so automatic copying reads available data instead of publishing an empty file.
|
|
108
|
+
- Size Windows native copy buffers to small inputs, reuse directory-enumeration buffers, and start ReFS workers as file jobs arrive. Report disk-full and sharing failures as `ENOSPC` and `EBUSY`; worker-start failures join admitted workers before returning instead of panicking across the native boundary.
|
|
109
|
+
|
|
110
|
+
### Permissions, compatibility, and tooling
|
|
111
|
+
|
|
112
|
+
- Inspect complete inherited local Windows ACLs through the native descriptor reader when available, preserving SID classification, explicit test injections, .NET normalization, and structured fallback diagnostics. Classify canonical SID facts directly without a redundant account lookup, retaining fail-closed behavior when discovery is unavailable.
|
|
113
|
+
- Retain the admitted archive destination's exact bigint identity through publication, rejecting replacements introduced by entry filters or concurrent actors before files can be published into them.
|
|
114
|
+
- Support Bun POSIX resolution for restrictive permissions, literal backslashes, sockets, and symlink/parent traversal. Preserve raw path components in absolute recursive-mkdir inputs to fix relative publication and queue writes on Bun for Windows.
|
|
115
|
+
- Export the caller-launched Python guest filesystem program and shared no-replace rename fragment through `@openclaw/fs-safe/guest`. The Linux/macOS protocol validates basenames before filesystem operations and uses short independent staging names for long-basename writes and cross-device moves.
|
|
116
|
+
- Support standalone `@pnpm/exe` in consumer smoke and lifecycle tests while retaining the declared pnpm version and isolated consumer configuration. Strengthen exact-inode durability checks and batch independent native ACL test observations.
|
|
117
|
+
- Expand method benchmarks to 557 representative workloads across Linux, macOS, and Windows, with native/JavaScript modes, equal-concurrency copy comparisons, large collections, contention, and explicit platform exclusions. Verify returned data and digests, initialize private Windows fixture ACLs, apply synchronous iteration reductions consistently, and handle expected synchronous rejections during timed calls as well as warmup.
|
|
118
|
+
|
|
5
119
|
## 0.10.0 - 2026-09-13
|
|
6
120
|
|
|
7
121
|
### Highlights
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2026 openclaw
|
|
4
|
+
Copyright (c) 2026 OpenClaw Foundation (guest filesystem Python source)
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -59,6 +59,8 @@ pnpm add @openclaw/fs-safe
|
|
|
59
59
|
|
|
60
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`. TAR/gzip fallback uses the bundled WASM build of the same Rust parser as native and works with optional dependencies omitted. ZIP fallback still needs optional `jszip`. See the [0.6 migration guide](docs/migrating-to-0.6.md).
|
|
61
61
|
|
|
62
|
+
Bun 1.4.2 is also supported with the [Bun runtime requirements](docs/install.md#bun-runtime), including the matching Rust addon on macOS and Linux. JIT-disabled Bun works too.
|
|
63
|
+
|
|
62
64
|
The package installs one prebuilt native binding for the current supported target. It
|
|
63
65
|
supplies fd-relative and atomic no-replace primitives that Node does not expose
|
|
64
66
|
directly. Configure the lazy loader before first use when you need a strict
|
|
@@ -131,6 +133,11 @@ await fs.move("notes/today.txt", "notes/archive/today.txt", { overwrite: true })
|
|
|
131
133
|
await fs.remove("notes/archive/today.txt");
|
|
132
134
|
```
|
|
133
135
|
|
|
136
|
+
Use `remove(path, { recursive: true, maxEntries: 20_000, maxDepth: 32 })` for
|
|
137
|
+
bounded tree cleanup. It streams directory entries, checks mutation authority
|
|
138
|
+
before every removal, and supports cancellation. See [removal options and partial
|
|
139
|
+
failure semantics](docs/writing.md).
|
|
140
|
+
|
|
134
141
|
`root()` takes the trusted directory; relative paths in subsequent calls are resolved against it. Defaults you pass to `root()` apply to every call below; per-call options override them.
|
|
135
142
|
|
|
136
143
|
`copyIn()` also accepts `{ root: sourceRoot, relativePath }`, `overwrite: false`,
|
|
@@ -151,6 +158,12 @@ const opened = await fs.open("notes/today.txt");
|
|
|
151
158
|
await fs.create("notes/README.md", "seed\n"); // throws if it already exists
|
|
152
159
|
```
|
|
153
160
|
|
|
161
|
+
`create()` also accepts an `AsyncIterable<Uint8Array>` for large or incrementally
|
|
162
|
+
produced files. Streamed creates keep the destination absent until all chunks
|
|
163
|
+
are written, support `maxBytes` and `signal`, and recheck mutation authority
|
|
164
|
+
before writes and publication. See [streamed creation](docs/writing.md#streamed-creation)
|
|
165
|
+
for producer ownership and cancellation semantics.
|
|
166
|
+
|
|
154
167
|
`write()` replaces file contents by default; pass `{ overwrite: false }` or use `create()` when an existing file should be an error. `move()` defaults to no clobber because it can otherwise delete an unrelated target while also consuming the source. Pass `{ overwrite: true }` when replacing the target is intended.
|
|
155
168
|
|
|
156
169
|
Mutating methods accept `assertBeforeMutation: () => void` for live lease or
|
|
@@ -220,7 +233,7 @@ const locked = await root("/srv/workspace", {
|
|
|
220
233
|
await locked.write(".env", "token"); // FsSafeError code "denied-path"
|
|
221
234
|
```
|
|
222
235
|
|
|
223
|
-
`stat()`, `exists()`, and `
|
|
236
|
+
`stat()`, `exists()`, `list()`, and `entries()` are boundary-checked, but they cannot pin a later operation to the same filesystem object. Use `read()`, `open()`, `write()`, `create()`, `copyIn()`, `move()`, or `remove()` for operation-local identity checks, and inspect `containment` when the platform distinction matters.
|
|
224
237
|
|
|
225
238
|
## Subpaths
|
|
226
239
|
|
|
@@ -239,18 +252,19 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
|
|
|
239
252
|
| `@openclaw/fs-safe/store` | `fileStore`, `fileStoreSync`, and `jsonStore` |
|
|
240
253
|
| `@openclaw/fs-safe/secret` | sync/async strict and try-style secret reads, atomic replace, and create-only secret writes |
|
|
241
254
|
| `@openclaw/fs-safe/atomic` | `replaceFileAtomic`, `replaceFileAtomicSync`, `replaceDirectoryAtomic`, `movePathWithCopyFallback` |
|
|
242
|
-
| `@openclaw/fs-safe/durability` | pinned directory identities, strict directory sync, durable nested-directory creation, exclusive publication, streaming SHA-256, provenance receipts, and sync-failure policy |
|
|
255
|
+
| `@openclaw/fs-safe/durability` | pinned directory identities, strict directory sync, durable nested-directory creation, exclusive publication, streaming and synchronous SHA-256, provenance receipts, and sync-failure policy |
|
|
243
256
|
| `@openclaw/fs-safe/temp` | `tempWorkspace`, `tempWorkspaceSync`, `withTempWorkspace`, `resolveSecureTempRoot` |
|
|
244
257
|
| `@openclaw/fs-safe/secure-file` | fd-pinned absolute file reads with owner, mode, ACL, trusted-dir, size, and timeout checks |
|
|
245
258
|
| `@openclaw/fs-safe/file-lock` | async/sync sidecar locks, root-bounded sidecars, ownership verification, and stale policy |
|
|
246
259
|
| `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
|
|
247
260
|
| `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
|
|
248
|
-
| `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, and
|
|
261
|
+
| `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, XFS, and ZFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
|
|
249
262
|
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, native gzip/zstd/bzip2, and bounded entry reads |
|
|
250
|
-
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, install
|
|
263
|
+
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, [borrowed-handle copying](docs/copy.md#borrowed-filehandle-transfers), [exact directory identity](docs/directory-identity.md), [case probing](docs/path-case.md), [in-place writes](docs/in-place-write.md), [versioned install-ID encoding](docs/install-path.md#safepathsegmenthashedv2), filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
|
|
251
264
|
| `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
|
|
252
265
|
| `@openclaw/fs-safe/types` | shared types: `DirEntry`, `PathStat`, … |
|
|
253
266
|
| `@openclaw/fs-safe/test-hooks` | hooks the test suite uses to inject races; registration requires `NODE_ENV=test` or `VITEST=true` |
|
|
267
|
+
| `@openclaw/fs-safe/guest` | Python source and exit constants for caller-launched filesystem operations in Linux/macOS guests without Node; see the [guest protocol and trust boundary](docs/guest.md) |
|
|
254
268
|
|
|
255
269
|
## Failure semantics in the name
|
|
256
270
|
|
|
@@ -311,7 +325,7 @@ original directory on Linux/macOS and requires native support for this operation
|
|
|
311
325
|
It offers atomic replace/no-replace publication, not expected-inode replacement
|
|
312
326
|
or a crash-durability promise; application checks and coordination remain yours.
|
|
313
327
|
|
|
314
|
-
`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,
|
|
328
|
+
`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 inherits only rwx bits from an existing non-symlink regular file; special bits, ownership, ACLs, and extended attributes are not copied. 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. Its optional synchronous `assertBeforeMutation` hook rechecks caller authority before renames and each source removal; `onDestinationPublished` reports an exact bigint destination identity before later checks or cleanup can fail. See [mutation authority and publication receipts](docs/atomic.md#mutation-authority-and-publication-receipts).
|
|
315
329
|
|
|
316
330
|
```ts
|
|
317
331
|
import { replaceFileAtomic } from "@openclaw/fs-safe/atomic";
|
|
@@ -352,6 +366,12 @@ fsyncs the completed file, and atomically renames it over the target. Choose it
|
|
|
352
366
|
when the destination directory is itself the writable boundary and atomic
|
|
353
367
|
replacement matters.
|
|
354
368
|
|
|
369
|
+
For sibling producers that can leave partial output before throwing, opt in to
|
|
370
|
+
`producerIsolation: "private-directory"`. The callback writes inside an owned
|
|
371
|
+
private workspace on the target filesystem, allowing cleanup after producer
|
|
372
|
+
failure while preserving sibling publication behavior. See [external outputs](docs/output.md)
|
|
373
|
+
for the identity checks and cleanup limits.
|
|
374
|
+
|
|
355
375
|
Use it when the final filename is known before the external writer runs. If the
|
|
356
376
|
filename depends on sniffing the produced bytes, write to a private temp
|
|
357
377
|
workspace first, then finalize through the normal root APIs after validation.
|
|
@@ -440,7 +460,9 @@ Use `readSecureFile()` when the caller gives you an absolute credential path
|
|
|
440
460
|
instead of a root-relative workspace path. It opens the file first, validates the
|
|
441
461
|
same handle it will read from, checks trusted directories, owner, POSIX mode or
|
|
442
462
|
Windows ACLs, hardlink count, size, and optional timeout, then reads through the
|
|
443
|
-
pinned handle.
|
|
463
|
+
pinned handle. On Windows, both the bytes and the owner/DACL facts come from that
|
|
464
|
+
handle; secure reads require the matching current native package and do not fall
|
|
465
|
+
back to a pathname ACL command.
|
|
444
466
|
|
|
445
467
|
```ts
|
|
446
468
|
import { readSecureFile } from "@openclaw/fs-safe/secure-file";
|
|
@@ -458,6 +480,17 @@ flows where a warning is preferable to refusing the file.
|
|
|
458
480
|
|
|
459
481
|
## Directory walking
|
|
460
482
|
|
|
483
|
+
[`Root.entries()`](docs/entries.md) observes one directory without descending or
|
|
484
|
+
following child symlinks. It streams in filesystem order by default and supports
|
|
485
|
+
cancellation, entry limits that throw on overflow, and bounded sorted-name
|
|
486
|
+
collection. Use it when the caller owns traversal or symlink validation:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
for await (const entry of fs.entries("plugins", { maxEntries: 1_000 })) {
|
|
490
|
+
console.log(entry.name, entry.isSymbolicLink);
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
461
494
|
`walkDirectory()` and `walkDirectorySync()` replace ad-hoc recursive
|
|
462
495
|
`readdir()` loops with entry and depth budgets, a symlink policy, and stable
|
|
463
496
|
relative paths.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"absolute-path.d.ts","sourceRoot":"","sources":["../src/absolute-path.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"absolute-path.d.ts","sourceRoot":"","sources":["../src/absolute-path.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAKhE,MAAM,MAAM,yBAAyB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAU5D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,GAAG;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,6BAA6B,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AAmL7D,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWhE;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAEnF;AAuBD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,8BAAmC,GAC3C,OAAO,CAAC,6BAA6B,CAAC,CA+ExC;AAED,wBAAsB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAazF;AAED,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,yBAAyB,CAAA;CAAO,GACrD,OAAO,CAAC,oBAAoB,CAAC,CAgB/B;AAED,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,yBAAyB,CAAA;CAAO,GACrD,OAAO,CAAC,4BAA4B,CAAC,CA2BvC"}
|
package/dist/absolute-path.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { pathExists } from "./fs.js";
|
|
7
|
+
import { realpathSync } from "./realpath.js";
|
|
7
8
|
import { resolveRootPath } from "./root-path.js";
|
|
8
9
|
function resolveSymlinkPolicy(policy) {
|
|
9
10
|
if (policy === undefined)
|
|
@@ -251,7 +252,7 @@ export async function canonicalPathFromExistingAncestor(filePath) {
|
|
|
251
252
|
}
|
|
252
253
|
let canonicalAncestor = ancestor;
|
|
253
254
|
try {
|
|
254
|
-
canonicalAncestor =
|
|
255
|
+
canonicalAncestor = realpathSync.native(ancestor);
|
|
255
256
|
}
|
|
256
257
|
catch {
|
|
257
258
|
// Keep lexical path when the existing ancestor cannot be canonicalized.
|
|
@@ -264,7 +265,7 @@ export async function resolveAbsolutePathForRead(filePath, options = {}) {
|
|
|
264
265
|
const normalized = assertAbsolutePathInput(filePath);
|
|
265
266
|
let canonicalPath;
|
|
266
267
|
try {
|
|
267
|
-
canonicalPath =
|
|
268
|
+
canonicalPath = realpathSync.native(normalized);
|
|
268
269
|
}
|
|
269
270
|
catch (err) {
|
|
270
271
|
if (err.code === "ENOENT") {
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { createAsyncLock } from "./async-lock.js";
|
|
2
|
+
export { copyFileHandle, type CopyFileHandleOptions } from "./file-handle-transfer.js";
|
|
3
|
+
export { overwriteFileHandle, type OverwriteFileHandleOptions } from "./overwrite-file-handle.js";
|
|
4
|
+
export { probePathCaseInsensitiveSync, type ProbePathCaseOptions } from "./path-case.js";
|
|
5
|
+
export { readDirectoryIdentity, assertDirectoryIdentitySync, type DirectoryIdentity, } from "./directory-guard.js";
|
|
2
6
|
export { stageFileInDirectory, type StagedFile, type StagedFileReceipt, type PublishedFileReceipt, type StagedFilePublication, type StagedFileCleanupReceipt, type StagedFileFailureDetails, } from "./native-staged-file.js";
|
|
3
7
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
4
8
|
export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOptions, } from "./positional-read.js";
|
|
@@ -15,7 +19,7 @@ export { assertNoHardlinkedFinalPath, assertNoPathAliasEscape, PATH_ALIAS_POLICI
|
|
|
15
19
|
export { openRootFile, openRootFileSync, canUseRootFileOpen, matchRootFileOpenFailure, type OpenRootFileParams, type OpenRootFileSyncParams, type RootFileOpenFailure, type RootFileOpenFailureReason, type RootFileOpenResult, } from "./root-file.js";
|
|
16
20
|
export { ROOT_PATH_ALIAS_POLICIES, resolvePathViaExistingAncestorSync, resolveRootPath, resolveRootPathSync, type ResolvedRootPath, type RootPathAliasPolicy, } from "./root-path.js";
|
|
17
21
|
export { ensureDirectoryWithinRoot, pathScope, resolveExistingPathsWithinRoot, resolvePathWithinRoot, resolvePathsWithinRoot, resolveStrictExistingPathsWithinRoot, resolveWritablePathWithinRoot, type PathScope, type PathScopeOptions, type PathScopeResolveOptions, } from "./root-paths.js";
|
|
18
|
-
export { safeDirName, safePathSegmentHashed, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
|
|
22
|
+
export { safeDirName, safePathSegmentHashed, safePathSegmentHashedV2, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
|
|
19
23
|
export { assertNoSymlinkParents, assertNoSymlinkParentsSync, type AssertNoSymlinkParentsOptions, } from "./symlink-parents.js";
|
|
20
24
|
export { movePathToTrash, type MovePathToTrashOptions } from "./trash.js";
|
|
21
25
|
export { withTimeout } from "./timing.js";
|
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,10 @@
|
|
|
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 { copyFileHandle } from "./file-handle-transfer.js";
|
|
6
|
+
export { overwriteFileHandle } from "./overwrite-file-handle.js";
|
|
7
|
+
export { probePathCaseInsensitiveSync } from "./path-case.js";
|
|
8
|
+
export { readDirectoryIdentity, assertDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
9
|
export { stageFileInDirectory, } from "./native-staged-file.js";
|
|
6
10
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
7
11
|
export { readFileWindowFully, readFileWindowFullySync, } from "./positional-read.js";
|
|
@@ -18,7 +22,7 @@ export { assertNoHardlinkedFinalPath, assertNoPathAliasEscape, PATH_ALIAS_POLICI
|
|
|
18
22
|
export { openRootFile, openRootFileSync, canUseRootFileOpen, matchRootFileOpenFailure, } from "./root-file.js";
|
|
19
23
|
export { ROOT_PATH_ALIAS_POLICIES, resolvePathViaExistingAncestorSync, resolveRootPath, resolveRootPathSync, } from "./root-path.js";
|
|
20
24
|
export { ensureDirectoryWithinRoot, pathScope, resolveExistingPathsWithinRoot, resolvePathWithinRoot, resolvePathsWithinRoot, resolveStrictExistingPathsWithinRoot, resolveWritablePathWithinRoot, } from "./root-paths.js";
|
|
21
|
-
export { safeDirName, safePathSegmentHashed, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
|
|
25
|
+
export { safeDirName, safePathSegmentHashed, safePathSegmentHashedV2, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
|
|
22
26
|
export { assertNoSymlinkParents, assertNoSymlinkParentsSync, } from "./symlink-parents.js";
|
|
23
27
|
export { movePathToTrash } from "./trash.js";
|
|
24
28
|
export { withTimeout } from "./timing.js";
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { type ExtractionDeadline } from "./archive-deadline.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type ArchiveDirectoryGuard } from "./archive-staging.js";
|
|
3
3
|
import type { PublishedWriteIdentity } from "./pinned-write.js";
|
|
4
4
|
import type { Root } from "./root.js";
|
|
5
5
|
export type ArchivePublishedFile = {
|
|
6
6
|
relativePath: string;
|
|
7
7
|
identity: PublishedWriteIdentity;
|
|
8
|
-
guards: readonly
|
|
8
|
+
guards: readonly ArchiveDirectoryGuard[];
|
|
9
9
|
};
|
|
10
10
|
export type ArchivePublishedDirectory = {
|
|
11
|
-
guard:
|
|
12
|
-
parents: readonly
|
|
11
|
+
guard: ArchiveDirectoryGuard;
|
|
12
|
+
parents: readonly ArchiveDirectoryGuard[];
|
|
13
13
|
mode: number;
|
|
14
14
|
};
|
|
15
15
|
export declare function finalizeArchivePublication(params: {
|
|
16
16
|
targetRoot: Root;
|
|
17
|
-
destinationGuard:
|
|
18
|
-
sourceGuard:
|
|
17
|
+
destinationGuard: ArchiveDirectoryGuard;
|
|
18
|
+
sourceGuard: ArchiveDirectoryGuard;
|
|
19
19
|
files: readonly ArchivePublishedFile[];
|
|
20
20
|
directories: readonly ArchivePublishedDirectory[];
|
|
21
21
|
durable: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-durability.d.ts","sourceRoot":"","sources":["../src/archive-durability.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-durability.d.ts","sourceRoot":"","sources":["../src/archive-durability.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAClG,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAI9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAC1C,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,UAAU,EAAE,IAAI,CAAC;IACjB,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,WAAW,EAAE,qBAAqB,CAAC;IACnC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvC,WAAW,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FhB"}
|
|
@@ -53,7 +53,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
53
53
|
import fsSync from "node:fs";
|
|
54
54
|
import path from "node:path";
|
|
55
55
|
import { ownExtractionDestinationMutation } from "./archive-deadline.js";
|
|
56
|
-
import { assertDirectoryIdentityGuard, assertResolvedInsideDestination, createArchiveSymlinkTraversalError } from "./archive-staging.js";
|
|
56
|
+
import { assertDirectoryIdentityGuard, assertResolvedInsideDestination, createArchiveSymlinkTraversalError, } from "./archive-staging.js";
|
|
57
57
|
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
58
58
|
import { pinDirectory, syncDirectory } from "./directory-durability.js";
|
|
59
59
|
import { syncFileBestEffort } from "./file-sync.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-entry.d.ts","sourceRoot":"","sources":["../src/archive-entry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-entry.d.ts","sourceRoot":"","sources":["../src/archive-entry.ts"],"names":[],"mappings":"AAOA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC,IAAI,CAgEN;AAGD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc1F;AAED,wBAAgB,8BAA8B,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAgBlG;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAWT"}
|
package/dist/archive-entry.js
CHANGED
|
@@ -3,10 +3,9 @@ import { ArchiveSecurityError } from "./archive-errors.js";
|
|
|
3
3
|
import { isWindowsReservedDeviceName } from "./device-path.js";
|
|
4
4
|
import { formatErrorDetail } from "./error-detail.js";
|
|
5
5
|
import { resolveSafeBaseDir } from "./path.js";
|
|
6
|
+
import { lowerCaseNfc, maxNormalizedUtf8Bytes } from "./unicode-path.js";
|
|
6
7
|
export function isWindowsDrivePath(value) {
|
|
7
|
-
return normalizeArchiveEntryPath(value)
|
|
8
|
-
.split("/")
|
|
9
|
-
.some((segment) => /^[a-zA-Z]:/.test(segment));
|
|
8
|
+
return /(?:^|\/)[a-zA-Z]:/.test(normalizeArchiveEntryPath(value));
|
|
10
9
|
}
|
|
11
10
|
export function normalizeArchiveEntryPath(raw) {
|
|
12
11
|
return raw.replaceAll("\\", "/");
|
|
@@ -22,16 +21,17 @@ export function validateArchiveEntryPath(entryPath, params) {
|
|
|
22
21
|
throw new ArchiveSecurityError("entry-path", `archive entry contains a NUL byte: ${formatErrorDetail(entryPath)}`);
|
|
23
22
|
}
|
|
24
23
|
const slashNormalized = normalizeArchiveEntryPath(entryPath);
|
|
24
|
+
const rawSegments = slashNormalized.split("/");
|
|
25
25
|
if (process.platform === "win32" &&
|
|
26
|
-
|
|
26
|
+
rawSegments.some((segment) => segment.includes(":"))) {
|
|
27
27
|
throw new ArchiveSecurityError("entry-path", `archive entry uses a Windows alternate data stream path: ${formatErrorDetail(entryPath)}`);
|
|
28
28
|
}
|
|
29
29
|
if (process.platform === "win32" &&
|
|
30
|
-
|
|
30
|
+
rawSegments.some((segment) => isWindowsReservedDeviceName(segment))) {
|
|
31
31
|
throw new ArchiveSecurityError("entry-path", `archive entry uses a reserved device path: ${formatErrorDetail(entryPath)}`);
|
|
32
32
|
}
|
|
33
33
|
const normalized = path.posix.normalize(slashNormalized);
|
|
34
|
-
if (normalized.split("/").some((segment) =>
|
|
34
|
+
if (normalized.split("/").some((segment) => maxNormalizedUtf8Bytes(segment) > 255)) {
|
|
35
35
|
throw new ArchiveSecurityError("entry-path", `archive entry has an overlong path component: ${formatErrorDetail(entryPath)}`);
|
|
36
36
|
}
|
|
37
37
|
const escapeLabel = params?.escapeLabel ?? "destination";
|
|
@@ -41,7 +41,7 @@ export function validateArchiveEntryPath(entryPath, params) {
|
|
|
41
41
|
if (path.posix.isAbsolute(normalized) || normalized.startsWith("//")) {
|
|
42
42
|
throw new ArchiveSecurityError("entry-path", `archive entry is absolute: ${formatErrorDetail(entryPath)}`);
|
|
43
43
|
}
|
|
44
|
-
if (
|
|
44
|
+
if (rawSegments.includes("..")) {
|
|
45
45
|
throw new ArchiveSecurityError("entry-path", `archive entry contains a parent segment: ${formatErrorDetail(entryPath)}`);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -67,7 +67,7 @@ export function createArchiveOutputPathTracker() {
|
|
|
67
67
|
// Archive policy must not depend on the destination volume's case or
|
|
68
68
|
// Unicode-normalization behavior. Otherwise the JavaScript and native
|
|
69
69
|
// writers can disagree about which of two colliding entries wins.
|
|
70
|
-
const collisionKey = normalized
|
|
70
|
+
const collisionKey = lowerCaseNfc(normalized);
|
|
71
71
|
if (seen.has(collisionKey)) {
|
|
72
72
|
throw new ArchiveSecurityError("entry-path", `archive entries collide at output path ${formatErrorDetail(normalized)}: ${formatErrorDetail(originalPath)}`);
|
|
73
73
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Writable } from "node:stream";
|
|
2
2
|
import type { Gunzip } from "node:zlib";
|
|
3
|
+
export declare function isGzipBuffer(input: Uint8Array): boolean;
|
|
3
4
|
/** Validate the same physical suffix when admission owns an in-memory input. */
|
|
4
5
|
export declare function validateGzipBufferTail(input: Buffer, consumed: number, signal?: AbortSignal): Promise<void>;
|
|
5
6
|
/** Track physical input, not the sum of bytes consumed across separate writes:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAaxC,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAEvD;AAQD,gFAAgF;AAChF,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CASjH;AAED;8EAC8E;AAC9E,qBAAa,SAAU,SAAQ,QAAQ;IAIzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAAqB;IAExC,YAA6B,OAAO,EAAE,MAAM,EAE3C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAcxG;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAGpF;CACF;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBvH"}
|
|
@@ -2,6 +2,17 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { Writable } from "node:stream";
|
|
4
4
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
5
|
+
const PADDING_WINDOW_BYTES = 65536;
|
|
6
|
+
let zeroPadding;
|
|
7
|
+
function assertZeroPadding(bytes) {
|
|
8
|
+
zeroPadding ??= Buffer.alloc(PADDING_WINDOW_BYTES);
|
|
9
|
+
if (!bytes.equals(zeroPadding.subarray(0, bytes.length))) {
|
|
10
|
+
throw new ArchiveFormatError("nonzero gzip container padding");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function isGzipBuffer(input) {
|
|
14
|
+
return input[0] === 31 && input[1] === 139;
|
|
15
|
+
}
|
|
5
16
|
function assertConsumedBoundary(consumed, size) {
|
|
6
17
|
if (!Number.isSafeInteger(consumed) || consumed <= 0 || consumed > size) {
|
|
7
18
|
throw new ArchiveFormatError("invalid gzip consumed-input boundary");
|
|
@@ -11,12 +22,10 @@ function assertConsumedBoundary(consumed, size) {
|
|
|
11
22
|
export async function validateGzipBufferTail(input, consumed, signal) {
|
|
12
23
|
signal?.throwIfAborted();
|
|
13
24
|
assertConsumedBoundary(consumed, input.length);
|
|
14
|
-
for (let position = consumed; position < input.length; position +=
|
|
25
|
+
for (let position = consumed; position < input.length; position += PADDING_WINDOW_BYTES) {
|
|
15
26
|
signal?.throwIfAborted();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
if (position + 65536 < input.length)
|
|
27
|
+
assertZeroPadding(input.subarray(position, position + PADDING_WINDOW_BYTES));
|
|
28
|
+
if (position + PADDING_WINDOW_BYTES < input.length)
|
|
20
29
|
await new Promise(resolve => setImmediate(resolve));
|
|
21
30
|
}
|
|
22
31
|
signal?.throwIfAborted();
|
|
@@ -72,7 +81,7 @@ export async function validateGzipContainerTail(filePath, consumed, signal) {
|
|
|
72
81
|
assertConsumedBoundary(consumed, size);
|
|
73
82
|
if (consumed === size)
|
|
74
83
|
return;
|
|
75
|
-
const buffer = Buffer.allocUnsafe(
|
|
84
|
+
const buffer = Buffer.allocUnsafe(PADDING_WINDOW_BYTES);
|
|
76
85
|
let position = consumed;
|
|
77
86
|
while (position < size) {
|
|
78
87
|
signal?.throwIfAborted();
|
|
@@ -80,9 +89,7 @@ export async function validateGzipContainerTail(filePath, consumed, signal) {
|
|
|
80
89
|
signal?.throwIfAborted();
|
|
81
90
|
if (bytesRead === 0)
|
|
82
91
|
throw new ArchiveFormatError("truncated gzip container padding");
|
|
83
|
-
|
|
84
|
-
throw new ArchiveFormatError("nonzero gzip container padding");
|
|
85
|
-
}
|
|
92
|
+
assertZeroPadding(buffer.subarray(0, bytesRead));
|
|
86
93
|
position += bytesRead;
|
|
87
94
|
}
|
|
88
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-input.d.ts","sourceRoot":"","sources":["../src/archive-input.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAGL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAM7B,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAM/E,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,GAAG,OAAO,CAAC,IAAI,CAAC,CAchB;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,4BAA4B,CAAC;IACrC,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,GAAG,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"archive-input.d.ts","sourceRoot":"","sources":["../src/archive-input.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAGL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAM7B,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AAM/E,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,GAAG,OAAO,CAAC,IAAI,CAAC,CAchB;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,4BAA4B,CAAC;IACrC,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqE7B"}
|
package/dist/archive-input.js
CHANGED
|
@@ -61,11 +61,13 @@ export async function stageArchiveFileForExtraction(params) {
|
|
|
61
61
|
? fsConstants.O_NOFOLLOW
|
|
62
62
|
: 0);
|
|
63
63
|
output = await fs.open(staged.path, flags, 0o600);
|
|
64
|
-
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
64
|
+
const buffer = Buffer.allocUnsafe(Math.min(512 * 1024, Math.max(64 * 1024, Number(opened.size)), params.limits.maxArchiveBytes + 1));
|
|
65
65
|
let written = 0;
|
|
66
66
|
while (true) {
|
|
67
67
|
params.deadline.check();
|
|
68
|
-
const
|
|
68
|
+
const length = Math.min(buffer.length, params.limits.maxArchiveBytes - written + 1);
|
|
69
|
+
const { bytesRead } = await handle.read(buffer, 0, length, null);
|
|
70
|
+
params.deadline.check();
|
|
69
71
|
if (bytesRead === 0)
|
|
70
72
|
break;
|
|
71
73
|
written += bytesRead;
|
package/dist/archive-merge.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ExtractionDeadline } from "./archive-deadline.js";
|
|
2
|
+
import { type ArchiveDirectoryGuard } from "./archive-staging.js";
|
|
2
3
|
export type ArchivePublicationEntry = {
|
|
3
4
|
path: string;
|
|
4
5
|
kind: "file" | "directory";
|
|
@@ -10,7 +11,10 @@ type MergeParams = {
|
|
|
10
11
|
destinationRealDir: string;
|
|
11
12
|
deadline?: ExtractionDeadline;
|
|
12
13
|
};
|
|
13
|
-
|
|
14
|
+
type GuardedMergeParams = Pick<MergeParams, "sourceDir" | "deadline"> & {
|
|
15
|
+
destinationGuard: ArchiveDirectoryGuard;
|
|
16
|
+
};
|
|
17
|
+
export declare function mergePlannedArchiveIntoDestination(params: GuardedMergeParams & {
|
|
14
18
|
entries: readonly ArchivePublicationEntry[];
|
|
15
19
|
durable?: boolean;
|
|
16
20
|
}): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAClG,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAa9B,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjG,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AACF,KAAK,kBAAkB,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IACtE,gBAAgB,EAAE,qBAAqB,CAAC;CACzC,CAAC;AAEF,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,kBAAkB,GAAG;IAAE,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9F,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1F"}
|