@openclaw/fs-safe 0.7.2 → 0.8.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 (155) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +5 -1
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +9 -10
  5. package/dist/archive-merge.d.ts +18 -0
  6. package/dist/archive-merge.d.ts.map +1 -0
  7. package/dist/archive-merge.js +157 -0
  8. package/dist/archive-native.d.ts.map +1 -1
  9. package/dist/archive-native.js +25 -5
  10. package/dist/archive-policy.d.ts +1 -1
  11. package/dist/archive-policy.d.ts.map +1 -1
  12. package/dist/archive-policy.js +1 -1
  13. package/dist/archive-read.d.ts.map +1 -1
  14. package/dist/archive-read.js +3 -2
  15. package/dist/archive-staging.d.ts +13 -8
  16. package/dist/archive-staging.d.ts.map +1 -1
  17. package/dist/archive-staging.js +44 -192
  18. package/dist/archive-tar-meta.d.ts.map +1 -1
  19. package/dist/archive-tar-meta.js +9 -5
  20. package/dist/archive-tar-runtime.d.ts +1 -0
  21. package/dist/archive-tar-runtime.d.ts.map +1 -1
  22. package/dist/archive-zip-admission.d.ts +2 -1
  23. package/dist/archive-zip-admission.d.ts.map +1 -1
  24. package/dist/archive-zip-admission.js +2 -2
  25. package/dist/archive-zip-directory.d.ts +8 -1
  26. package/dist/archive-zip-directory.d.ts.map +1 -1
  27. package/dist/archive-zip-directory.js +6 -2
  28. package/dist/archive-zip-entry.d.ts +1 -1
  29. package/dist/archive-zip-entry.d.ts.map +1 -1
  30. package/dist/archive-zip-names.d.ts +1 -1
  31. package/dist/archive-zip-names.d.ts.map +1 -1
  32. package/dist/archive-zip-names.js +1 -0
  33. package/dist/archive.d.ts.map +1 -1
  34. package/dist/archive.js +17 -25
  35. package/dist/directory-guard.d.ts +17 -5
  36. package/dist/directory-guard.d.ts.map +1 -1
  37. package/dist/directory-guard.js +8 -6
  38. package/dist/directory-mode-node.d.ts +8 -0
  39. package/dist/directory-mode-node.d.ts.map +1 -0
  40. package/dist/directory-mode-node.js +93 -0
  41. package/dist/directory-mode-owner.d.ts +21 -0
  42. package/dist/directory-mode-owner.d.ts.map +1 -0
  43. package/dist/directory-mode-owner.js +73 -0
  44. package/dist/file-observation.d.ts +1 -1
  45. package/dist/file-observation.d.ts.map +1 -1
  46. package/dist/file-store-boundary.d.ts +2 -0
  47. package/dist/file-store-boundary.d.ts.map +1 -1
  48. package/dist/file-store-boundary.js +13 -0
  49. package/dist/file-store.d.ts.map +1 -1
  50. package/dist/file-store.js +8 -11
  51. package/dist/filename.d.ts +6 -0
  52. package/dist/filename.d.ts.map +1 -1
  53. package/dist/filename.js +36 -0
  54. package/dist/guarded-mkdir.d.ts +2 -0
  55. package/dist/guarded-mkdir.d.ts.map +1 -1
  56. package/dist/guarded-mkdir.js +2 -2
  57. package/dist/guarded-mutation.d.ts +2 -2
  58. package/dist/guarded-mutation.d.ts.map +1 -1
  59. package/dist/json-document-store.d.ts +3 -0
  60. package/dist/json-document-store.d.ts.map +1 -1
  61. package/dist/json-document-store.js +2 -0
  62. package/dist/json-durable-queue-ownership.d.ts +3 -1
  63. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  64. package/dist/json-durable-queue-ownership.js +7 -4
  65. package/dist/json-durable-queue.d.ts.map +1 -1
  66. package/dist/json-durable-queue.js +25 -28
  67. package/dist/local-roots.d.ts.map +1 -1
  68. package/dist/local-roots.js +18 -56
  69. package/dist/move-path-cleanup.d.ts +43 -0
  70. package/dist/move-path-cleanup.d.ts.map +1 -0
  71. package/dist/move-path-cleanup.js +159 -0
  72. package/dist/move-path.d.ts.map +1 -1
  73. package/dist/move-path.js +30 -84
  74. package/dist/native-operations.d.ts +2 -4
  75. package/dist/native-operations.d.ts.map +1 -1
  76. package/dist/native-operations.js +7 -9
  77. package/dist/native-pinned-write-windows.d.ts +2 -2
  78. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  79. package/dist/native-pinned-write-windows.js +5 -5
  80. package/dist/native-pinned-write.d.ts.map +1 -1
  81. package/dist/native-pinned-write.js +14 -4
  82. package/dist/native-staged-file.d.ts +2 -2
  83. package/dist/native-staged-file.d.ts.map +1 -1
  84. package/dist/opened-file-failure.d.ts +1 -0
  85. package/dist/opened-file-failure.d.ts.map +1 -1
  86. package/dist/opened-file-failure.js +4 -0
  87. package/dist/output-sibling.d.ts.map +1 -1
  88. package/dist/output-sibling.js +9 -3
  89. package/dist/pinned-write.d.ts +2 -2
  90. package/dist/pinned-write.d.ts.map +1 -1
  91. package/dist/pinned-write.js +32 -13
  92. package/dist/publish-file.d.ts.map +1 -1
  93. package/dist/publish-file.js +7 -3
  94. package/dist/regular-file.d.ts +1 -1
  95. package/dist/regular-file.d.ts.map +1 -1
  96. package/dist/regular-file.js +35 -6
  97. package/dist/replace-file-descriptor.d.ts +9 -1
  98. package/dist/replace-file-descriptor.d.ts.map +1 -1
  99. package/dist/replace-file-descriptor.js +58 -10
  100. package/dist/replace-file-temp-owner.d.ts +7 -0
  101. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  102. package/dist/replace-file-temp-owner.js +29 -0
  103. package/dist/replace-file.d.ts +1 -0
  104. package/dist/replace-file.d.ts.map +1 -1
  105. package/dist/replace-file.js +12 -38
  106. package/dist/root-context.d.ts +4 -0
  107. package/dist/root-context.d.ts.map +1 -1
  108. package/dist/root-context.js +19 -0
  109. package/dist/root-impl.d.ts +43 -0
  110. package/dist/root-impl.d.ts.map +1 -1
  111. package/dist/root-impl.js +13 -11
  112. package/dist/root-path-symlink.d.ts +6 -2
  113. package/dist/root-path-symlink.d.ts.map +1 -1
  114. package/dist/root-path-symlink.js +7 -6
  115. package/dist/root-path.d.ts +1 -0
  116. package/dist/root-path.d.ts.map +1 -1
  117. package/dist/root-path.js +6 -2
  118. package/dist/root-walk.d.ts.map +1 -1
  119. package/dist/root-walk.js +4 -0
  120. package/dist/root-write-verification.d.ts +3 -2
  121. package/dist/root-write-verification.d.ts.map +1 -1
  122. package/dist/root-write-verification.js +6 -0
  123. package/dist/secret-file.d.ts +9 -0
  124. package/dist/secret-file.d.ts.map +1 -1
  125. package/dist/secret-file.js +136 -74
  126. package/dist/sibling-temp.d.ts.map +1 -1
  127. package/dist/sibling-temp.js +9 -3
  128. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  129. package/dist/sidecar-lock-acquire.js +18 -5
  130. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  131. package/dist/sidecar-lock-reclaim.js +6 -4
  132. package/dist/sidecar-lock.d.ts.map +1 -1
  133. package/dist/sidecar-lock.js +26 -0
  134. package/dist/write-open-flags.d.ts +5 -0
  135. package/dist/write-open-flags.d.ts.map +1 -0
  136. package/dist/write-open-flags.js +35 -0
  137. package/docs/advanced.md +1 -1
  138. package/docs/archive.md +56 -7
  139. package/docs/atomic.md +8 -2
  140. package/docs/contributing.md +13 -0
  141. package/docs/durability.md +3 -2
  142. package/docs/errors.md +3 -1
  143. package/docs/file-store.md +38 -8
  144. package/docs/local-roots.md +4 -3
  145. package/docs/output.md +4 -1
  146. package/docs/private-file-store.md +11 -2
  147. package/docs/reading.md +3 -1
  148. package/docs/regular-file.md +16 -2
  149. package/docs/root.md +4 -2
  150. package/docs/secret-file.md +37 -2
  151. package/docs/sidecar-lock.md +32 -5
  152. package/docs/store.md +5 -1
  153. package/docs/temp.md +19 -1
  154. package/docs/writing.md +24 -3
  155. package/package.json +14 -10
@@ -5,11 +5,13 @@ import { ownExtractionDestinationMutation, } from "./archive-deadline.js";
5
5
  import { ArchiveSecurityError, } from "./archive-errors.js";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { formatErrorDetail } from "./error-detail.js";
8
- import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
8
+ import { root } from "./root.js";
9
9
  import { isNotFoundPathError, isPathInside } from "./path.js";
10
- import { resolveReadOpenFlags } from "./read-open-flags.js";
11
10
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
12
11
  import { getFsSafeTestHooks } from "./test-hooks.js";
12
+ import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
13
+ import { expandRelativePathWithHome } from "./root-context.js";
14
+ import { resolveRootPath } from "./root-path.js";
13
15
  const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
14
16
  const ARCHIVE_STAGING_MODE = 0o700;
15
17
  function checkExtractionDeadline(deadline) {
@@ -19,7 +21,7 @@ export { ArchiveSecurityError } from "./archive-errors.js";
19
21
  function symlinkTraversalError(originalPath) {
20
22
  return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${formatErrorDetail(originalPath)}`);
21
23
  }
22
- async function createDirectoryIdentityGuard(dir) {
24
+ export async function createDirectoryIdentityGuard(dir) {
23
25
  try {
24
26
  return await createAsyncDirectoryGuard(dir);
25
27
  }
@@ -30,7 +32,7 @@ async function createDirectoryIdentityGuard(dir) {
30
32
  throw err;
31
33
  }
32
34
  }
33
- async function assertDirectoryIdentityGuard(guard) {
35
+ export async function assertDirectoryIdentityGuard(guard) {
34
36
  try {
35
37
  await assertAsyncDirectoryGuard(guard);
36
38
  }
@@ -82,7 +84,7 @@ async function assertNoSymlinkTraversal(params) {
82
84
  }
83
85
  }
84
86
  }
85
- async function assertResolvedInsideDestination(params) {
87
+ export async function assertResolvedInsideDestination(params) {
86
88
  let resolved;
87
89
  try {
88
90
  resolved = await fs.realpath(params.targetPath);
@@ -109,8 +111,39 @@ async function mkdirArchiveOutput(params) {
109
111
  }
110
112
  }
111
113
  export async function prepareArchiveOutputPath(params) {
114
+ await prepareOutputPath(params);
115
+ }
116
+ export async function preparePrivateArchiveOutputPath(params, assertGuards) {
117
+ await prepareOutputPath(params, assertGuards, true);
118
+ }
119
+ async function prepareOutputPath(params, assertGuards, privateWorkingMode = false) {
112
120
  checkExtractionDeadline(params.deadline);
113
- const targetRoot = await root(params.destinationRealDir);
121
+ const targetRoot = privateWorkingMode ? {
122
+ async mkdir(relativePath) {
123
+ // Retain Root.mkdir's strict alias admission before the shared traversal,
124
+ // while selecting private creation modes without adding a public option.
125
+ const resolved = await resolveRootPath({
126
+ absolutePath: path.resolve(params.destinationRealDir, await expandRelativePathWithHome(relativePath)),
127
+ rootPath: params.destinationRealDir,
128
+ rootCanonicalPath: params.destinationRealDir,
129
+ boundaryLabel: "archive destination",
130
+ rejectSymlinks: true,
131
+ });
132
+ checkExtractionDeadline(params.deadline);
133
+ await mkdirPathComponentsWithGuards({
134
+ rootReal: params.destinationRealDir,
135
+ targetPath: resolved.canonicalPath,
136
+ mode: 0o700,
137
+ rejectSymlinks: true,
138
+ beforeComponent: async () => {
139
+ await assertDirectoryIdentityGuard(destinationGuard);
140
+ checkExtractionDeadline(params.deadline);
141
+ await assertGuards?.();
142
+ checkExtractionDeadline(params.deadline);
143
+ },
144
+ });
145
+ },
146
+ } : await root(params.destinationRealDir);
114
147
  checkExtractionDeadline(params.deadline);
115
148
  const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
116
149
  checkExtractionDeadline(params.deadline);
@@ -127,6 +160,8 @@ export async function prepareArchiveOutputPath(params) {
127
160
  await ownExtractionDestinationMutation(params.deadline, async () => {
128
161
  await assertDirectoryIdentityGuard(destinationGuard);
129
162
  checkExtractionDeadline(params.deadline);
163
+ await assertGuards?.();
164
+ checkExtractionDeadline(params.deadline);
130
165
  await mkdirArchiveOutput({
131
166
  targetRoot,
132
167
  relativePath: relPath,
@@ -151,6 +186,8 @@ export async function prepareArchiveOutputPath(params) {
151
186
  await ownExtractionDestinationMutation(params.deadline, async () => {
152
187
  await assertDirectoryIdentityGuard(destinationGuard);
153
188
  checkExtractionDeadline(params.deadline);
189
+ await assertGuards?.();
190
+ checkExtractionDeadline(params.deadline);
154
191
  await mkdirArchiveOutput({
155
192
  targetRoot,
156
193
  relativePath: parentRel,
@@ -168,104 +205,6 @@ export async function prepareArchiveOutputPath(params) {
168
205
  });
169
206
  checkExtractionDeadline(params.deadline);
170
207
  }
171
- async function chmodInsideDestinationBestEffort(params) {
172
- await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", params.destinationPath);
173
- checkExtractionDeadline(params.deadline);
174
- const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
175
- checkExtractionDeadline(params.deadline);
176
- await assertDirectoryIdentityGuard(destinationGuard);
177
- checkExtractionDeadline(params.deadline);
178
- const handle = await fs
179
- .open(params.destinationPath, resolveReadOpenFlags())
180
- .catch(() => null);
181
- checkExtractionDeadline(params.deadline);
182
- if (!handle) {
183
- const stat = await fs.lstat(params.destinationPath).catch(() => null);
184
- checkExtractionDeadline(params.deadline);
185
- if (stat?.isSymbolicLink()) {
186
- throw symlinkTraversalError(params.originalPath);
187
- }
188
- return;
189
- }
190
- try {
191
- const stat = await handle.stat();
192
- checkExtractionDeadline(params.deadline);
193
- if (!stat.isDirectory() && !stat.isFile()) {
194
- return;
195
- }
196
- const realPath = await resolveOpenedFileRealPathForHandle(handle, params.destinationPath);
197
- checkExtractionDeadline(params.deadline);
198
- if (!isPathInside(params.destinationRealDir, realPath)) {
199
- throw symlinkTraversalError(params.originalPath);
200
- }
201
- await ownExtractionDestinationMutation(params.deadline, async () => {
202
- await handle.chmod(params.mode).catch(() => undefined);
203
- checkExtractionDeadline(params.deadline);
204
- await assertDirectoryIdentityGuard(destinationGuard);
205
- checkExtractionDeadline(params.deadline);
206
- });
207
- }
208
- finally {
209
- await handle.close().catch(() => undefined);
210
- }
211
- }
212
- async function applyStagedEntryMode(params) {
213
- checkExtractionDeadline(params.deadline);
214
- const destinationPath = path.join(params.destinationRealDir, params.relPath);
215
- await assertResolvedInsideDestination({
216
- destinationRealDir: params.destinationRealDir,
217
- targetPath: destinationPath,
218
- originalPath: params.originalPath,
219
- });
220
- checkExtractionDeadline(params.deadline);
221
- if (params.mode !== 0) {
222
- await chmodInsideDestinationBestEffort({
223
- destinationRealDir: params.destinationRealDir,
224
- destinationPath,
225
- mode: params.mode,
226
- originalPath: params.originalPath,
227
- deadline: params.deadline,
228
- });
229
- }
230
- checkExtractionDeadline(params.deadline);
231
- }
232
- async function assertExtractedFileHasNoHardlinkAlias(params) {
233
- const destinationPath = path.join(params.destinationRealDir, params.relPath);
234
- await assertResolvedInsideDestination({
235
- destinationRealDir: params.destinationRealDir,
236
- targetPath: destinationPath,
237
- originalPath: params.originalPath,
238
- });
239
- const stat = await fs.lstat(destinationPath);
240
- if (stat.isFile() && stat.nlink > 1) {
241
- throw symlinkTraversalError(params.originalPath);
242
- }
243
- }
244
- async function removeExtractedDestinationFile(params) {
245
- const destinationPath = path.join(params.destinationRealDir, params.relPath);
246
- let stat;
247
- try {
248
- stat = await fs.lstat(destinationPath);
249
- }
250
- catch {
251
- return;
252
- }
253
- if (!stat.isFile()) {
254
- return;
255
- }
256
- let resolved;
257
- try {
258
- resolved = await fs.realpath(destinationPath);
259
- }
260
- catch {
261
- return;
262
- }
263
- if (!isPathInside(params.destinationRealDir, resolved)) {
264
- return;
265
- }
266
- const targetRoot = await root(params.destinationRealDir);
267
- await targetRoot.remove(params.relPath).catch(() => undefined);
268
- }
269
208
  function assertSafeArchiveStagingPrefix(prefix) {
270
209
  if (!prefix ||
271
210
  prefix === "." ||
@@ -305,94 +244,7 @@ export async function withStagedArchiveDestination(params) {
305
244
  }
306
245
  }
307
246
  }
308
- export async function mergeExtractedTreeIntoDestination(params) {
309
- const targetRoot = await root(params.destinationRealDir);
310
- const sourceRootGuard = await createDirectoryIdentityGuard(params.sourceDir);
311
- const sourceRootReal = sourceRootGuard.realPath;
312
- const walk = async (currentSourceDir) => {
313
- await assertDirectoryIdentityGuard(sourceRootGuard);
314
- const entries = await fs.readdir(currentSourceDir, { withFileTypes: true });
315
- for (const entry of entries) {
316
- await assertDirectoryIdentityGuard(sourceRootGuard);
317
- const sourcePath = path.join(currentSourceDir, entry.name);
318
- const relPath = path.relative(params.sourceDir, sourcePath);
319
- const originalPath = relPath.split(path.sep).join("/");
320
- const destinationPath = path.join(params.destinationDir, relPath);
321
- const sourceStat = await fs.lstat(sourcePath);
322
- if (sourceStat.isSymbolicLink()) {
323
- throw symlinkTraversalError(originalPath);
324
- }
325
- const sourceReal = await fs.realpath(sourcePath);
326
- if (!isPathInside(sourceRootReal, sourceReal)) {
327
- throw symlinkTraversalError(originalPath);
328
- }
329
- if (sourceStat.isDirectory()) {
330
- await prepareArchiveOutputPath({
331
- destinationDir: params.destinationDir,
332
- destinationRealDir: params.destinationRealDir,
333
- relPath,
334
- outPath: destinationPath,
335
- originalPath,
336
- isDirectory: true,
337
- deadline: params.deadline,
338
- });
339
- await walk(sourcePath);
340
- await applyStagedEntryMode({
341
- destinationRealDir: params.destinationRealDir,
342
- relPath,
343
- mode: sourceStat.mode & 0o777,
344
- originalPath,
345
- deadline: params.deadline,
346
- });
347
- continue;
348
- }
349
- if (!sourceStat.isFile()) {
350
- throw new Error(`archive staging contains unsupported entry: ${formatErrorDetail(originalPath)}`);
351
- }
352
- await prepareArchiveOutputPath({
353
- destinationDir: params.destinationDir,
354
- destinationRealDir: params.destinationRealDir,
355
- relPath,
356
- outPath: destinationPath,
357
- originalPath,
358
- isDirectory: false,
359
- deadline: params.deadline,
360
- });
361
- await ownExtractionDestinationMutation(params.deadline, async () => {
362
- try {
363
- await targetRoot.copyIn(relPath, sourcePath, { mkdir: true });
364
- checkExtractionDeadline(params.deadline);
365
- await assertExtractedFileHasNoHardlinkAlias({
366
- destinationRealDir: params.destinationRealDir,
367
- relPath,
368
- originalPath,
369
- });
370
- checkExtractionDeadline(params.deadline);
371
- await applyStagedEntryMode({
372
- destinationRealDir: params.destinationRealDir,
373
- relPath,
374
- mode: sourceStat.mode & 0o777,
375
- originalPath,
376
- deadline: params.deadline,
377
- });
378
- checkExtractionDeadline(params.deadline);
379
- }
380
- catch (err) {
381
- await removeExtractedDestinationFile({
382
- destinationRealDir: params.destinationRealDir,
383
- relPath,
384
- });
385
- if (err instanceof FsSafeError &&
386
- (err.code === "hardlink" || err.code === "path-alias")) {
387
- throw symlinkTraversalError(originalPath);
388
- }
389
- throw err;
390
- }
391
- });
392
- }
393
- };
394
- await walk(params.sourceDir);
395
- }
247
+ export { mergeExtractedTreeIntoDestination } from "./archive-merge.js";
396
248
  export function createArchiveSymlinkTraversalError(originalPath) {
397
249
  return symlinkTraversalError(originalPath);
398
250
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-meta.d.ts","sourceRoot":"","sources":["../src/archive-tar-meta.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAY,MAAM,aAAa,CAAC;AAIlD,OAAO,EAAqE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI7H,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAWrD,qBAAa,gBAAiB,SAAQ,SAAS;IAYjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAkB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAX/E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IACnD,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,aAAa,CAAK;IAE1B,YAA6B,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,aAAA,EAM7G;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,YAAY;IA6EpB,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,KAAK;IAyCJ,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAc3H;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAM9D;CACF;AAaD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC1C,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
1
+ {"version":3,"file":"archive-tar-meta.d.ts","sourceRoot":"","sources":["../src/archive-tar-meta.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAY,MAAM,aAAa,CAAC;AAIlD,OAAO,EAAqE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI7H,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAWrD,qBAAa,gBAAiB,SAAQ,SAAS;IAYjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAkB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAX/E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IACnD,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,aAAa,CAAK;IAE1B,YAA6B,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,aAAA,EAM7G;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,YAAY;IA6EpB,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,KAAK;IAyCJ,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAc3H;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAM9D;CACF;AAaD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC1C,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB"}
@@ -261,13 +261,17 @@ async function isGzip(filePath) {
261
261
  }
262
262
  }
263
263
  export async function preflightTarMetadata(params) {
264
- const input = fs.createReadStream(params.archivePath);
265
264
  const meter = new TarMetadataMeter(params.limits, params.onMember);
266
265
  const sink = new Writable({ write(_chunk, _encoding, callback) { callback(); } });
267
- if (await isGzip(params.archivePath)) {
268
- await pipeline(input, createGunzip(), meter, sink, { signal: params.signal });
266
+ const gzip = await isGzip(params.archivePath);
267
+ const decoder = gzip ? createGunzip() : undefined;
268
+ const input = fs.createReadStream(params.archivePath);
269
+ try {
270
+ await (decoder
271
+ ? pipeline(input, decoder, meter, sink, { signal: params.signal })
272
+ : pipeline(input, meter, sink, { signal: params.signal }));
269
273
  }
270
- else {
271
- await pipeline(input, meter, sink, { signal: params.signal });
274
+ finally {
275
+ input.destroy();
272
276
  }
273
277
  }
@@ -27,6 +27,7 @@ export type TarModule = {
27
27
  noChmod: true;
28
28
  preserveOwner: false;
29
29
  noMtime: true;
30
+ dmode: number;
30
31
  strict: true;
31
32
  maxMetaEntrySize: number;
32
33
  maxDecompressionRatio: number;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-runtime.d.ts","sourceRoot":"","sources":["../src/archive-tar-runtime.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,GAAG;IAC9C,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC1B,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IAChF,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IACzE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC;IACnE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC;IAChE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,SAAS,CAAC;CACnD,CAAC;AAIF,eAAO,MAAM,uBAAuB,QAA2B,CAAC;AAEhE,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,KAAK,OAAO,EAAE;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAC;IAC/E,CAAC,CAAC,OAAO,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,aAAa,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC;QACd,aAAa,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC;QACd,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,MAAM,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QACrE,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;KAClD,GAAG,SAAS,CAAC;IACd,CAAC,CAAC,OAAO,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG;YAAE,MAAM,IAAI,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;KACvE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAEF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAS5D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS/D"}
1
+ {"version":3,"file":"archive-tar-runtime.d.ts","sourceRoot":"","sources":["../src/archive-tar-runtime.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,GAAG;IAC9C,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC1B,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IAChF,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IACzE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC;IACnE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC;IAChE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,SAAS,CAAC;CACnD,CAAC;AAIF,eAAO,MAAM,uBAAuB,QAA2B,CAAC;AAEhE,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,KAAK,OAAO,EAAE;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAC;IAC/E,CAAC,CAAC,OAAO,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,aAAa,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC;QACd,aAAa,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,MAAM,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QACrE,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;KAClD,GAAG,SAAS,CAAC;IACd,CAAC,CAAC,OAAO,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG;YAAE,MAAM,IAAI,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;KACvE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAEF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAS5D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS/D"}
@@ -1,7 +1,8 @@
1
1
  import type { ExtractionDeadline } from "./archive-deadline.js";
2
2
  import { type ResolvedArchiveExtractLimits } from "./archive-limits.js";
3
+ import { type ZipDirectoryEntry } from "./archive-zip-directory.js";
3
4
  /** Admit physical records before a decoder can normalize or collapse names. */
4
5
  export declare function admitZipBuffer(input: Uint8Array, limits: ResolvedArchiveExtractLimits): number;
5
6
  /** The extraction input is already private and staged; read only bounded metadata. */
6
- export declare function admitZipFile(archivePath: string, limits: ResolvedArchiveExtractLimits, deadline: ExtractionDeadline): Promise<number>;
7
+ export declare function admitZipFile(archivePath: string, limits: ResolvedArchiveExtractLimits, deadline: ExtractionDeadline, onEntry?: (entry: ZipDirectoryEntry) => void): Promise<number>;
7
8
  //# sourceMappingURL=archive-zip-admission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-admission.d.ts","sourceRoot":"","sources":["../src/archive-zip-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAA+C,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAYrH,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,GAAG,MAAM,CAU9F;AAED,sFAAsF;AACtF,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+B3I"}
1
+ {"version":3,"file":"archive-zip-admission.d.ts","sourceRoot":"","sources":["../src/archive-zip-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAA+C,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAErH,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAUtF,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,GAAG,MAAM,CAU9F;AAED,sFAAsF;AACtF,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,kBAAkB,EACvF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC,CA+BjB"}
@@ -24,7 +24,7 @@ export function admitZipBuffer(input, limits) {
24
24
  return step.value;
25
25
  }
26
26
  /** The extraction input is already private and staged; read only bounded metadata. */
27
- export async function admitZipFile(archivePath, limits, deadline) {
27
+ export async function admitZipFile(archivePath, limits, deadline, onEntry) {
28
28
  deadline.check();
29
29
  const opened = await openRootFile({
30
30
  absolutePath: archivePath, rootPath: path.dirname(archivePath),
@@ -34,7 +34,7 @@ export async function admitZipFile(archivePath, limits, deadline) {
34
34
  throw opened.error ?? new Error("cannot open staged ZIP archive");
35
35
  try {
36
36
  checkSize(opened.stat.size, limits);
37
- const scan = scanZipDirectory(opened.stat.size, limits);
37
+ const scan = scanZipDirectory(opened.stat.size, limits, onEntry);
38
38
  let step = scan.next();
39
39
  while (!step.done) {
40
40
  const { offset, length } = step.value;
@@ -4,5 +4,12 @@ export type ZipRead = {
4
4
  length: number;
5
5
  };
6
6
  export type ZipScan = Generator<ZipRead, number, Buffer>;
7
- export declare function scanZipDirectory(size: number, limits: ResolvedArchiveExtractLimits): ZipScan;
7
+ export type ZipDirectoryEntry = {
8
+ index: number;
9
+ creatorSystem: number;
10
+ externalAttributes: number;
11
+ size: number;
12
+ path?: string;
13
+ };
14
+ export declare function scanZipDirectory(size: number, limits: ResolvedArchiveExtractLimits, onEntry?: (entry: ZipDirectoryEntry) => void): ZipScan;
8
15
  //# sourceMappingURL=archive-zip-directory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAmIzD,wBAAiB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,GAAG,OAAO,CA8D7F"}
1
+ {"version":3,"file":"archive-zip-directory.d.ts","sourceRoot":"","sources":["../src/archive-zip-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAmIF,wBAAiB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC/F,OAAO,CAkET"}
@@ -150,7 +150,7 @@ function* descriptorEnd(at, bound, crc, compressed, uncompressed, wide) {
150
150
  zipFormat("invalid or ambiguous data descriptor");
151
151
  return matches[0];
152
152
  }
153
- export function* scanZipDirectory(size, limits) {
153
+ export function* scanZipDirectory(size, limits, onEntry) {
154
154
  const directory = yield* layout(size);
155
155
  assertArchiveEntryCountWithinLimit(directory.count, limits);
156
156
  const seen = new Set();
@@ -193,7 +193,7 @@ export function* scanZipDirectory(size, limits) {
193
193
  const localExtraLength = local.readUInt16LE(28);
194
194
  const localNames = yield* read(localAt + 30, localNameLength + localExtraLength, directory.start);
195
195
  const localExtra = zipExtraFields(localNames.subarray(localNameLength));
196
- admitZipNames({ central: centralName, local: localNames.subarray(0, localNameLength), flags, centralExtra, localExtra, seen });
196
+ const entryPath = admitZipNames({ central: centralName, local: localNames.subarray(0, localNameLength), flags, centralExtra, localExtra, seen });
197
197
  const localValues = wideValues(local, localExtra, false);
198
198
  const crc = central.readUInt32LE(16);
199
199
  if (!(flags & 8) && (local.readUInt32LE(14) !== crc || localValues.compressed !== values.compressed || localValues.uncompressed !== values.uncompressed)) {
@@ -211,6 +211,10 @@ export function* scanZipDirectory(size, limits) {
211
211
  if (flags & 8)
212
212
  dataEnd = yield* descriptorEnd(dataEnd, directory.start, crc, values.compressed, values.uncompressed, values.wide || localValues.wide);
213
213
  spans.push({ start: localAt, end: dataEnd });
214
+ onEntry?.({
215
+ index: count - 1, creatorSystem: central[5], externalAttributes: central.readUInt32LE(38),
216
+ size: values.uncompressed, path: entryPath,
217
+ });
214
218
  at = next;
215
219
  }
216
220
  if (count !== directory.count)
@@ -2,7 +2,7 @@ import { type ArchiveEntryModePolicy } from "./archive-policy.js";
2
2
  export type ZipEntry = {
3
3
  name: string;
4
4
  dir: boolean;
5
- unixPermissions?: number;
5
+ unixPermissions?: number | null;
6
6
  _data?: {
7
7
  crc32?: number;
8
8
  uncompressedSize?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-entry.d.ts","sourceRoot":"","sources":["../src/archive-zip-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7E,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,cAAc,CAAC;IACzC,KAAK,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChD,CAAC;AAEF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,QAAQ,GACd;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAI3D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAGhE;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAK1D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,MAAM,CAMR;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE5D"}
1
+ {"version":3,"file":"archive-zip-entry.d.ts","sourceRoot":"","sources":["../src/archive-zip-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7E,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,cAAc,CAAC;IACzC,KAAK,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChD,CAAC;AAEF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,QAAQ,GACd;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAI3D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAGhE;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAK1D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,MAAM,CAMR;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE5D"}
@@ -8,5 +8,5 @@ export declare function admitZipNames(params: {
8
8
  centralExtra: Map<number, Buffer>;
9
9
  localExtra: Map<number, Buffer>;
10
10
  seen: Set<string>;
11
- }): void;
11
+ }): string | undefined;
12
12
  //# sourceMappingURL=archive-zip-names.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AA6BD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG,IAAI,CA6BP"}
1
+ {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AA6BD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG,MAAM,GAAG,SAAS,CA8BrB"}
@@ -96,4 +96,5 @@ export function admitZipNames(params) {
96
96
  }
97
97
  for (const identity of identities)
98
98
  seen.add(identity);
99
+ return centralUnicode ?? centralUtf8 ?? (central.every((byte) => byte < 128) ? central.toString("ascii") : undefined);
99
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAyNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6KjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AA6MpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqKjF"}
package/dist/archive.js CHANGED
@@ -7,7 +7,8 @@ import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchiveP
7
7
  import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
8
8
  import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, resolveTarMeterLimits, } from "./archive-limits.js";
9
9
  import { resolveArchiveKind } from "./archive-kind.js";
10
- import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
10
+ import { prepareArchiveDestinationDir, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
11
+ import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
11
12
  import { createTarEntryPreflightChecker, readTarEntryInfo, } from "./archive-tar.js";
12
13
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
13
14
  import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
@@ -64,9 +65,6 @@ function resolveZipOutputPath(params) {
64
65
  }),
65
66
  };
66
67
  }
67
- async function prepareZipOutputPath(params) {
68
- await prepareArchiveOutputPath(params);
69
- }
70
68
  async function writeZipFileEntry(params) {
71
69
  params.deadline.check();
72
70
  params.budget.startEntry();
@@ -78,9 +76,9 @@ async function writeZipFileEntry(params) {
78
76
  await writeSiblingTempFile({
79
77
  dir: path.dirname(destinationPath),
80
78
  chmodDir: false,
81
- mode: params.mode,
79
+ mode: 0o600,
82
80
  writeTemp: async (tempPath) => {
83
- tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o666);
81
+ tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
84
82
  const writable = tempHandle.createWriteStream();
85
83
  writable.once("close", () => {
86
84
  handleClosedByStream = true;
@@ -137,6 +135,7 @@ async function extractZip(params) {
137
135
  destinationRealDir,
138
136
  run: async (stagingDir) => {
139
137
  const stagingRealDir = await fs.realpath(stagingDir);
138
+ const acceptedEntries = [];
140
139
  for (const entry of entries) {
141
140
  params.deadline.check();
142
141
  const output = resolveZipOutputPath({
@@ -163,7 +162,8 @@ async function extractZip(params) {
163
162
  throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
164
163
  }
165
164
  const mode = zipEntryMode(entry, params.entryModes);
166
- await prepareZipOutputPath({
165
+ acceptedEntries.push({ path: output.relPath, kind: entry.dir ? "directory" : "file", mode });
166
+ await preparePrivateArchiveOutputPath({
167
167
  destinationDir: stagingRealDir,
168
168
  destinationRealDir: stagingRealDir,
169
169
  relPath: output.relPath,
@@ -173,7 +173,6 @@ async function extractZip(params) {
173
173
  deadline: params.deadline,
174
174
  });
175
175
  if (entry.dir) {
176
- await fs.chmod(output.outPath, mode);
177
176
  continue;
178
177
  }
179
178
  await writeZipFileEntry({
@@ -181,11 +180,11 @@ async function extractZip(params) {
181
180
  outPath: output.outPath,
182
181
  budget,
183
182
  deadline: params.deadline,
184
- mode,
185
183
  });
186
184
  }
187
185
  params.deadline.check();
188
- await mergeExtractedTreeIntoDestination({
186
+ await mergePlannedArchiveIntoDestination({
187
+ entries: acceptedEntries,
189
188
  sourceDir: stagingRealDir,
190
189
  destinationDir: params.destDir,
191
190
  destinationRealDir,
@@ -276,6 +275,7 @@ export async function extractArchive(params) {
276
275
  noChmod: true,
277
276
  preserveOwner: false,
278
277
  noMtime: true,
278
+ dmode: 0o700,
279
279
  strict: true,
280
280
  maxMetaEntrySize: tarLimits.maxMetaEntryBytes,
281
281
  maxDecompressionRatio: NODE_TAR_RATIO_DISABLED,
@@ -286,18 +286,19 @@ export async function extractArchive(params) {
286
286
  if (!relPath) {
287
287
  return false;
288
288
  }
289
- // Writes and the mode pass use the admitted output identity.
289
+ const kind = info.type === "Directory" || info.type === "GNUDumpDir" ? "directory" : "file";
290
+ // Keep archived modes before changing node-tar's creation mode.
290
291
  entry.path = relPath;
291
292
  acceptedEntries.push({
292
293
  path: relPath,
294
+ kind,
293
295
  mode: resolveArchiveEntryMode({
294
- kind: info.type === "Directory" || info.type === "GNUDumpDir"
295
- ? "directory"
296
- : "file",
296
+ kind,
297
297
  archivedMode: info.mode,
298
298
  policy: params.entryModes,
299
299
  }),
300
300
  });
301
+ entry.mode = kind === "directory" ? 0o700 : 0o600;
301
302
  return true;
302
303
  }
303
304
  catch (error) {
@@ -329,18 +330,9 @@ export async function extractArchive(params) {
329
330
  throw normalizeTarParserError(createPipelineTimeoutError(error, deadline));
330
331
  }
331
332
  admission.finish();
332
- for (const accepted of acceptedEntries) {
333
- deadline.check();
334
- const outputPath = resolveArchiveOutputPath({
335
- rootDir: stagingDir,
336
- relPath: accepted.path,
337
- originalPath: accepted.path,
338
- });
339
- await fs.chmod(outputPath, accepted.mode);
340
- deadline.check();
341
- }
342
333
  deadline.check();
343
- await mergeExtractedTreeIntoDestination({
334
+ await mergePlannedArchiveIntoDestination({
335
+ entries: acceptedEntries,
344
336
  sourceDir: stagingDir,
345
337
  destinationDir: params.destDir,
346
338
  destinationRealDir,
@@ -1,19 +1,31 @@
1
1
  import type { BigIntStats, Stats } from "node:fs";
2
- export type AsyncDirectoryGuard = {
2
+ export type AsyncDirectoryGuard<T extends Stats | BigIntStats = Stats> = {
3
3
  dir: string;
4
4
  realPath: string;
5
- stat: Stats;
5
+ stat: T;
6
6
  };
7
+ export type AnyAsyncDirectoryGuard = AsyncDirectoryGuard<Stats | BigIntStats>;
7
8
  export type SyncDirectoryGuard = {
8
9
  dir: string;
9
10
  realPath: string;
10
11
  stat: Stats;
11
12
  };
12
- export declare function createAsyncDirectoryGuard(dir: string): Promise<AsyncDirectoryGuard>;
13
- export declare function assertAsyncDirectoryGuard(guard: AsyncDirectoryGuard): Promise<void>;
13
+ export declare function createAsyncDirectoryGuard(dir: string, options: {
14
+ bigint: true;
15
+ }): Promise<AsyncDirectoryGuard<BigIntStats>>;
16
+ export declare function createAsyncDirectoryGuard(dir: string, options?: {
17
+ bigint?: false;
18
+ }): Promise<AsyncDirectoryGuard>;
19
+ export declare function createAsyncDirectoryGuard(dir: string, options: {
20
+ bigint: boolean;
21
+ }): Promise<AnyAsyncDirectoryGuard>;
22
+ export declare function assertAsyncDirectoryGuard(guard: AnyAsyncDirectoryGuard): Promise<void>;
14
23
  export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuard;
15
24
  export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard): void;
16
25
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
26
+ export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
27
+ bigint: boolean;
28
+ }): Promise<AnyAsyncDirectoryGuard>;
17
29
  export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
18
- export declare function inspectDirectoryIdentity(dir: string, expected?: BigIntStats): Promise<BigIntStats>;
30
+ export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
19
31
  //# sourceMappingURL=directory-guard.d.ts.map