@openclaw/fs-safe 0.14.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 +88 -0
- package/README.md +38 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- 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-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +10 -11
- 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-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +11 -11
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -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/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- 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-mode-owner.js +5 -5
- 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-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- 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-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +5 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- 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/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +13 -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 +5 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +27 -7
- 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 +174 -29
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +26 -14
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +209 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- 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 +121 -72
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- 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 +5 -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/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- 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/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -9
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +56 -72
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +8 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +17 -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-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -17
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +24 -15
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-publication.js +1 -1
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +3 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- 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/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- 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/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +4 -3
- 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 +4 -2
- package/docs/archive.md +97 -46
- package/docs/atomic.md +85 -8
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +37 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/file-store.md +19 -0
- package/docs/install.md +31 -7
- package/docs/json-store.md +5 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +32 -12
- package/docs/native.md +38 -7
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +50 -14
- package/docs/public-api.md +3 -2
- package/docs/root.md +56 -3
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +114 -8
- package/docs/staged-file.md +12 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +76 -6
- package/package.json +19 -16
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.16.0 - 2026-09-19
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- **Private files and directories through existing APIs:** add `private: true` to Root `mkdir()`, `ensureRoot()`, `create()`, and `createJson()`, with verified owner-only permissions and creation-time Windows ACL protection. New `/advanced` helpers `createDirectory()`, `createDirectorySync()`, and `createFileSync()` create one exclusive entry under an existing trusted parent; the file helper returns an owned, disposable descriptor. ([#487](https://github.com/openclaw/fs-safe/pull/487), [#482](https://github.com/openclaw/fs-safe/issues/482))
|
|
10
|
+
- **Safer secret and private writes:** verify the actual staging permissions before writing content, rejecting filesystems that accept permission changes without enforcing the required private mode. ([#491](https://github.com/openclaw/fs-safe/pull/491), [#493](https://github.com/openclaw/fs-safe/pull/493))
|
|
11
|
+
- **Atomic buffered file creation:** opt into `atomic: true` on `Root.create()` or `createJson()` to keep the destination absent until its content is ready, including without the native addon. Independently select `durable: "file"` when file-flush failures must propagate. Existing defaults remain unchanged. ([#486](https://github.com/openclaw/fs-safe/pull/486))
|
|
12
|
+
- **Compressed TAR without native installation:** extract and read bounded entries from zstd and bzip2 TAR archives through bundled WASM codecs, even with all optional dependencies omitted. The fallback retains the shared Rust TAR parser, full-stream validation, byte limits, and guarded publication, with no runtime interpreter or download. ([#488](https://github.com/openclaw/fs-safe/pull/488))
|
|
13
|
+
- **Windows security operations without the addon:** use packaged, readable PowerShell/C# helpers for raw owner/DACL inspection, private-directory creation, and secure-file reads in native `auto` or `off` mode when capabilities are unavailable. Private permissions apply at creation, and secure reads inspect the same open handle that supplies the bytes. ([#476](https://github.com/openclaw/fs-safe/pull/476))
|
|
14
|
+
|
|
15
|
+
### Compatibility and upgrade notes
|
|
16
|
+
|
|
17
|
+
- Private creation on macOS requires the matching native helper's ACL-inspection capability; disabled, missing, or older helpers reject before creating parents or stages. Relevant inheritable parent ACLs reject creation, while noninheriting ACLs remain allowed. Existing entries are never repaired or made less restrictive. Nonprivate creation and native-free secret-file writes retain their behavior; see the [creation contract](https://github.com/openclaw/fs-safe/blob/v0.16.0/docs/creation.md) for supported modes and platform limits.
|
|
18
|
+
- Native-free `atomic: true` creation requires hardlinks and rejects unsupported filesystems without publishing partial content. Private Windows file publication also requires hardlinks on the same local filesystem. Streamed creates continue to stage complete content; atomic visibility does not strengthen pathname containment or guarantee crash durability.
|
|
19
|
+
- `durable: "file"` applies to buffered, streamed, and JSON creates and propagates file-sync errors, including `EPERM`. Parent-directory synchronization remains best-effort, independently of the selected publication strategy.
|
|
20
|
+
- Windows fallbacks require system PowerShell and permission to run the packaged scripts and `.NET Add-Type` under normal system policy. They emit a path-free `FS_SAFE_NATIVE_FALLBACK` warning once per capability per process and add startup and compilation overhead; disallowed execution fails closed. No system policy is bypassed.
|
|
21
|
+
- Native `require` remains strict, and failures from available native operations never trigger a fallback retry. No-clobber `Root.move()` still requires native support. ZIP fallback still needs optional `jszip`, and `inspectTarArchive()` continues to accept only plain TAR and gzip.
|
|
22
|
+
- Atomic, streamed, and private creation can report cleanup, close, mode, or synchronization errors after a complete file has been published. Preserve the publication and cleanup receipts when handling failures; completed destinations remain in place, and indeterminate outcomes retain names for recovery.
|
|
23
|
+
|
|
24
|
+
### Security and correctness
|
|
25
|
+
|
|
26
|
+
- Verify `0600` before secret-file payload writes in native and JavaScript writers. Verify actual native private-file ownership and permissions before payload writes and at publication, rejecting filesystems that accept permission changes without enforcing them; retain explicit final modes and public staging's `0600` guarantees. ([#491](https://github.com/openclaw/fs-safe/pull/491), [#493](https://github.com/openclaw/fs-safe/pull/493))
|
|
27
|
+
- Compare exact directory identities before and after durability syncs, retry unknown Windows observations once, and reject rounded or unverifiable caller receipts. Bind numeric metadata to the same observation and preserve private identity authority across receipt mutation, durable creation, publication, and retained staging. ([#483](https://github.com/openclaw/fs-safe/pull/483))
|
|
28
|
+
- Retain streamed `Root.create()` authority callbacks and abort signals across producer waits so replacing caller options cannot detach a revoked lease or redirect cancellation; preserve callback receivers and owned-stage cleanup. ([#490](https://github.com/openclaw/fs-safe/pull/490))
|
|
29
|
+
- Recheck retained parent and staging identities after final publication authority callbacks on JavaScript and native writers, preserving substituted entries before their bytes can be published. Flush native Windows creation through the retained writable descriptor and preserve published destinations after finalization failures.
|
|
30
|
+
- Snapshot `Root.move()` mutation policy before asynchronous admission so changes to caller-owned deny paths or prefixes cannot change an in-flight move. Recheck native no-clobber source identity, type, and hardlink policy after the final authority callback; live revocation remains available through `assertBeforeMutation`.
|
|
31
|
+
- Keep asynchronous Root-backed file locks waiting through successive owner handoffs instead of failing with `path-mismatch`, while retaining identity checks and requiring a fresh exclusive acquisition.
|
|
32
|
+
- Return one unsupported clone-metadata result per input without the native addon on non-macOS platforms in `auto` and `off` modes. Preserve input validation, strict `require` mode, and native-only APFS metadata on macOS. ([#489](https://github.com/openclaw/fs-safe/pull/489))
|
|
33
|
+
|
|
34
|
+
### Diagnostics and maintenance
|
|
35
|
+
|
|
36
|
+
- Address Windows security commands by their built-in module names and restrict discovery to PowerShell's bundled system modules, avoiding broad discovery scans on helper startup. ([#484](https://github.com/openclaw/fs-safe/pull/484))
|
|
37
|
+
- Clarify that `resolveExistingPathsWithinRoot()` permits missing paths while `resolveStrictExistingPathsWithinRoot()` requires existing regular files, and distinguish native beneath mechanisms from the best-effort containment reported by public Root open, read, and writable-open results. ([#485](https://github.com/openclaw/fs-safe/pull/485))
|
|
38
|
+
- Honor case-insensitive Windows build environment names so configured WASM compilers and archivers remain selected in worker processes; preserve child-only compiler flags without duplicate case variants. ([#492](https://github.com/openclaw/fs-safe/pull/492))
|
|
39
|
+
- Refresh JavaScript and Rust dependencies, pnpm, CodeQL, and archive/release build toolchains, including current NAPI interoperability fixes; retain Node 22 support and Rust 1.88 compatibility. Includes the Dependabot updates in [#481](https://github.com/openclaw/fs-safe/pull/481).
|
|
40
|
+
|
|
41
|
+
## 0.15.0 - 2026-09-18
|
|
42
|
+
|
|
43
|
+
### Highlights
|
|
44
|
+
|
|
45
|
+
- Enforce Root authority throughout synchronous lock admission, stale recovery, and cleanup; keep asynchronous reclaim guards inside the Root capability.
|
|
46
|
+
- Preserve directory and archive-source identity across publication, callbacks, and recovery, and reject corrupt portable ZIP payloads and metadata.
|
|
47
|
+
- Support asynchronous walk filters, configurable-depth suffix probing, and bounded synchronous descriptor copying.
|
|
48
|
+
|
|
49
|
+
### Compatibility and upgrade notes
|
|
50
|
+
|
|
51
|
+
- Directory replacement now requires compatible native identity-fenced, no-replace support, including when the destination is absent. Disabled, missing, or older helpers reject before target-parent creation.
|
|
52
|
+
- Treat ordinary post-dispatch native rename errors as indeterminate. Preserve staged files and directory backups when a rename may have committed before its reply was lost; automatic retry and rollback require explicit evidence that dispatch never occurred.
|
|
53
|
+
- Synchronous Root locks require a genuine registered Root handle and enforce its policies. Interrupted asynchronous Root reclaim guards can remain after process exit or reset; recover them only after an application-owned liveness check proves the attempt ended.
|
|
54
|
+
- Root lock authority checks add measurable overhead. Removing redundant observations reduces some cost, but these paths remain slower than the earlier implementation that did not enforce the same boundary.
|
|
55
|
+
|
|
56
|
+
### Security and correctness
|
|
57
|
+
|
|
58
|
+
- Serialize sidecar admission across raw and Root-based synchronous callers, retain separate cleanup authority, and recheck asynchronous authorization at the final stale-removal boundary.
|
|
59
|
+
- Keep asynchronous Root reclaim-guard creation, token/byte ownership checks, and removal inside the capability. Preserve unsettled guards instead of following replaced parents through raw cleanup.
|
|
60
|
+
- Create native Root lock records through retained-parent exclusive opens so racing contenders retry normally. Ordinary Root writes retain private staging. Treat raw reclaim guards disappearing or changing during collision inspection as contention.
|
|
61
|
+
- Verify reclaim-guard ownership after the final sidecar snapshot and parser, preserving mutation authorization and parser failures. Evaluate stale age after snapshot parsing and reject cleanup when exact identity is unknown.
|
|
62
|
+
- Retain source-root and child-directory authority during archive merges and bind file identity and mode to the admitted copy descriptor, including exact large Windows identities.
|
|
63
|
+
- Preserve ZIP payloads and physical entry metadata during portable loading so extraction rejects corrupt empty content and honors admitted directory, symlink, and special-file kinds without reordering callbacks.
|
|
64
|
+
- Snapshot FileStore policies before asynchronous work, streams, source reads, and JSON serialization; snapshot JsonStore durability/newline options before queued mutations and updater callbacks.
|
|
65
|
+
- Snapshot external-output and sibling-temp options before asynchronous setup while preserving callback receivers.
|
|
66
|
+
- Enforce Windows native parent mutation policies before creating each missing component and retain originating native descriptor ownership through cleanup.
|
|
67
|
+
- Preserve files and symlinks replacing an observed empty FileStore directory, and leave directories that become nonempty untouched.
|
|
68
|
+
- Recheck child entries before walk descent so symlink replacements during filters honor the selected policy.
|
|
69
|
+
- Keep Windows owner/ACL diagnostics bounded and fail-closed for hostile thrown values. Preserve ordinary diagnostics and the original cause without invoking getters, proxy traps, coercion, or altered buffer properties.
|
|
70
|
+
|
|
71
|
+
### APIs and failure handling
|
|
72
|
+
|
|
73
|
+
- Allow asynchronous `include`/`descend` callbacks in `walkDirectory()` and `entryFilter` in `Root.walk()`, retaining serial selection, callback receivers, budgets, cancellation, and post-callback identity checks.
|
|
74
|
+
- Add `maxDepth` to `probePathSuffixAliasesSync()` with proportional budgets, a 32,768-forward-observation ceiling, unchanged defaults and string limits, and complete owned cleanup.
|
|
75
|
+
- Add `copyFileDescriptorSync()` for bounded positional transfers between caller-owned regular-file descriptors, preserving cursors, destination suffixes, descriptor lifetime, and caller-owned durability/publication.
|
|
76
|
+
- Preserve the first tree-copy operation failure, including falsy values and existing codes, while closing each owned resource once. Successful operations retain output-before-input and source-before-parent close-failure precedence.
|
|
77
|
+
- Preserve falsy atomic operation, cleanup, close, restoration, temporary-workspace removal, and Windows native cleanup failures; consume retained handles before close to prevent repeated release.
|
|
78
|
+
- Propagate portable ZIP fallback-close failures before publication and copy-fallback writer close failures after successful unsynchronized writes, including restoration with an absent destination.
|
|
79
|
+
- Preserve synchronous destination-admission failures when best-effort close also fails, and preserve directory-mode authority/deadline failures before and after dispatch.
|
|
80
|
+
|
|
81
|
+
### Performance and maintenance
|
|
82
|
+
|
|
83
|
+
- Bound path-prefix queue consumption on long paths while preserving short-path behavior and resolution checks.
|
|
84
|
+
- Reuse eligible Windows parent-creation receipts, equivalent Root identity observations, and guarded regular-file unlink paths while retaining final identity and policy checks.
|
|
85
|
+
- Consolidate native copy ownership, regular/secret read admission, copy-fallback admission, and temporary-workspace setup and settlement; remove redundant wrappers, write-admission storage, and permission-diagnostic work.
|
|
86
|
+
- Bind public copy, ZIP extraction, atomic settlement, and permission benchmarks to verified workload receipts; keep setup, content checks, and cleanup verification outside timing.
|
|
87
|
+
- Update @napi-rs/cli to 3.10.0 and the locked Node type definitions to 26.6.0, and align Vitest/coverage on 5.0.1.
|
|
88
|
+
- Expand security-owner coverage to native/archive code, platform packages, executable benchmarks, and build/test/release configuration.
|
|
89
|
+
- Raise publication and release-proof job ceilings to 90 minutes, retaining bounded registry retries and fail-closed artifact, signature, and provenance checks.
|
|
90
|
+
|
|
3
91
|
## 0.14.0 - 2026-09-17
|
|
4
92
|
|
|
5
93
|
### Highlights
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ The same idea has landed in other languages. Go [added `os.Root` and `OpenInRoot
|
|
|
45
45
|
| `path.resolve().startsWith()` | string check only | – | – | – | – |
|
|
46
46
|
| [`write-file-atomic`](https://www.npmjs.com/package/write-file-atomic) | – | ✓ | – | – | – |
|
|
47
47
|
| Go [`os.Root`](https://go.dev/blog/osroot) / Rust [`cap-std`](https://github.com/bytecodealliance/cap-std) | ✓ | platform | ✓ | ✓ | – |
|
|
48
|
-
| **`@openclaw/fs-safe`** | **✓** | **✓** | **✓** | **Linux atomic; others best-effort** | **✓ (ZIP/TAR
|
|
48
|
+
| **`@openclaw/fs-safe`** | **✓** | **✓** | **✓** | **Linux atomic; others best-effort** | **✓ (ZIP/TAR/gzip/zstd/bzip2)** |
|
|
49
49
|
|
|
50
50
|
## Not a sandbox
|
|
51
51
|
|
|
@@ -57,7 +57,7 @@ This is a **library-level guardrail**, not OS-level isolation. It does not repla
|
|
|
57
57
|
pnpm add @openclaw/fs-safe
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Node 22 or newer. Core root/path/json/temp helpers avoid framework dependencies. With all optional dependencies omitted, public subpaths remain safe to import and fallback-capable operations work in `auto` or `off`. Native-only features, including no-clobber `Root.move()
|
|
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 fallback-capable operations work in `auto` or `off`. Native-only features, including no-clobber `Root.move()` and [`private: true` creation on macOS](docs/creation.md#permission-options), remain unavailable and fail with `helper-unavailable`. TAR, gzip, zstd, and bzip2 extraction and bounded entry reads use the same Rust TAR parser through bundled WASM when native support is disabled or absent. Zstd/bzip2 codecs are bundled too; gzip uses Node's built-in decoder. ZIP fallback still needs optional `jszip`. See the [0.6 migration guide](docs/migrating-to-0.6.md).
|
|
61
61
|
|
|
62
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
63
|
|
|
@@ -70,7 +70,7 @@ environment policy:
|
|
|
70
70
|
import { configureFsSafeNative } from "@openclaw/fs-safe";
|
|
71
71
|
|
|
72
72
|
configureFsSafeNative({ mode: "auto" }); // default: native when available
|
|
73
|
-
configureFsSafeNative({ mode: "off" }); //
|
|
73
|
+
configureFsSafeNative({ mode: "off" }); // disable the addon; use supported fallbacks
|
|
74
74
|
configureFsSafeNative({ mode: "require" }); // fail closed if the binding is unavailable
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -158,6 +158,23 @@ const opened = await fs.open("notes/today.txt");
|
|
|
158
158
|
await fs.create("notes/README.md", "seed\n"); // throws if it already exists
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
Use `private: true` on `mkdir()`, `ensureRoot()`, `create()`, or `createJson()`
|
|
162
|
+
for private creation. On macOS, this requires native ACL inspection before
|
|
163
|
+
creating parents or stages and verifies owner-only permissions with no ACL
|
|
164
|
+
before writing payload bytes. Native `off` or a missing ACL capability rejects
|
|
165
|
+
with `helper-unavailable`; nonprivate creation is unchanged. See the
|
|
166
|
+
[creation contract](docs/creation.md#permission-options) for parent ACL handling
|
|
167
|
+
and platform support.
|
|
168
|
+
|
|
169
|
+
Pass `{ atomic: true }` to buffered `create()` or `createJson()` to keep the
|
|
170
|
+
destination absent until complete content is ready, including with native support
|
|
171
|
+
disabled. The JavaScript fallback requires hardlinks and never downgrades to a
|
|
172
|
+
partial visible file. Omitted or `false` retains the existing buffered behavior.
|
|
173
|
+
Atomic visibility is separate from the existing `durable` synchronization policy.
|
|
174
|
+
Use `durable: "file"` on `create()` or `createJson()` when file-flush errors,
|
|
175
|
+
including `EPERM`, must propagate. It combines with `atomic: true` without
|
|
176
|
+
requiring strict parent-directory synchronization.
|
|
177
|
+
|
|
161
178
|
`create()` also accepts an `AsyncIterable<Uint8Array>` for large or incrementally
|
|
162
179
|
produced files. Streamed creates keep the destination absent until all chunks
|
|
163
180
|
are written, support `maxBytes` and `signal`, and recheck mutation authority
|
|
@@ -259,8 +276,8 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
|
|
|
259
276
|
| `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
|
|
260
277
|
| `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
|
|
261
278
|
| `@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) |
|
|
262
|
-
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits,
|
|
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), [suffix-alias probing](docs/path-suffix-aliases.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 |
|
|
279
|
+
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, gzip/zstd/bzip2 support, and bounded entry reads |
|
|
280
|
+
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, [borrowed-handle and descriptor copying](docs/copy.md#borrowed-filehandle-transfers), [exact directory identity](docs/directory-identity.md), [case probing](docs/path-case.md), [suffix-alias probing](docs/path-suffix-aliases.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 |
|
|
264
281
|
| `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
|
|
265
282
|
| `@openclaw/fs-safe/types` | shared types: `DirEntry`, `PathStat`, … |
|
|
266
283
|
| `@openclaw/fs-safe/test-hooks` | hooks the test suite uses to inject races; registration requires `NODE_ENV=test` or `VITEST=true` |
|
|
@@ -466,8 +483,13 @@ instead of a root-relative workspace path. It opens the file first, validates th
|
|
|
466
483
|
same handle it will read from, checks trusted directories, owner, POSIX mode or
|
|
467
484
|
Windows ACLs, hardlink count, size, and optional timeout, then reads through the
|
|
468
485
|
pinned handle. On Windows, both the bytes and the owner/DACL facts come from that
|
|
469
|
-
handle
|
|
470
|
-
|
|
486
|
+
handle. In native `auto` or `off` mode, a packaged, readable PowerShell script can
|
|
487
|
+
inspect that borrowed handle when the native capability is unavailable, subject
|
|
488
|
+
to the [Windows security fallback prerequisites](docs/install.md#windows-security-fallback).
|
|
489
|
+
It emits one fallback warning per process for secure reads and adds PowerShell
|
|
490
|
+
startup and compilation overhead per call. Native `require` remains strict, and
|
|
491
|
+
native operation failures are terminal. Neither route reopens the pathname to
|
|
492
|
+
inspect its ACL.
|
|
471
493
|
|
|
472
494
|
```ts
|
|
473
495
|
import { readSecureFile } from "@openclaw/fs-safe/secure-file";
|
|
@@ -517,12 +539,18 @@ for (const file of scan.entries) {
|
|
|
517
539
|
|
|
518
540
|
Check `scan.truncated` before treating the result as complete, and `scan.failedDirs` to tell an incomplete scan (a directory that could not be read) from an empty one before pruning state from the listing.
|
|
519
541
|
|
|
542
|
+
`walkDirectory()` accepts asynchronous `include` and `descend` callbacks through `AsyncWalkDirectoryOptions`, so a marker lookup can prune a directory before its children are read. Decisions remain serial and retain the options object as their `this` receiver; `walkDirectorySync()` and its options remain synchronous. See [Directory walking](docs/walk.md) for callback timing, JavaScript result compatibility, and error handling.
|
|
543
|
+
|
|
520
544
|
For caller-controlled paths, `Root.walk()` is the root-bounded async iterator.
|
|
521
545
|
It supports entry/depth budgets, in-root symlink following, cancellation, and a
|
|
522
546
|
truncation marker (or typed error) when a budget is reached. Its `entryFilter`
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
547
|
+
accepts `"include"`, `"skip"`, or `"skip-subtree"`, directly or through a Promise.
|
|
548
|
+
After an awaited decision resolves, the walk rechecks cancellation and the
|
|
549
|
+
current listing directory and Root identities before using it. Pending callbacks
|
|
550
|
+
settle before cancellation or iterator disposal completes. Callback failures
|
|
551
|
+
reject the walk. `onDirectoryError: "skip-and-report"` yields typed `"directory-error"` markers
|
|
552
|
+
for directory read or identity-check failures while preserving entries from
|
|
553
|
+
readable subtrees.
|
|
526
554
|
|
|
527
555
|
## Archive extraction
|
|
528
556
|
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { createAsyncLock } from "./async-lock.js";
|
|
2
|
-
export {
|
|
2
|
+
export { createDirectory, createDirectorySync, createFileSync, type CreateDirectoryOptions, type CreateFileOptions, type OwnedFileDescriptorSync, } from "./create.js";
|
|
3
|
+
export { copyFileHandle, copyFileDescriptorSync, type CopyFileHandleOptions } from "./file-handle-transfer.js";
|
|
3
4
|
export { overwriteFileHandle, type OverwriteFileHandleOptions } from "./overwrite-file-handle.js";
|
|
4
5
|
export { probePathCaseInsensitiveSync, type ProbePathCaseOptions } from "./path-case.js";
|
|
5
6
|
export { resolvePathPrefixSync, type ResolvedPathPrefix } from "./path-prefix.js";
|
package/dist/advanced.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,eAAe,EAAE,mBAAmB,EAAE,cAAc,EACpD,KAAK,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,KAAK,uBAAuB,GAClF,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC/G,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAClG,OAAO,EAAE,4BAA4B,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,KAAK,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAC1G,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,uBAAuB,EACvB,iCAAiC,EACjC,uBAAuB,EACvB,oBAAoB,EACpB,0BAA0B,EAC1B,2BAA2B,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,GAClC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,6BAA6B,EAClC,KAAK,oCAAoC,GAC1C,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,wBAAwB,EACxB,kCAAkC,EAClC,eAAe,EACf,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,SAAS,EACT,8BAA8B,EAC9B,qBAAqB,EACrB,sBAAsB,EACtB,oCAAoC,EACpC,6BAA6B,EAC7B,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,6BAA6B,GACnC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,6BAA6B,EAC7B,eAAe,EACf,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,QAAQ,EACb,QAAQ,EACR,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,2BAA2B,EAC3B,mBAAmB,EACnB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC"}
|
package/dist/advanced.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
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 {
|
|
5
|
+
export { createDirectory, createDirectorySync, createFileSync, } from "./create.js";
|
|
6
|
+
export { copyFileHandle, copyFileDescriptorSync } from "./file-handle-transfer.js";
|
|
6
7
|
export { overwriteFileHandle } from "./overwrite-file-handle.js";
|
|
7
8
|
export { probePathCaseInsensitiveSync } from "./path-case.js";
|
|
8
9
|
export { resolvePathPrefixSync } from "./path-prefix.js";
|
package/dist/archive-kind.d.ts
CHANGED
|
@@ -4,6 +4,5 @@ type ResolvePackedRootDirOptions = {
|
|
|
4
4
|
rootMarkers?: string[];
|
|
5
5
|
};
|
|
6
6
|
export declare function resolvePackedRootDir(extractDir: string, options?: ResolvePackedRootDirOptions): Promise<string>;
|
|
7
|
-
export declare function assertPortableArchiveKind(kind: ArchiveKind): void;
|
|
8
7
|
export {};
|
|
9
8
|
//# sourceMappingURL=archive-kind.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC;AAQnE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAgBvE;AAED,KAAK,2BAA2B,GAAG;IACjC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAkBF,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,MAAM,CAAC,CA4BjB"}
|
package/dist/archive-kind.js
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { FsSafeError } from "./errors.js";
|
|
5
4
|
import { getNativeBinding } from "./native.js";
|
|
6
5
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
7
6
|
const TAR_SUFFIXES = [".tgz", ".tar.gz", ".tar"];
|
|
8
|
-
const
|
|
7
|
+
const COMPRESSED_TAR_SUFFIXES = [
|
|
9
8
|
{ suffixes: [".tbz2", ".tbz", ".tar.bz2"], kind: "tar-bzip2" },
|
|
10
9
|
{ suffixes: [".tzst", ".tar.zst", ".tar.zstd"], kind: "tar-zstd" },
|
|
11
10
|
];
|
|
12
|
-
function requireNativeArchiveKind(kind) {
|
|
13
|
-
if (!getNativeBinding()) {
|
|
14
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
15
|
-
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
16
|
-
}
|
|
17
|
-
return kind;
|
|
18
|
-
}
|
|
19
11
|
export function resolveArchiveKind(filePath) {
|
|
20
12
|
const lower = normalizeLowercaseStringOrEmpty(filePath);
|
|
21
13
|
if (lower.endsWith(".zip")) {
|
|
22
14
|
return "zip";
|
|
23
15
|
}
|
|
24
|
-
for (const { suffixes, kind } of
|
|
16
|
+
for (const { suffixes, kind } of COMPRESSED_TAR_SUFFIXES) {
|
|
25
17
|
if (suffixes.some((suffix) => lower.endsWith(suffix))) {
|
|
26
|
-
|
|
18
|
+
// Preserve strict require diagnostics even when only resolving a suffix.
|
|
19
|
+
getNativeBinding();
|
|
20
|
+
return kind;
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
23
|
if (TAR_SUFFIXES.some((suffix) => lower.endsWith(suffix))) {
|
|
@@ -75,9 +69,3 @@ export async function resolvePackedRootDir(extractDir, options) {
|
|
|
75
69
|
}
|
|
76
70
|
return path.join(extractDir, onlyDir);
|
|
77
71
|
}
|
|
78
|
-
export function assertPortableArchiveKind(kind) {
|
|
79
|
-
if (kind === "tar-zstd" || kind === "tar-bzip2") {
|
|
80
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
81
|
-
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -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;AAClG,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,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;AAiB9B,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,CAGf;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAS1F"}
|
package/dist/archive-merge.js
CHANGED
|
@@ -5,16 +5,19 @@ import { ownExtractionDestinationMutation } from "./archive-deadline.js";
|
|
|
5
5
|
import { assertDirectoryIdentityGuard, assertResolvedInsideDestination, createDirectoryIdentityGuard, createArchiveSymlinkTraversalError, preparePrivateArchiveOutputPath, } from "./archive-staging.js";
|
|
6
6
|
import {} from "./directory-mode-owner.js";
|
|
7
7
|
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
8
|
+
import { assertSyncDirectoryGuard, inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
8
9
|
import { FsSafeError } from "./errors.js";
|
|
9
10
|
import { formatErrorDetail } from "./error-detail.js";
|
|
10
|
-
import { isPathInside } from "./path.js";
|
|
11
|
+
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
11
12
|
import { rootFromDirectoryGuard } from "./root-impl.js";
|
|
13
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
12
14
|
import { realpathSync } from "./realpath.js";
|
|
13
15
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
14
|
-
import { onCopyPublication } from "./copy-publication.js";
|
|
16
|
+
import { onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
|
|
15
17
|
import { syncFileBestEffortSync } from "./file-sync.js";
|
|
16
18
|
import { finalizeArchivePublication } from "./archive-durability.js";
|
|
17
19
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
20
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
18
21
|
export async function mergePlannedArchiveIntoDestination(params) {
|
|
19
22
|
assertMergePathInputs(params);
|
|
20
23
|
await mergeTree(params, params.entries, params.durable === true);
|
|
@@ -58,6 +61,33 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
58
61
|
plan.set(stagedPath, entry);
|
|
59
62
|
}
|
|
60
63
|
const ancestors = [];
|
|
64
|
+
const sourceAncestors = [];
|
|
65
|
+
const assertSourceDirectory = (guard, canonical = false) => {
|
|
66
|
+
try {
|
|
67
|
+
// Child receipts already retain canonical ancestry. An exact no-follow
|
|
68
|
+
// observation checks each named association without re-running realpath.
|
|
69
|
+
if (canonical)
|
|
70
|
+
assertSyncDirectoryGuard(guard);
|
|
71
|
+
else
|
|
72
|
+
inspectDirectoryIdentitySync(guard.dir, guard.stat);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof FsSafeError || isNotFoundPathError(error)) {
|
|
76
|
+
throw createArchiveSymlinkTraversalError(path.relative(params.sourceDir, guard.dir));
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
check();
|
|
81
|
+
};
|
|
82
|
+
const assertSourceFrontier = () => {
|
|
83
|
+
const current = sourceAncestors.at(-1);
|
|
84
|
+
if (current)
|
|
85
|
+
assertSourceDirectory(current);
|
|
86
|
+
};
|
|
87
|
+
const assertSourceAncestors = () => {
|
|
88
|
+
for (const ancestor of sourceAncestors)
|
|
89
|
+
assertSourceDirectory(ancestor);
|
|
90
|
+
};
|
|
61
91
|
const assertGuards = async () => {
|
|
62
92
|
await assertDirectoryIdentityGuard(destinationGuard);
|
|
63
93
|
check();
|
|
@@ -72,15 +102,21 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
72
102
|
};
|
|
73
103
|
const walk = async (sourceDir) => {
|
|
74
104
|
await assertGuards();
|
|
105
|
+
assertSourceFrontier();
|
|
75
106
|
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
76
107
|
check();
|
|
108
|
+
if (entries.length === 0) {
|
|
109
|
+
await assertGuards();
|
|
110
|
+
assertSourceFrontier();
|
|
111
|
+
}
|
|
77
112
|
for (const entry of entries) {
|
|
78
113
|
await assertGuards();
|
|
114
|
+
assertSourceFrontier();
|
|
79
115
|
const sourcePath = path.join(sourceDir, entry.name);
|
|
80
116
|
const relPath = path.relative(params.sourceDir, sourcePath);
|
|
81
117
|
const originalPath = relPath.split(path.sep).join("/");
|
|
82
118
|
const destinationPath = path.join(destinationDir, relPath);
|
|
83
|
-
const sourceStat = fsSync.lstatSync(sourcePath);
|
|
119
|
+
const sourceStat = inspectFileIdentitySync(() => fsSync.lstatSync(sourcePath, { bigint: true }));
|
|
84
120
|
check();
|
|
85
121
|
if (sourceStat.isSymbolicLink())
|
|
86
122
|
throw createArchiveSymlinkTraversalError(originalPath);
|
|
@@ -96,61 +132,73 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
96
132
|
if (plan && ((planned && planned.kind !== kind) || (!planned && kind === "file"))) {
|
|
97
133
|
throw new FsSafeError("path-mismatch", "archive staging disagrees with the admitted publication plan");
|
|
98
134
|
}
|
|
99
|
-
|
|
135
|
+
let mode = plan ? planned?.mode ?? 0o755 : Number(sourceStat.mode & 511n);
|
|
100
136
|
await preparePrivateArchiveOutputPath({
|
|
101
137
|
destinationDir, destinationRealDir, deadline: params.deadline,
|
|
102
138
|
relPath, outPath: destinationPath, originalPath, isDirectory: kind === "directory",
|
|
103
139
|
}, assertGuards, destinationGuard);
|
|
104
140
|
check();
|
|
105
141
|
if (kind === "directory") {
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const guard = await createDirectoryIdentityGuard(destinationPath);
|
|
118
|
-
check();
|
|
119
|
-
await owner.verify(check);
|
|
120
|
-
ancestors.push({ guard, owner });
|
|
121
|
-
try {
|
|
122
|
-
await walk(sourcePath);
|
|
123
|
-
if (publication) {
|
|
124
|
-
await assertGuards();
|
|
125
|
-
publishedDirectories.push({ guard, mode, parents: ancestors.slice(0, -1).map((ancestor) => ancestor.guard) });
|
|
126
|
-
return;
|
|
142
|
+
// Retain the observation made before output preparation: recapturing
|
|
143
|
+
// here could silently authorize a replacement source directory.
|
|
144
|
+
sourceAncestors.push({ dir: sourcePath, realPath: sourceReal, stat: sourceStat });
|
|
145
|
+
try {
|
|
146
|
+
// Ownership spans open, descendants, finalization and close, including timeout.
|
|
147
|
+
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
148
|
+
await assertGuards();
|
|
149
|
+
assertSourceFrontier();
|
|
150
|
+
const owner = await pinNodeDirectoryForMode(destinationPath).catch((error) => {
|
|
151
|
+
if (error instanceof FsSafeError && (error.code === "not-file" || error.code === "path-mismatch")) {
|
|
152
|
+
throw createArchiveSymlinkTraversalError(originalPath);
|
|
127
153
|
}
|
|
128
|
-
|
|
154
|
+
throw error;
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
129
157
|
check();
|
|
130
|
-
await
|
|
131
|
-
// Do not recursively verify this owner from inside its serialized apply.
|
|
132
|
-
ancestors.pop();
|
|
133
|
-
await owner.apply(mode, { check, beforeChmod: async () => {
|
|
134
|
-
await assertGuards();
|
|
135
|
-
await assertDirectoryIdentityGuard(guard);
|
|
136
|
-
check();
|
|
137
|
-
await assertResolvedInsideDestination({
|
|
138
|
-
destinationRealDir, targetPath: destinationPath, originalPath,
|
|
139
|
-
});
|
|
140
|
-
check();
|
|
141
|
-
} });
|
|
158
|
+
const guard = await createDirectoryIdentityGuard(destinationPath);
|
|
142
159
|
check();
|
|
143
|
-
await
|
|
160
|
+
await owner.verify(check);
|
|
161
|
+
ancestors.push({ guard, owner });
|
|
162
|
+
try {
|
|
163
|
+
await walk(sourcePath);
|
|
164
|
+
if (publication) {
|
|
165
|
+
await assertGuards();
|
|
166
|
+
assertSourceAncestors();
|
|
167
|
+
publishedDirectories.push({ guard, mode, parents: ancestors.slice(0, -1).map((ancestor) => ancestor.guard) });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", destinationPath);
|
|
171
|
+
check();
|
|
172
|
+
await assertGuards();
|
|
173
|
+
// Do not recursively verify this owner from inside its serialized apply.
|
|
174
|
+
ancestors.pop();
|
|
175
|
+
await owner.apply(mode, { check, beforeChmod: async () => {
|
|
176
|
+
await assertGuards();
|
|
177
|
+
assertSourceAncestors();
|
|
178
|
+
await assertDirectoryIdentityGuard(guard);
|
|
179
|
+
check();
|
|
180
|
+
await assertResolvedInsideDestination({
|
|
181
|
+
destinationRealDir, targetPath: destinationPath, originalPath,
|
|
182
|
+
});
|
|
183
|
+
check();
|
|
184
|
+
} });
|
|
185
|
+
check();
|
|
186
|
+
await assertGuards();
|
|
187
|
+
assertSourceAncestors();
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
if (ancestors.at(-1)?.owner === owner)
|
|
191
|
+
ancestors.pop();
|
|
192
|
+
}
|
|
144
193
|
}
|
|
145
194
|
finally {
|
|
146
|
-
|
|
147
|
-
ancestors.pop();
|
|
195
|
+
await owner.close();
|
|
148
196
|
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
sourceAncestors.pop();
|
|
201
|
+
}
|
|
154
202
|
}
|
|
155
203
|
else {
|
|
156
204
|
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
@@ -158,6 +206,25 @@ async function mergeTree(params, publication, durable = true) {
|
|
|
158
206
|
try {
|
|
159
207
|
const options = {
|
|
160
208
|
mkdir: true, mode, durable: publication ? false : true,
|
|
209
|
+
[onCopySourceAdmission]: (identity, realPath) => {
|
|
210
|
+
assertSourceDirectory(sourceGuard, true);
|
|
211
|
+
assertSourceFrontier();
|
|
212
|
+
inspectFileIdentitySync(() => identity, sourceStat);
|
|
213
|
+
if (!admitPathInsideRoot({
|
|
214
|
+
rootPath: sourceGuard.realPath, candidatePath: realPath, rootIdentity: sourceGuard.stat,
|
|
215
|
+
}))
|
|
216
|
+
throw createArchiveSymlinkTraversalError(originalPath);
|
|
217
|
+
// Public merge permissions come from the admitted descriptor;
|
|
218
|
+
// extraction keeps the separately admitted publication plan.
|
|
219
|
+
if (!plan)
|
|
220
|
+
mode = Number(identity.mode & 511n);
|
|
221
|
+
return { mode, verify() {
|
|
222
|
+
// Only copy publication boundaries scan the active chain.
|
|
223
|
+
// Destination mkdir callbacks must not multiply this work.
|
|
224
|
+
assertSourceDirectory(sourceGuard, true);
|
|
225
|
+
assertSourceAncestors();
|
|
226
|
+
} };
|
|
227
|
+
},
|
|
161
228
|
};
|
|
162
229
|
if (publication && durable) {
|
|
163
230
|
options[onCopyPublication] = async (fd, identity) => {
|
package/dist/archive-parser.wasm
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAgBA,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqOzE,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAiBjB"}
|
package/dist/archive-read.js
CHANGED
|
@@ -7,12 +7,13 @@ import { readBoundedAsync } from "./bounded-read.js";
|
|
|
7
7
|
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
8
8
|
import { formatErrorDetail } from "./error-detail.js";
|
|
9
9
|
import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveArchiveKind } from "./archive-kind.js";
|
|
11
11
|
import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
|
|
12
12
|
import { isGzipBuffer } from "./archive-gzip-tail.js";
|
|
13
13
|
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
14
14
|
import { loadAdmittedZipArchive } from "./archive-zip-loader.js";
|
|
15
15
|
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
16
|
+
import { isZipSymlinkEntry, zipEntryKind } from "./archive-zip-entry.js";
|
|
16
17
|
import { FsSafeError } from "./errors.js";
|
|
17
18
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
18
19
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
@@ -22,8 +23,6 @@ import { admitZipBuffer } from "./archive-zip-admission.js";
|
|
|
22
23
|
import { validateNativeZipManifest } from "./archive-zip-manifest.js";
|
|
23
24
|
import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
|
|
24
25
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
25
|
-
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
26
|
-
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
27
26
|
function canonicalEntryPath(entryPath) {
|
|
28
27
|
validateArchiveEntryPath(entryPath, { escapeLabel: "archive root" });
|
|
29
28
|
return stripArchivePath(entryPath, 0) ?? "";
|
|
@@ -109,10 +108,11 @@ async function readZipEntry(buffer, entryPath, maxBytes, admitted) {
|
|
|
109
108
|
if (!entry || entry.dir) {
|
|
110
109
|
throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
|
|
111
110
|
}
|
|
112
|
-
if (
|
|
113
|
-
(entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE) {
|
|
111
|
+
if (isZipSymlinkEntry(entry)) {
|
|
114
112
|
throw new Error(`archive entry is a link: ${formatErrorDetail(entryPath)}`);
|
|
115
113
|
}
|
|
114
|
+
if (zipEntryKind(entry) !== "file")
|
|
115
|
+
throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
|
|
116
116
|
const integrity = createZipIntegrityTransform(entry);
|
|
117
117
|
const stream = typeof entry.nodeStream === "function"
|
|
118
118
|
? entry.nodeStream()
|
|
@@ -139,11 +139,11 @@ async function readZipEntry(buffer, entryPath, maxBytes, admitted) {
|
|
|
139
139
|
throw normalizeZipIntegrityError(error);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
|
|
142
|
+
async function readTarEntry(archiveBuffer, entryPath, maxBytes, kind) {
|
|
143
143
|
const seenPaths = new Set();
|
|
144
144
|
let selected;
|
|
145
145
|
const limits = resolveTarMeterLimits();
|
|
146
|
-
await inspectTar({ archiveBuffer, limits, onMember(info) {
|
|
146
|
+
await inspectTar({ archiveBuffer, kind, limits, onMember(info) {
|
|
147
147
|
const normalized = canonicalEntryPath(info.path);
|
|
148
148
|
if (seenPaths.has(normalized)) {
|
|
149
149
|
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
@@ -159,7 +159,7 @@ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
|
|
|
159
159
|
}
|
|
160
160
|
if (selected.size > maxBytes)
|
|
161
161
|
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
162
|
-
if (!isGzipBuffer(archiveBuffer)) {
|
|
162
|
+
if (kind === "tar" && !isGzipBuffer(archiveBuffer)) {
|
|
163
163
|
// Complete admission already validated this private snapshot through EOF.
|
|
164
164
|
// Copy the admitted payload so the result cannot expose or mutate its input.
|
|
165
165
|
const end = selected.offset + selected.size;
|
|
@@ -170,7 +170,7 @@ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
|
|
|
170
170
|
return Buffer.from(archiveBuffer.subarray(selected.offset, end));
|
|
171
171
|
}
|
|
172
172
|
let result;
|
|
173
|
-
await replayTar({ archiveBuffer, limits, members: [selected], async consume(member, payload) {
|
|
173
|
+
await replayTar({ archiveBuffer, kind, limits, members: [selected], async consume(member, payload) {
|
|
174
174
|
result = await readAdmittedTarPayload(payload, member.size);
|
|
175
175
|
} });
|
|
176
176
|
return result;
|
|
@@ -238,7 +238,6 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
238
238
|
const native = getNativeBinding();
|
|
239
239
|
if (native)
|
|
240
240
|
return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, zipEntries);
|
|
241
|
-
assertPortableArchiveKind(kind);
|
|
242
241
|
return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes, zipEntries)
|
|
243
|
-
: await readTarEntry(buffer, requestedEntry, options.maxBytes);
|
|
242
|
+
: await readTarEntry(buffer, requestedEntry, options.maxBytes, kind);
|
|
244
243
|
}
|