@openclaw/fs-safe 0.9.0 → 0.10.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.
Files changed (178) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +17 -0
  3. package/dist/advanced.d.ts +1 -0
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts.map +1 -1
  7. package/dist/archive-crc32.js +6 -1
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +3 -4
  10. package/dist/archive-gzip-tail.d.ts +2 -0
  11. package/dist/archive-gzip-tail.d.ts.map +1 -1
  12. package/dist/archive-gzip-tail.js +20 -3
  13. package/dist/archive-merge.js +1 -1
  14. package/dist/archive-native.d.ts.map +1 -1
  15. package/dist/archive-native.js +3 -2
  16. package/dist/archive-read.d.ts.map +1 -1
  17. package/dist/archive-read.js +67 -66
  18. package/dist/archive-tar-stream.d.ts +11 -4
  19. package/dist/archive-tar-stream.d.ts.map +1 -1
  20. package/dist/archive-tar-stream.js +20 -8
  21. package/dist/archive-tar-wasm.d.ts.map +1 -1
  22. package/dist/archive-tar-wasm.js +2 -1
  23. package/dist/bounded-read.d.ts +7 -0
  24. package/dist/bounded-read.d.ts.map +1 -1
  25. package/dist/bounded-read.js +73 -43
  26. package/dist/clone-metadata.d.ts +19 -0
  27. package/dist/clone-metadata.d.ts.map +1 -0
  28. package/dist/clone-metadata.js +32 -0
  29. package/dist/copy-file-input.d.ts +23 -0
  30. package/dist/copy-file-input.d.ts.map +1 -0
  31. package/dist/copy-file-input.js +91 -0
  32. package/dist/copy-policy.d.ts +3 -0
  33. package/dist/copy-policy.d.ts.map +1 -0
  34. package/dist/copy-policy.js +8 -0
  35. package/dist/copy-publication.d.ts +10 -1
  36. package/dist/copy-publication.d.ts.map +1 -1
  37. package/dist/copy-publication.js +27 -0
  38. package/dist/copy-tree-portable.d.ts +9 -0
  39. package/dist/copy-tree-portable.d.ts.map +1 -0
  40. package/dist/copy-tree-portable.js +191 -0
  41. package/dist/copy.d.ts +16 -0
  42. package/dist/copy.d.ts.map +1 -0
  43. package/dist/copy.js +123 -0
  44. package/dist/durability.d.ts +1 -1
  45. package/dist/durability.d.ts.map +1 -1
  46. package/dist/error-detail.d.ts.map +1 -1
  47. package/dist/error-detail.js +4 -1
  48. package/dist/file-hash.d.ts +6 -2
  49. package/dist/file-hash.d.ts.map +1 -1
  50. package/dist/file-hash.js +44 -8
  51. package/dist/filename.d.ts.map +1 -1
  52. package/dist/filename.js +2 -12
  53. package/dist/guarded-mkdir.d.ts +1 -0
  54. package/dist/guarded-mkdir.d.ts.map +1 -1
  55. package/dist/guarded-mkdir.js +1 -0
  56. package/dist/guarded-mutation.d.ts +2 -0
  57. package/dist/guarded-mutation.d.ts.map +1 -1
  58. package/dist/guarded-mutation.js +8 -4
  59. package/dist/home-dir.d.ts.map +1 -1
  60. package/dist/home-dir.js +9 -7
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/install-path.d.ts.map +1 -1
  64. package/dist/install-path.js +2 -6
  65. package/dist/json-durable-queue.d.ts.map +1 -1
  66. package/dist/json-durable-queue.js +13 -14
  67. package/dist/local-roots.d.ts.map +1 -1
  68. package/dist/local-roots.js +22 -24
  69. package/dist/move-path-stage.d.ts +8 -0
  70. package/dist/move-path-stage.d.ts.map +1 -0
  71. package/dist/move-path-stage.js +55 -0
  72. package/dist/move-path.d.ts.map +1 -1
  73. package/dist/move-path.js +26 -31
  74. package/dist/mutation-authority.d.ts +8 -0
  75. package/dist/mutation-authority.d.ts.map +1 -0
  76. package/dist/mutation-authority.js +36 -0
  77. package/dist/native-binding.d.ts +24 -1
  78. package/dist/native-binding.d.ts.map +1 -1
  79. package/dist/native-operations.d.ts +1 -1
  80. package/dist/native-operations.d.ts.map +1 -1
  81. package/dist/native-operations.js +11 -5
  82. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  83. package/dist/native-pinned-write-windows.js +18 -7
  84. package/dist/native-pinned-write.d.ts.map +1 -1
  85. package/dist/native-pinned-write.js +1 -0
  86. package/dist/native-staged-file.d.ts +3 -3
  87. package/dist/native-staged-file.d.ts.map +1 -1
  88. package/dist/native-staged-file.js +35 -8
  89. package/dist/path.d.ts.map +1 -1
  90. package/dist/path.js +3 -1
  91. package/dist/permissions-windows.d.ts.map +1 -1
  92. package/dist/permissions-windows.js +38 -48
  93. package/dist/pinned-operation.js +1 -1
  94. package/dist/pinned-write.d.ts +5 -1
  95. package/dist/pinned-write.d.ts.map +1 -1
  96. package/dist/pinned-write.js +50 -30
  97. package/dist/positional-read.d.ts +9 -0
  98. package/dist/positional-read.d.ts.map +1 -0
  99. package/dist/positional-read.js +36 -0
  100. package/dist/publish-copy-stage.d.ts +13 -0
  101. package/dist/publish-copy-stage.d.ts.map +1 -0
  102. package/dist/publish-copy-stage.js +47 -0
  103. package/dist/publish-file.d.ts.map +1 -1
  104. package/dist/publish-file.js +6 -5
  105. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  106. package/dist/replace-file-temp-owner.js +6 -2
  107. package/dist/root-context.d.ts +3 -0
  108. package/dist/root-context.d.ts.map +1 -1
  109. package/dist/root-context.js +51 -26
  110. package/dist/root-directory-list.d.ts +24 -0
  111. package/dist/root-directory-list.d.ts.map +1 -0
  112. package/dist/root-directory-list.js +201 -0
  113. package/dist/root-errors.d.ts +1 -0
  114. package/dist/root-errors.d.ts.map +1 -1
  115. package/dist/root-errors.js +3 -0
  116. package/dist/root-file.d.ts +2 -0
  117. package/dist/root-file.d.ts.map +1 -1
  118. package/dist/root-file.js +12 -4
  119. package/dist/root-impl.d.ts +6 -50
  120. package/dist/root-impl.d.ts.map +1 -1
  121. package/dist/root-impl.js +330 -210
  122. package/dist/root-options.d.ts +66 -0
  123. package/dist/root-options.d.ts.map +1 -0
  124. package/dist/root-options.js +18 -0
  125. package/dist/root-path-existing.d.ts +5 -0
  126. package/dist/root-path-existing.d.ts.map +1 -1
  127. package/dist/root-path-existing.js +56 -1
  128. package/dist/root-path.d.ts +1 -0
  129. package/dist/root-path.d.ts.map +1 -1
  130. package/dist/root-path.js +74 -162
  131. package/dist/root-symlink-policy.d.ts +13 -0
  132. package/dist/root-symlink-policy.d.ts.map +1 -0
  133. package/dist/root-symlink-policy.js +34 -0
  134. package/dist/root-walk.d.ts +5 -4
  135. package/dist/root-walk.d.ts.map +1 -1
  136. package/dist/root-walk.js +99 -50
  137. package/dist/root-write-mode.d.ts.map +1 -1
  138. package/dist/root-write-mode.js +1 -4
  139. package/dist/root.d.ts +3 -1
  140. package/dist/root.d.ts.map +1 -1
  141. package/dist/secure-file.d.ts.map +1 -1
  142. package/dist/secure-file.js +4 -4
  143. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  144. package/dist/sidecar-lock-acquire.js +2 -1
  145. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  146. package/dist/sidecar-lock-policy.js +3 -1
  147. package/dist/temp-cleanup.d.ts.map +1 -1
  148. package/dist/temp-cleanup.js +3 -2
  149. package/dist/timing.d.ts +1 -0
  150. package/dist/timing.d.ts.map +1 -1
  151. package/dist/timing.js +25 -6
  152. package/dist/trash.js +2 -2
  153. package/dist/walk.d.ts.map +1 -1
  154. package/dist/walk.js +6 -26
  155. package/dist/windows-owner.d.ts +9 -12
  156. package/dist/windows-owner.d.ts.map +1 -1
  157. package/dist/windows-owner.js +24 -58
  158. package/dist/write-file-handle.d.ts +6 -0
  159. package/dist/write-file-handle.d.ts.map +1 -0
  160. package/dist/write-file-handle.js +25 -0
  161. package/docs/advanced.md +18 -4
  162. package/docs/archive.md +22 -7
  163. package/docs/atomic.md +7 -0
  164. package/docs/contributing.md +7 -0
  165. package/docs/copy.md +86 -0
  166. package/docs/durability.md +17 -2
  167. package/docs/errors.md +1 -1
  168. package/docs/local-roots.md +8 -1
  169. package/docs/native.md +14 -1
  170. package/docs/permissions.md +20 -10
  171. package/docs/positional-read.md +63 -0
  172. package/docs/root.md +166 -4
  173. package/docs/security-model.md +14 -0
  174. package/docs/timing.md +2 -0
  175. package/docs/types.md +18 -11
  176. package/docs/walk.md +54 -5
  177. package/docs/writing.md +20 -2
  178. package/package.json +13 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,83 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.10.0 - 2026-09-13
6
+
7
+ ### Highlights
8
+
9
+ - **Faster ZIP and TAR reads:** read archive members without temporary disk snapshots, reuse admitted native buffers, and reduce integrity-check and decompression overhead while retaining full validation.
10
+ - **Directory copies with native acceleration:** use `copyTree` to prefer or require APFS clones, Btrfs snapshots, or parallel ReFS/XFS reflinks, or choose portable byte copying for controlled, immutable templates and checkouts.
11
+ - **Guarded file copies:** copy from checked Root sources with exclusive publication, cancellation that waits for writes, and optional native cloning; copied data stays independent of its source.
12
+ - **Faster Windows byte copies:** reuse bounded buffers and parallelize directory copying, with native transfers between checked handles in automatic mode.
13
+ - **Reuse buffers for file reads:** new async and sync positional readers fill caller-owned buffers without changing the file's current offset; hashing gains byte limits and cancellation.
14
+ - **Walk large directories incrementally:** Root walks bound metadata work by the entry budget, with an opt-in filesystem-order stream for directories too wide to enumerate up front.
15
+ - **Recheck live access authority:** Root mutations can verify caller-owned leases or cancellation immediately before dispatch, and new symlink policies allow contained parent-directory aliases while rejecting final symlinks.
16
+
17
+ ### Compatibility and upgrade notes
18
+
19
+ - **Ambiguous paths may now reject.** Reads preserve actual symlink and parent traversal instead of normalizing it away; mutations reject ambiguous symlink/parent combinations. Use the new explicit parent-alias policies when that behavior is intended.
20
+ - **Mutation failures are reported more accurately.** Failed parent-directory checks after fallback moves and removals now reject instead of reporting success. Treat rejection after dispatch as a potentially completed mutation, not proof that nothing changed.
21
+ - **Windows fallback writes honor durability settings.** Root defaults and per-call overrides now apply to JavaScript write/create fallbacks. Use `durable: false` explicitly for reconstructible data when syncing is unnecessary.
22
+
23
+ ### Archive reads
24
+
25
+ - Retain admitted ZIP input buffers and parsed directories across native inspection and selected-member reads, removing disk staging and archive handoff copies. JavaScript ZIP reads also consume the admitted in-memory archive directly.
26
+ - Retain native TAR input and admitted member offsets. Plain TAR copies only the selected range after complete validation; gzip, zstd, and bzip2 replay bounded decompression with framing, trailer, padding, and limit checks intact. JavaScript TAR/gzip reads also avoid disk staging, with gzip output chunks matched to the WASM input window.
27
+ - Accelerate ZIP integrity checks with Node's native CRC32 on Node 22.2 and newer, retaining checksum validation and compatibility with earlier Node 22 versions. Reuse the strict UTF-8 decoder for TAR metadata without changing filename validation or BOM handling.
28
+ - Give each native archive pass its own abort signal so completed inspection cannot mask an extraction deadline.
29
+
30
+ ### File reads, hashing, and directory walks
31
+
32
+ - Add `readFileWindowFully()` and `readFileWindowFullySync()` to fill caller-owned buffers through short reads, stop at EOF, and preserve descriptor offsets and ownership. Async cancellation waits for the pending read to settle before the buffer can be reused.
33
+ - Bound speculative allocations for large or exhausted files and accelerate synchronous bounded reads of regular files up to 16 MiB without extra chunk copies; retain one-read async performance through the default Root byte budget.
34
+ - Add byte limits and cooperative cancellation to `sha256File`, preserving descriptor ownership and waiting for native work to stop before rejecting. Batch larger JavaScript hash reads in bounded buffers up to 256 KiB to reduce filesystem calls.
35
+ - Bound Root walk metadata batches by the global entry budget and stop batches at directories and symlinks before descent. Add `order: "filesystem"` for incremental directory streaming with bounded lookahead; sorted traversal remains the default, and unbounded sorted walks retain fast snapshots.
36
+ - Reduce walk overhead by sharing equivalent directory checks and synchronous entry classification, reusing joined paths, and avoiding an extra promise per async entry. Preserve both operation and close failures during disposal, and report a followed symlink target's size consistently in filters and returned metadata.
37
+ - Speed up POSIX containment and filename helpers while preserving traversal rejection, Unicode handling, reserved names, and collision-resistant install names.
38
+
39
+ ### File and directory copying
40
+
41
+ - Extend `Root.copyIn` with guarded Root sources, exclusive publication, settled cancellation, exact publication receipts, and optional native file cloning while keeping copied data independent. Share `clone: "auto" | "always" | "never"` with `copyTree`; Root keeps its `"never"` default, which uses ordinary reads and writes without clone or copy-offload calls.
42
+ - Add `copyTree` in `@openclaw/fs-safe/copy` with `clone: "auto"` (default), `"always"`, and `"never"` policies. Automatic copying prefers native cloning and falls back to byte copying only when the binding or filesystem capability is unavailable, or cloning cannot cross filesystems; strict cloning never falls back, and ordinary copying avoids clone and copy-offload calls. Destinations must be absent, and cancellation waits for admitted writes to settle.
43
+ - Speed up Windows directory byte copies with bounded parallelism, reusable 1 MiB buffers, and native transfers between checked handles in automatic mode. Honor copy concurrency across portable backends and settle admitted writes before reporting failures or cancellation.
44
+ - Support APFS directory clones, Btrfs subvolume preparation and snapshots, and parallel ReFS/XFS reflinks through `probeTreeClone` and `createCloneSource`. Preserve XFS file and directory extended attributes and ACLs, and restore directory timestamps after APFS bulk cloning.
45
+ - Add batched `readCloneFileMetadata` for APFS clone IDs and file metadata. These are point-in-time observations, not authorization or proof that later contents remain unchanged.
46
+ - Document metadata and filesystem limits: APFS directory cloning does not guarantee descendant ACL preservation or inheritance, Btrfs snapshots omit nested subvolume contents, and ReFS rejects unsupported reparse points and alternate data streams. Portable copying does not promise ownership, ACL, extended-attribute, alternate-stream, or sparse-layout preservation. Callers retain responsibility for source immutability, permission policy, and recovery after a failed or aborted copy.
47
+
48
+ ### Root policies and path safety
49
+
50
+ - Add composed Root `assertBeforeMutation` callbacks that recheck live caller authority immediately before mutation dispatch, including each buffered-write chunk and direct file removal. Root-level and per-call checks both apply; refusal errors and owned cleanup are preserved across native and JavaScript writers.
51
+ - Add `symlinks: "follow-parents-within-root"` for reads and opt-in `mutationSymlinks` policies, following contained directory aliases while rejecting final symlinks at absolute root entry, open, and publication boundaries.
52
+ - Preserve checked canonical and raw traversal in Root, absolute-path, local-root, and `openRootFile`/`openRootFileSync` reads, including home expansion and parent components that cancel a missing prefix. Validation can no longer normalize away a rejected link or select a different in-root file; local `requireFile` reads retain final-symlink rejection.
53
+ - Pin Root directory identities as bigint values, reject indistinguishable numeric inode replacements, and fail closed after bounded retries when Windows root identity remains unknown. Require a verified opened identity before inheriting an existing write target's mode.
54
+ - Preserve Linux native directory-only open flags so the kernel rejects non-directories before FIFO blocking or truncation, while removing the redundant post-open stat.
55
+ - Expand leading home-directory prefixes before resolving parent segments, so `~/../file` resolves against the home directory's parent; keep other tildes literal. Shorten only the home directory and its descendants in error messages, preserving similarly prefixed sibling paths.
56
+ - Resolve Windows ACL principal names such as `constructor` and `__proto__` as real dictionary keys, preserving SID lookup and translated entries.
57
+ - Preserve Unicode Windows paths during fallback permission inspection by reading structured SID and access-mask facts instead of localized command output.
58
+
59
+ ### Writes, moves, and cleanup
60
+
61
+ - Check the complete encoded newline before Root append, avoiding duplicated or missing separators in UTF-16LE files on Windows and other platforms.
62
+ - Preserve unowned staging replacements after Windows fallback write or sync failures, and retain exact parent-directory identities so owned partial-file cleanup works even when Windows directory indexes exceed numeric precision.
63
+ - Report failed post-operation parent checks after fallback moves and removals, including moved source parents, instead of silently reporting success.
64
+ - Close publication descriptors when initial inspection fails and relinquish descriptor numbers before potentially failing closes, preventing cleanup from closing a reused descriptor.
65
+ - Keep move-fallback publication and cleanup tied to the initially admitted staging identity, preserve substituted paths after copy failures, and reject writes that make no progress.
66
+ - Preserve recreated temporary paths after exit cleanup observes the original name missing; absence no longer authorizes removal of a later replacement.
67
+ - Allow explicitly authorized filesystem-root descendants in Trash moves and keep reservation-directory names bounded for long source filenames.
68
+
69
+ ### Locks and durable queues
70
+
71
+ - Honor finite timeouts and retry delays above Node's single-timer limit by rearming bounded timers instead of expiring after approximately 1 ms.
72
+ - Keep zero-delay lock retries finite when a large backoff factor overflows, preserving retry and timeout budgets.
73
+ - Skip invalid delivered-marker names during durable queue batch loading, preserving malformed files while continuing to load valid pending entries.
74
+ - Read durable queue entries through the shared bounded buffer and admitted file-size hint, reducing filesystem calls and chunk copies while retaining exact identity and byte-limit checks.
75
+
76
+ ### Validation and maintenance
77
+
78
+ - Add a method-by-method benchmark with callable API coverage checks, native/fallback reports, and fixture setup outside measurement.
79
+ - Give the full documentation-build smoke test a bounded longer runtime on slow Windows runners. Validate retained native archive buffers across concurrent reads and forced garbage collection; use efficient byte comparisons in large-buffer coverage tests without relaxing timeouts.
80
+
3
81
  ## 0.9.0 - 2026-09-11
4
82
 
5
83
  **Highlights:** Faster bulk extraction and configurable durability, with Windows filesystem fixes and reliable mixed-version lock cleanup.
package/README.md CHANGED
@@ -133,6 +133,11 @@ await fs.remove("notes/archive/today.txt");
133
133
 
134
134
  `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
135
 
136
+ `copyIn()` also accepts `{ root: sourceRoot, relativePath }`, `overwrite: false`,
137
+ and `clone: "auto"` for guarded, exclusive file copies with optional native
138
+ copy-on-write acceleration. Byte limits, cancellation, and publication receipts
139
+ are described in the [Root copy contract](docs/root.md#writes).
140
+
136
141
  When you need metadata or a `FileHandle`:
137
142
 
138
143
  ```ts
@@ -148,6 +153,17 @@ await fs.create("notes/README.md", "seed\n"); // throws if it already exists
148
153
 
149
154
  `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.
150
155
 
156
+ Mutating methods accept `assertBeforeMutation: () => void` for live lease or
157
+ cancellation checks immediately before filesystem dispatch. Root defaults and
158
+ per-call checks compose; cleanup and already-dispatched work still settle.
159
+ See [live mutation authority](docs/root.md#live-mutation-authority) for the exact
160
+ scope, including raw writable handles and lock bookkeeping.
161
+ For workspaces with directory aliases, use `symlinks: "follow-parents-within-root"`
162
+ on reads and `mutationSymlinks: "follow-parents-within-root"` on mutations or root
163
+ defaults. Contained parent symlinks are resolved by the library, while a final
164
+ symlink is rejected. Read policy and mutation policy are separate; omitting
165
+ `mutationSymlinks` preserves the existing mutation behavior. See [root policies](docs/root.md#defaults-vs-per-call-options).
166
+
151
167
  Use `ensureRoot()` when a computed relative directory target resolves to the root itself (`""` or `"."`) and you want the operation to be accepted. `root()` still requires the trusted root directory to already exist.
152
168
 
153
169
  ## Reading
@@ -229,6 +245,7 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
229
245
  | `@openclaw/fs-safe/file-lock` | async/sync sidecar locks, root-bounded sidecars, ownership verification, and stale policy |
230
246
  | `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
231
247
  | `@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 XFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
232
249
  | `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, native gzip/zstd/bzip2, and bounded entry reads |
233
250
  | `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, install paths, filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
234
251
  | `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
@@ -1,6 +1,7 @@
1
1
  export { createAsyncLock } from "./async-lock.js";
2
2
  export { stageFileInDirectory, type StagedFile, type StagedFileReceipt, type PublishedFileReceipt, type StagedFilePublication, type StagedFileCleanupReceipt, type StagedFileFailureDetails, } from "./native-staged-file.js";
3
3
  export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
4
+ export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOptions, } from "./positional-read.js";
4
5
  export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, type UnsafeDeviceReadPathMatch, type UnsafeDeviceReadPathOptions, type UnsafeDeviceReadPathReason, } from "./device-path.js";
5
6
  export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, type AbsolutePathSymlinkPolicy, type EnsureAbsoluteDirectoryOptions, type EnsureAbsoluteDirectoryResult, type ResolvedAbsolutePath, type ResolvedWritableAbsolutePath, } from "./absolute-path.js";
6
7
  export { sameFileIdentity, type FileIdentityStat } from "./file-identity.js";
@@ -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
@@ -4,6 +4,7 @@
4
4
  export { createAsyncLock } from "./async-lock.js";
5
5
  export { stageFileInDirectory, } from "./native-staged-file.js";
6
6
  export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
7
+ export { readFileWindowFully, readFileWindowFullySync, } from "./positional-read.js";
7
8
  export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, } from "./device-path.js";
8
9
  export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, } from "./absolute-path.js";
9
10
  export { sameFileIdentity } from "./file-identity.js";
@@ -1 +1 @@
1
- {"version":3,"file":"archive-crc32.d.ts","sourceRoot":"","sources":["../src/archive-crc32.ts"],"names":[],"mappings":"AAQA,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMpE"}
1
+ {"version":3,"file":"archive-crc32.d.ts","sourceRoot":"","sources":["../src/archive-crc32.ts"],"names":[],"mappings":"AAYA,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOpE"}
@@ -1,4 +1,7 @@
1
- const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
1
+ import zlib from "node:zlib";
2
+ const nativeCrc32 = zlib.crc32;
3
+ // Node 22.0–22.1 are supported but predate zlib.crc32.
4
+ const CRC32_TABLE = nativeCrc32 ? undefined : Array.from({ length: 256 }, (_, index) => {
2
5
  let value = index;
3
6
  for (let bit = 0; bit < 8; bit += 1) {
4
7
  value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
@@ -6,6 +9,8 @@ const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
6
9
  return value >>> 0;
7
10
  });
8
11
  export function updateCrc32(previous, buffer) {
12
+ if (nativeCrc32)
13
+ return nativeCrc32(buffer, previous >>> 0);
9
14
  let crc = previous ^ -1;
10
15
  for (const byte of buffer) {
11
16
  crc = (crc >>> 8) ^ (CRC32_TABLE[(crc ^ byte) & 0xff] ?? 0);
@@ -1 +1 @@
1
- {"version":3,"file":"archive-deadline.d.ts","sourceRoot":"","sources":["../src/archive-deadline.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,sBAAsB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACjE,2BAA2B,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAsB,gCAAgC,CAAC,CAAC,EACtD,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAWD,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAST;AAED,wBAAsB,eAAe,CAAC,CAAC,EACrC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAyDD,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
1
+ {"version":3,"file":"archive-deadline.d.ts","sourceRoot":"","sources":["../src/archive-deadline.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,sBAAsB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACjE,2BAA2B,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAsB,gCAAgC,CAAC,CAAC,EACtD,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAWD,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAST;AAED,wBAAsB,eAAe,CAAC,CAAC,EACrC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAuDD,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
@@ -1,3 +1,4 @@
1
+ import { scheduleTimeout } from "./timing.js";
1
2
  export async function ownExtractionDestinationMutation(deadline, run) {
2
3
  return deadline ? await deadline.ownDestinationMutation(run) : await run();
3
4
  }
@@ -68,16 +69,14 @@ function createExtractionDeadline(timeoutMs, label) {
68
69
  dispose: () => undefined,
69
70
  };
70
71
  }
71
- const timeoutId = setTimeout(() => {
72
+ const cancelTimeout = scheduleTimeout(() => {
72
73
  controller.abort(timeoutError);
73
74
  }, timeoutMs);
74
75
  return {
75
76
  signal: controller.signal,
76
77
  check,
77
78
  ...mutationOwner,
78
- dispose: () => {
79
- clearTimeout(timeoutId);
80
- },
79
+ dispose: cancelTimeout,
81
80
  };
82
81
  }
83
82
  export async function withExtractionDeadline(timeoutMs, label, run) {
@@ -1,5 +1,7 @@
1
1
  import { Writable } from "node:stream";
2
2
  import type { Gunzip } from "node:zlib";
3
+ /** Validate the same physical suffix when admission owns an in-memory input. */
4
+ export declare function validateGzipBufferTail(input: Buffer, consumed: number, signal?: AbortSignal): Promise<void>;
3
5
  /** Track physical input, not the sum of bytes consumed across separate writes:
4
6
  * gunzip can resume on a later chunk after leaving an earlier padding gap. */
5
7
  export declare class GzipInput extends Writable {
@@ -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;AAGxC;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,CAsBvH"}
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;AASxC,gFAAgF;AAChF,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAWjH;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,CAoBvH"}
@@ -2,6 +2,25 @@ 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
+ function assertConsumedBoundary(consumed, size) {
6
+ if (!Number.isSafeInteger(consumed) || consumed <= 0 || consumed > size) {
7
+ throw new ArchiveFormatError("invalid gzip consumed-input boundary");
8
+ }
9
+ }
10
+ /** Validate the same physical suffix when admission owns an in-memory input. */
11
+ export async function validateGzipBufferTail(input, consumed, signal) {
12
+ signal?.throwIfAborted();
13
+ assertConsumedBoundary(consumed, input.length);
14
+ for (let position = consumed; position < input.length; position += 65536) {
15
+ signal?.throwIfAborted();
16
+ if (input.subarray(position, position + 65536).some(byte => byte !== 0)) {
17
+ throw new ArchiveFormatError("nonzero gzip container padding");
18
+ }
19
+ if (position + 65536 < input.length)
20
+ await new Promise(resolve => setImmediate(resolve));
21
+ }
22
+ signal?.throwIfAborted();
23
+ }
5
24
  /** Track physical input, not the sum of bytes consumed across separate writes:
6
25
  * gunzip can resume on a later chunk after leaving an earlier padding gap. */
7
26
  export class GzipInput extends Writable {
@@ -50,9 +69,7 @@ export async function validateGzipContainerTail(filePath, consumed, signal) {
50
69
  const handle = await fs.open(filePath, "r");
51
70
  try {
52
71
  const { size } = fsSync.fstatSync(handle.fd);
53
- if (!Number.isSafeInteger(consumed) || consumed <= 0 || consumed > size) {
54
- throw new ArchiveFormatError("invalid gzip consumed-input boundary");
55
- }
72
+ assertConsumedBoundary(consumed, size);
56
73
  if (consumed === size)
57
74
  return;
58
75
  const buffer = Buffer.allocUnsafe(65536);
@@ -172,7 +172,7 @@ async function mergeTree(params, publication, durable = true) {
172
172
  }
173
173
  }
174
174
  catch (error) {
175
- // copyIn alone owns cleanup; no receipt permits archive-layer unlink.
175
+ // copyIn cleans unpublished stages; merge never gains rollback authority.
176
176
  if (error instanceof FsSafeError && (error.code === "hardlink" || error.code === "path-alias")) {
177
177
  throw createArchiveSymlinkTraversalError(originalPath);
178
178
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYnE;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,4BAA4B,CAAC;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;CAClD,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FhB"}
1
+ {"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYnE;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,4BAA4B,CAAC;IACrC,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;CAClD,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FhB"}
@@ -40,8 +40,9 @@ export async function extractNativeArchive(params) {
40
40
  destinationRealDir,
41
41
  run: async (stagingDir) => {
42
42
  params.deadline.check();
43
+ // N-API retains completed task state on its signal; each pass needs its own.
43
44
  const manifest = await params.binding
44
- .inspectArchiveNative(stagedArchive.path, params.kind, tarLimits, params.deadline.signal)
45
+ .inspectArchiveNative(stagedArchive.path, params.kind, tarLimits, AbortSignal.any([params.deadline.signal]))
45
46
  .catch(throwMappedNativeArchiveError);
46
47
  params.deadline.check();
47
48
  assertArchiveEntryCountWithinLimit(manifest.length, limits);
@@ -84,7 +85,7 @@ export async function extractNativeArchive(params) {
84
85
  (typeof fsConstants.O_DIRECTORY === "number" ? fsConstants.O_DIRECTORY : 0));
85
86
  try {
86
87
  params.deadline.check();
87
- await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan.map((entry) => ({ ...entry, mode: entry.kind === "directory" ? 0o700 : 0o600 })), tarLimits, params.deadline.signal).catch(throwMappedNativeArchiveError);
88
+ await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan.map((entry) => ({ ...entry, mode: entry.kind === "directory" ? 0o700 : 0o600 })), tarLimits, AbortSignal.any([params.deadline.signal])).catch(throwMappedNativeArchiveError);
88
89
  }
89
90
  finally {
90
91
  await directory.close().catch(() => undefined);
@@ -1 +1 @@
1
- {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkLpG,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,CA6EjB"}
1
+ {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiD,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAiOpG,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,CAkBjB"}
@@ -2,7 +2,7 @@ import { classifyArchiveParserError } from "./archive-parser-errors.js";
2
2
  import fsSync from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import { Readable } from "node:stream";
5
- import { readFileHandleBounded } from "./bounded-read.js";
5
+ import { readBoundedAsync } from "./bounded-read.js";
6
6
  import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
7
7
  import { formatErrorDetail } from "./error-detail.js";
8
8
  import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
@@ -17,7 +17,6 @@ import { resolveReadOpenFlags } from "./read-open-flags.js";
17
17
  import { getNativeBinding } from "./native.js";
18
18
  import { admitZipBuffer } from "./archive-zip-admission.js";
19
19
  import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
20
- import { tempFile } from "./temp-target.js";
21
20
  const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
22
21
  const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
23
22
  function canonicalEntryPath(entryPath) {
@@ -63,7 +62,7 @@ async function readStreamBounded(stream, maxBytes) {
63
62
  }
64
63
  return Buffer.concat(chunks, total);
65
64
  }
66
- async function stageArchiveInput(archivePath) {
65
+ async function readArchiveInput(archivePath) {
67
66
  const resolved = fsSync.realpathSync.native(archivePath);
68
67
  const before = await inspectFileIdentity(async () => {
69
68
  const stat = fsSync.lstatSync(archivePath, { bigint: true });
@@ -73,9 +72,7 @@ async function stageArchiveInput(archivePath) {
73
72
  return stat;
74
73
  });
75
74
  const handle = await fs.open(resolved, resolveReadOpenFlags());
76
- let staged;
77
75
  try {
78
- staged = await tempFile({ prefix: "fs-safe-archive-read", fileName: "archive.bin" });
79
76
  const opened = await inspectFileIdentity(async () => {
80
77
  const stat = fsSync.fstatSync(handle.fd, { bigint: true });
81
78
  if (!stat.isFile())
@@ -88,12 +85,14 @@ async function stageArchiveInput(archivePath) {
88
85
  throw new Error("archive changed during validation");
89
86
  return stat;
90
87
  }, opened);
91
- const buffer = await readFileHandleBounded(handle, DEFAULT_MAX_ARCHIVE_BYTES_ZIP);
92
- await fs.writeFile(staged.path, buffer, { flag: "wx", mode: 0o600 });
93
- return { path: staged.path, buffer, cleanup: staged.cleanup };
88
+ // Native archive workers borrow this private buffer. A pooled allocation could
89
+ // share its ArrayBuffer with unrelated JS buffers while the worker runs.
90
+ return await readBoundedAsync(DEFAULT_MAX_ARCHIVE_BYTES_ZIP, async (buffer, length) => (await handle.read(buffer, 0, length, null)).bytesRead, { unpooled: true, observeRegularFileSize: () => {
91
+ const size = fsSync.fstatSync(handle.fd).size;
92
+ return Number.isSafeInteger(size) && size >= 0 ? size : undefined;
93
+ } });
94
94
  }
95
95
  catch (error) {
96
- await staged?.cleanup().catch(() => undefined);
97
96
  if (error instanceof FsSafeError && error.code === "path-mismatch") {
98
97
  throw new FsSafeError("path-mismatch", "archive changed during validation", { cause: error });
99
98
  }
@@ -134,11 +133,11 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
134
133
  stream.once("error", (error) => integrity.destroy(normalizeZipIntegrityError(error)));
135
134
  return await readStreamBounded(stream.pipe(integrity), maxBytes);
136
135
  }
137
- async function readTarEntry(archivePath, entryPath, maxBytes) {
136
+ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
138
137
  const seenPaths = new Set();
139
138
  let selected;
140
139
  const limits = resolveTarMeterLimits();
141
- await inspectTar({ archivePath, limits, onMember(info) {
140
+ await inspectTar({ archiveBuffer, limits, onMember(info) {
142
141
  const normalized = canonicalEntryPath(info.path);
143
142
  if (seenPaths.has(normalized)) {
144
143
  throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
@@ -155,11 +154,57 @@ async function readTarEntry(archivePath, entryPath, maxBytes) {
155
154
  if (selected.size > maxBytes)
156
155
  throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
157
156
  let result;
158
- await replayTar({ archivePath, limits, members: [selected], async consume(_member, payload) {
157
+ await replayTar({ archiveBuffer, limits, members: [selected], async consume(_member, payload) {
159
158
  result = await readStreamBounded(payload, maxBytes);
160
159
  } });
161
160
  return result;
162
161
  }
162
+ function selectNativeEntry(manifest, requested, displayPath, physicalCount) {
163
+ if (physicalCount !== undefined && manifest.length !== physicalCount) {
164
+ throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
165
+ }
166
+ const seen = new Set();
167
+ let selected;
168
+ for (const entry of manifest) {
169
+ const normalized = canonicalEntryPath(entry.path);
170
+ if (seen.has(normalized)) {
171
+ throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
172
+ }
173
+ seen.add(normalized);
174
+ if (normalized === requested) {
175
+ if (entry.kind !== "file")
176
+ throw new Error(`archive entry is not a file: ${formatErrorDetail(displayPath)}`);
177
+ selected = entry;
178
+ }
179
+ }
180
+ if (!selected)
181
+ throw new Error(`archive entry not found: ${formatErrorDetail(displayPath)}`);
182
+ return selected;
183
+ }
184
+ function throwNativeReadError(error) {
185
+ if (error instanceof Error) {
186
+ const mapped = classifyArchiveParserError(error.message, { cause: error });
187
+ if (mapped)
188
+ throw mapped;
189
+ if (isArchiveFormatErrorMessage(error.message))
190
+ throw new ArchiveFormatError(error.message, { cause: error });
191
+ }
192
+ throw error;
193
+ }
194
+ async function readNativeBufferEntry(native, buffer, kind, requested, displayPath, maxBytes, physicalCount) {
195
+ try {
196
+ const signal = new AbortController().signal;
197
+ const limits = resolveTarMeterLimits();
198
+ const reader = kind === "zip"
199
+ ? await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]))
200
+ : await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
201
+ const selected = selectNativeEntry(reader.entries, requested, displayPath, physicalCount);
202
+ return await reader.readEntry(selected.index, maxBytes, AbortSignal.any([signal]));
203
+ }
204
+ catch (error) {
205
+ throwNativeReadError(error);
206
+ }
207
+ }
163
208
  export async function readArchiveEntry(archivePath, entryPath, options) {
164
209
  if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
165
210
  throw new RangeError("maxBytes must be a non-negative safe integer");
@@ -169,58 +214,14 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
169
214
  throw new Error(`unsupported archive: ${archivePath}`);
170
215
  }
171
216
  const requestedEntry = normalizedRequestedEntry(entryPath);
172
- const staged = await stageArchiveInput(archivePath);
173
- try {
174
- const physicalCount = kind === "zip"
175
- ? admitZipBuffer(staged.buffer, resolveExtractLimits())
176
- : undefined;
177
- const native = getNativeBinding();
178
- if (native) {
179
- try {
180
- const signal = new AbortController().signal;
181
- const limits = resolveTarMeterLimits();
182
- const manifest = await native.inspectArchiveNative(staged.path, kind, limits, signal);
183
- let rawEntryPath;
184
- if (physicalCount !== undefined && manifest.length !== physicalCount) {
185
- throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
186
- }
187
- const seenPaths = new Set();
188
- for (const entry of manifest) {
189
- const normalized = canonicalEntryPath(entry.path);
190
- if (seenPaths.has(normalized)) {
191
- throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
192
- }
193
- seenPaths.add(normalized);
194
- if (normalized === requestedEntry) {
195
- if (entry.kind !== "file") {
196
- throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
197
- }
198
- rawEntryPath = entry.path;
199
- }
200
- }
201
- if (!rawEntryPath) {
202
- throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
203
- }
204
- return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes, limits, signal);
205
- }
206
- catch (error) {
207
- if (error instanceof Error) {
208
- const mapped = classifyArchiveParserError(error.message, { cause: error });
209
- if (mapped)
210
- throw mapped;
211
- }
212
- if (error instanceof Error && isArchiveFormatErrorMessage(error.message)) {
213
- throw new ArchiveFormatError(error.message, { cause: error });
214
- }
215
- throw error;
216
- }
217
- }
218
- assertPortableArchiveKind(kind);
219
- return kind === "zip"
220
- ? await readZipEntry(staged.buffer, requestedEntry, options.maxBytes)
221
- : await readTarEntry(staged.path, requestedEntry, options.maxBytes);
222
- }
223
- finally {
224
- await staged.cleanup();
225
- }
217
+ const buffer = await readArchiveInput(archivePath);
218
+ const physicalCount = kind === "zip"
219
+ ? admitZipBuffer(buffer, resolveExtractLimits())
220
+ : undefined;
221
+ const native = getNativeBinding();
222
+ if (native)
223
+ return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, physicalCount);
224
+ assertPortableArchiveKind(kind);
225
+ return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes)
226
+ : await readTarEntry(buffer, requestedEntry, options.maxBytes);
226
227
  }
@@ -1,18 +1,25 @@
1
1
  import type { TarMeterLimits } from "./archive-limits.js";
2
2
  import { type AdmittedTarMember } from "./archive-tar-wasm.js";
3
- export declare function inspectTar(params: {
3
+ /** Buffers are private immutable snapshots, just like the staged file route. */
4
+ type TarInput = {
4
5
  archivePath: string;
6
+ archiveBuffer?: never;
7
+ } | {
8
+ archiveBuffer: Buffer;
9
+ archivePath?: never;
10
+ };
11
+ export declare function inspectTar(params: TarInput & {
5
12
  limits: TarMeterLimits;
6
13
  signal?: AbortSignal;
7
14
  onMember?: (entry: AdmittedTarMember) => void;
8
15
  }): Promise<void>;
9
16
  /** Replay in physical order, retaining at most one decoded chunk. Every range
10
- * comes from complete admission of the immutable staged input. */
11
- export declare function replayTar<T extends AdmittedTarMember>(params: {
12
- archivePath: string;
17
+ * comes from complete admission of the immutable input. */
18
+ export declare function replayTar<T extends AdmittedTarMember>(params: TarInput & {
13
19
  limits: TarMeterLimits;
14
20
  signal?: AbortSignal;
15
21
  members: readonly T[];
16
22
  consume(member: T, payload: AsyncIterable<Buffer>): Promise<void>;
17
23
  }): Promise<void>;
24
+ export {};
18
25
  //# sourceMappingURL=archive-tar-stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AA4ChF,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;kEACkE;AAClE,wBAAsB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE;IACnE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAClE,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
1
+ {"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,gFAAgF;AAChF,KAAK,QAAQ,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAuDhH,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;IAClD,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;2DAC2D;AAC3D,wBAAsB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,QAAQ,GAAG;IAC9E,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC7C,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
@@ -1,10 +1,14 @@
1
1
  import fs from "node:fs";
2
- import { Writable } from "node:stream";
2
+ import { Readable, Writable } from "node:stream";
3
3
  import { pipeline } from "node:stream/promises";
4
4
  import { createGunzip } from "node:zlib";
5
- import { GzipInput, validateGzipContainerTail } from "./archive-gzip-tail.js";
5
+ import { GzipInput, validateGzipBufferTail, validateGzipContainerTail } from "./archive-gzip-tail.js";
6
6
  import { ArchiveFormatError } from "./archive-errors.js";
7
7
  import { TarParserStream } from "./archive-tar-wasm.js";
8
+ function* bufferChunks(buffer) {
9
+ for (let offset = 0; offset < buffer.length; offset += 65536)
10
+ yield buffer.subarray(offset, offset + 65536);
11
+ }
8
12
  async function gzipFile(filePath) {
9
13
  const handle = await fs.promises.open(filePath, "r");
10
14
  try {
@@ -17,10 +21,14 @@ async function gzipFile(filePath) {
17
21
  }
18
22
  }
19
23
  async function withTarStream(params, consume) {
20
- const gzip = await gzipFile(params.archivePath);
24
+ const buffer = params.archiveBuffer;
25
+ const gzip = buffer !== undefined ? buffer[0] === 31 && buffer[1] === 139 : await gzipFile(params.archivePath);
21
26
  const parser = new TarParserStream(params.limits, params.onMember);
22
- const input = fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
23
- const decoder = gzip ? createGunzip() : undefined;
27
+ const input = buffer !== undefined
28
+ ? Readable.from(bufferChunks(buffer), { objectMode: false, highWaterMark: 65536 })
29
+ : fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
30
+ // Match the WASM input window for buffered reads instead of emitting 16 KiB chunks.
31
+ const decoder = gzip ? createGunzip(buffer === undefined ? undefined : { chunkSize: 65536 }) : undefined;
24
32
  const gzipInput = decoder ? new GzipInput(decoder) : undefined;
25
33
  const destroy = (error) => {
26
34
  input.destroy(error);
@@ -42,8 +50,12 @@ async function withTarStream(params, consume) {
42
50
  const error = await settled;
43
51
  if (error)
44
52
  throw error;
45
- if (gzipInput)
46
- await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
53
+ if (gzipInput) {
54
+ if (buffer !== undefined)
55
+ await validateGzipBufferTail(buffer, gzipInput.tailOffset, params.signal);
56
+ else
57
+ await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
58
+ }
47
59
  return result;
48
60
  }
49
61
  finally {
@@ -57,7 +69,7 @@ export async function inspectTar(params) {
57
69
  });
58
70
  }
59
71
  /** Replay in physical order, retaining at most one decoded chunk. Every range
60
- * comes from complete admission of the immutable staged input. */
72
+ * comes from complete admission of the immutable input. */
61
73
  export async function replayTar(params) {
62
74
  await withTarStream(params, async (parser) => {
63
75
  const iterator = parser[Symbol.asyncIterator]();
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuClE,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAER,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAD9D,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAQjG;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAkB/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAKjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAKpF;CACF"}
1
+ {"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAyClE,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAER,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAD9D,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAQjG;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAkB/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAKjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAKpF;CACF"}