@openclaw/fs-safe 0.4.7 → 0.5.1

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 (247) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +82 -43
  3. package/dist/archive-entry.d.ts.map +1 -1
  4. package/dist/archive-entry.js +5 -4
  5. package/dist/archive-errors.d.ts +11 -0
  6. package/dist/archive-errors.d.ts.map +1 -0
  7. package/dist/archive-errors.js +16 -0
  8. package/dist/archive-input.d.ts +12 -0
  9. package/dist/archive-input.d.ts.map +1 -0
  10. package/dist/archive-input.js +72 -0
  11. package/dist/archive-kind.d.ts +1 -1
  12. package/dist/archive-kind.d.ts.map +1 -1
  13. package/dist/archive-kind.js +18 -0
  14. package/dist/archive-limits.d.ts +10 -0
  15. package/dist/archive-limits.d.ts.map +1 -1
  16. package/dist/archive-limits.js +18 -0
  17. package/dist/archive-native.d.ts +18 -0
  18. package/dist/archive-native.d.ts.map +1 -0
  19. package/dist/archive-native.js +115 -0
  20. package/dist/archive-options.d.ts +21 -0
  21. package/dist/archive-options.d.ts.map +1 -0
  22. package/dist/archive-options.js +1 -0
  23. package/dist/archive-policy.d.ts +20 -0
  24. package/dist/archive-policy.d.ts.map +1 -0
  25. package/dist/archive-policy.js +29 -0
  26. package/dist/archive-read.d.ts +6 -0
  27. package/dist/archive-read.d.ts.map +1 -0
  28. package/dist/archive-read.js +200 -0
  29. package/dist/archive-staging.d.ts +2 -5
  30. package/dist/archive-staging.d.ts.map +1 -1
  31. package/dist/archive-staging.js +19 -10
  32. package/dist/archive-tar-meta.d.ts +6 -0
  33. package/dist/archive-tar-meta.d.ts.map +1 -0
  34. package/dist/archive-tar-meta.js +143 -0
  35. package/dist/archive-tar-runtime.d.ts +43 -0
  36. package/dist/archive-tar-runtime.d.ts.map +1 -0
  37. package/dist/archive-tar-runtime.js +8 -0
  38. package/dist/archive-tar.d.ts +5 -1
  39. package/dist/archive-tar.d.ts.map +1 -1
  40. package/dist/archive-tar.js +24 -6
  41. package/dist/archive-zip-entry.d.ts +15 -0
  42. package/dist/archive-zip-entry.d.ts.map +1 -0
  43. package/dist/archive-zip-entry.js +17 -0
  44. package/dist/archive.d.ts +7 -17
  45. package/dist/archive.d.ts.map +1 -1
  46. package/dist/archive.js +111 -108
  47. package/dist/atomic.d.ts +1 -1
  48. package/dist/atomic.d.ts.map +1 -1
  49. package/dist/config.d.ts +1 -1
  50. package/dist/config.d.ts.map +1 -1
  51. package/dist/config.js +1 -1
  52. package/dist/containment.d.ts +2 -0
  53. package/dist/containment.d.ts.map +1 -0
  54. package/dist/containment.js +1 -0
  55. package/dist/device-path.d.ts.map +1 -1
  56. package/dist/device-path.js +24 -2
  57. package/dist/durability.d.ts +2 -0
  58. package/dist/durability.d.ts.map +1 -1
  59. package/dist/durability.js +2 -0
  60. package/dist/errors.d.ts +4 -1
  61. package/dist/errors.d.ts.map +1 -1
  62. package/dist/errors.js +2 -0
  63. package/dist/file-hash.d.ts +10 -0
  64. package/dist/file-hash.d.ts.map +1 -0
  65. package/dist/file-hash.js +73 -0
  66. package/dist/file-lock-sync.d.ts +35 -0
  67. package/dist/file-lock-sync.d.ts.map +1 -0
  68. package/dist/file-lock-sync.js +233 -0
  69. package/dist/file-lock.d.ts +2 -0
  70. package/dist/file-lock.d.ts.map +1 -1
  71. package/dist/file-lock.js +1 -0
  72. package/dist/filename.d.ts.map +1 -1
  73. package/dist/filename.js +4 -1
  74. package/dist/guarded-mkdir.d.ts +7 -1
  75. package/dist/guarded-mkdir.d.ts.map +1 -1
  76. package/dist/guarded-mkdir.js +45 -3
  77. package/dist/index.d.ts +4 -3
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/json-document-store.d.ts.map +1 -1
  81. package/dist/json-document-store.js +30 -15
  82. package/dist/move-path.d.ts.map +1 -1
  83. package/dist/move-path.js +101 -17
  84. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  85. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  86. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  87. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  88. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  89. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  90. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
  91. package/dist/native-binding.d.ts +79 -0
  92. package/dist/native-binding.d.ts.map +1 -0
  93. package/dist/native-binding.js +1 -0
  94. package/dist/native-config.d.ts +17 -0
  95. package/dist/native-config.d.ts.map +1 -0
  96. package/dist/native-config.js +71 -0
  97. package/dist/native-operations.d.ts +21 -0
  98. package/dist/native-operations.d.ts.map +1 -0
  99. package/dist/native-operations.js +115 -0
  100. package/dist/native-pinned-write.d.ts +5 -0
  101. package/dist/native-pinned-write.d.ts.map +1 -0
  102. package/dist/native-pinned-write.js +115 -0
  103. package/dist/native.d.ts +14 -0
  104. package/dist/native.d.ts.map +1 -0
  105. package/dist/native.js +204 -0
  106. package/dist/output-sibling.d.ts +8 -0
  107. package/dist/output-sibling.d.ts.map +1 -0
  108. package/dist/output-sibling.js +122 -0
  109. package/dist/output.d.ts +2 -0
  110. package/dist/output.d.ts.map +1 -1
  111. package/dist/output.js +32 -5
  112. package/dist/owner-dacl.d.ts +31 -0
  113. package/dist/owner-dacl.d.ts.map +1 -0
  114. package/dist/owner-dacl.js +27 -0
  115. package/dist/path-policy.js +1 -1
  116. package/dist/path.d.ts.map +1 -1
  117. package/dist/path.js +3 -2
  118. package/dist/permissions-public.d.ts +2 -0
  119. package/dist/permissions-public.d.ts.map +1 -1
  120. package/dist/permissions-public.js +2 -0
  121. package/dist/permissions.d.ts.map +1 -1
  122. package/dist/permissions.js +6 -0
  123. package/dist/pinned-operation.d.ts +2 -0
  124. package/dist/pinned-operation.d.ts.map +1 -0
  125. package/dist/pinned-operation.js +35 -0
  126. package/dist/pinned-write.d.ts +3 -16
  127. package/dist/pinned-write.d.ts.map +1 -1
  128. package/dist/pinned-write.js +20 -85
  129. package/dist/private-directory.d.ts +5 -0
  130. package/dist/private-directory.d.ts.map +1 -0
  131. package/dist/private-directory.js +13 -0
  132. package/dist/private-temp-workspace.d.ts +6 -2
  133. package/dist/private-temp-workspace.d.ts.map +1 -1
  134. package/dist/private-temp-workspace.js +40 -13
  135. package/dist/publish-file-failure.d.ts +27 -0
  136. package/dist/publish-file-failure.d.ts.map +1 -0
  137. package/dist/publish-file-failure.js +21 -0
  138. package/dist/publish-file.d.ts +21 -0
  139. package/dist/publish-file.d.ts.map +1 -0
  140. package/dist/publish-file.js +351 -0
  141. package/dist/read-opened-file.d.ts +3 -0
  142. package/dist/read-opened-file.d.ts.map +1 -1
  143. package/dist/read-opened-file.js +1 -0
  144. package/dist/replace-file-copy-fallback.d.ts +34 -0
  145. package/dist/replace-file-copy-fallback.d.ts.map +1 -0
  146. package/dist/replace-file-copy-fallback.js +339 -0
  147. package/dist/replace-file.d.ts +6 -2
  148. package/dist/replace-file.d.ts.map +1 -1
  149. package/dist/replace-file.js +39 -90
  150. package/dist/root-context.d.ts +4 -1
  151. package/dist/root-context.d.ts.map +1 -1
  152. package/dist/root-context.js +20 -1
  153. package/dist/root-impl.d.ts +5 -0
  154. package/dist/root-impl.d.ts.map +1 -1
  155. package/dist/root-impl.js +128 -225
  156. package/dist/root-path-existing.d.ts +3 -0
  157. package/dist/root-path-existing.d.ts.map +1 -0
  158. package/dist/root-path-existing.js +69 -0
  159. package/dist/root-path.d.ts +1 -2
  160. package/dist/root-path.d.ts.map +1 -1
  161. package/dist/root-path.js +67 -81
  162. package/dist/root-walk.d.ts +41 -0
  163. package/dist/root-walk.d.ts.map +1 -0
  164. package/dist/root-walk.js +114 -0
  165. package/dist/root.d.ts +2 -0
  166. package/dist/root.d.ts.map +1 -1
  167. package/dist/safe-path-segment.d.ts.map +1 -1
  168. package/dist/safe-path-segment.js +16 -4
  169. package/dist/secret-file.d.ts +5 -2
  170. package/dist/secret-file.d.ts.map +1 -1
  171. package/dist/secret-file.js +20 -2
  172. package/dist/secret-read-async.d.ts +4 -0
  173. package/dist/secret-read-async.d.ts.map +1 -0
  174. package/dist/secret-read-async.js +95 -0
  175. package/dist/secret.d.ts +2 -1
  176. package/dist/secret.d.ts.map +1 -1
  177. package/dist/secret.js +2 -1
  178. package/dist/sibling-temp.d.ts.map +1 -1
  179. package/dist/sibling-temp.js +1 -0
  180. package/dist/sidecar-lock-handle.d.ts +20 -0
  181. package/dist/sidecar-lock-handle.d.ts.map +1 -0
  182. package/dist/sidecar-lock-handle.js +28 -0
  183. package/dist/sidecar-lock-policy.d.ts +10 -0
  184. package/dist/sidecar-lock-policy.d.ts.map +1 -0
  185. package/dist/sidecar-lock-policy.js +29 -0
  186. package/dist/sidecar-lock-reclaim.d.ts +22 -6
  187. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  188. package/dist/sidecar-lock-reclaim.js +106 -20
  189. package/dist/sidecar-lock-types.d.ts +56 -0
  190. package/dist/sidecar-lock-types.d.ts.map +1 -0
  191. package/dist/sidecar-lock-types.js +1 -0
  192. package/dist/sidecar-lock.d.ts +2 -45
  193. package/dist/sidecar-lock.d.ts.map +1 -1
  194. package/dist/sidecar-lock.js +94 -55
  195. package/dist/temp-cleanup.d.ts +7 -1
  196. package/dist/temp-cleanup.d.ts.map +1 -1
  197. package/dist/temp-cleanup.js +35 -5
  198. package/dist/temp-target.d.ts.map +1 -1
  199. package/dist/temp-target.js +51 -3
  200. package/dist/temp.d.ts +2 -1
  201. package/dist/temp.d.ts.map +1 -1
  202. package/dist/test-hooks.d.ts +3 -0
  203. package/dist/test-hooks.d.ts.map +1 -1
  204. package/dist/windows-permissions-native.d.ts +9 -0
  205. package/dist/windows-permissions-native.d.ts.map +1 -0
  206. package/dist/windows-permissions-native.js +31 -0
  207. package/docs/archive.md +131 -13
  208. package/docs/atomic.md +30 -1
  209. package/docs/config.md +51 -21
  210. package/docs/durability.md +211 -1
  211. package/docs/errors.md +36 -6
  212. package/docs/filename.md +0 -0
  213. package/docs/index.md +11 -8
  214. package/docs/install.md +27 -26
  215. package/docs/json-store.md +18 -3
  216. package/docs/migrating-to-0.5.md +196 -0
  217. package/docs/native-helper.md +85 -0
  218. package/docs/native.md +134 -0
  219. package/docs/output.md +37 -11
  220. package/docs/path.md +1 -1
  221. package/docs/permissions.md +84 -1
  222. package/docs/quickstart.md +5 -2
  223. package/docs/reading.md +4 -4
  224. package/docs/root.md +31 -15
  225. package/docs/secret-file.md +50 -1
  226. package/docs/security-model.md +29 -8
  227. package/docs/sidecar-lock.md +92 -7
  228. package/docs/temp.md +24 -1
  229. package/docs/test-hooks.md +22 -2
  230. package/docs/testing.md +18 -7
  231. package/docs/types.md +3 -1
  232. package/docs/walk.md +55 -0
  233. package/docs/writing.md +3 -3
  234. package/package.json +10 -4
  235. package/dist/pinned-helper.d.ts +0 -9
  236. package/dist/pinned-helper.d.ts.map +0 -1
  237. package/dist/pinned-helper.js +0 -19
  238. package/dist/pinned-path.d.ts +0 -7
  239. package/dist/pinned-path.d.ts.map +0 -1
  240. package/dist/pinned-path.js +0 -21
  241. package/dist/pinned-python-config.d.ts +0 -9
  242. package/dist/pinned-python-config.d.ts.map +0 -1
  243. package/dist/pinned-python-config.js +0 -38
  244. package/dist/pinned-python.d.ts +0 -12
  245. package/dist/pinned-python.d.ts.map +0 -1
  246. package/dist/pinned-python.js +0 -688
  247. package/docs/python-helper.md +0 -103
@@ -0,0 +1,18 @@
1
+ import type { ExtractionDeadline } from "./archive-deadline.js";
2
+ import type { ArchiveKind } from "./archive-kind.js";
3
+ import { type ArchiveExtractLimits } from "./archive-limits.js";
4
+ import type { ExtractArchiveOptions } from "./archive-options.js";
5
+ import type { NativeBinding } from "./native.js";
6
+ export declare function extractNativeArchive(params: {
7
+ binding: NativeBinding;
8
+ archivePath: string;
9
+ destDir: string;
10
+ kind: ArchiveKind;
11
+ stripComponents?: number;
12
+ limits?: ArchiveExtractLimits;
13
+ deadline: ExtractionDeadline;
14
+ entryModes?: ExtractArchiveOptions["entryModes"];
15
+ entryFilter?: ExtractArchiveOptions["entryFilter"];
16
+ onFiltered?: ExtractArchiveOptions["onFiltered"];
17
+ }): Promise<void>;
18
+ //# sourceMappingURL=archive-native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-native.d.ts","sourceRoot":"","sources":["../src/archive-native.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAOL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAOlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoBjD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,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,CAAC,EAAE,oBAAoB,CAAC;IAC9B,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,CAsHhB"}
@@ -0,0 +1,115 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
4
+ import { resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath } from "./archive-entry.js";
5
+ import { stageArchiveFileForExtraction } from "./archive-input.js";
6
+ import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, resolveExtractLimits, } from "./archive-limits.js";
7
+ import { resolveArchiveEntryMode, shouldExtractArchiveEntry } from "./archive-policy.js";
8
+ import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, withStagedArchiveDestination, } from "./archive-staging.js";
9
+ function policyKind(kind) {
10
+ if (kind === "file" || kind === "directory")
11
+ return kind;
12
+ if (kind === "symlink" || kind === "hardlink")
13
+ return "symlink";
14
+ return "other";
15
+ }
16
+ function throwMappedNativeError(error) {
17
+ if (error instanceof Error) {
18
+ for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
19
+ if (error.message.includes(code))
20
+ throw new ArchiveLimitError(code);
21
+ }
22
+ if (error.message.includes("archive-header-invalid")) {
23
+ throw new ArchiveFormatError(error.message, { cause: error });
24
+ }
25
+ }
26
+ throw error;
27
+ }
28
+ export async function extractNativeArchive(params) {
29
+ const limits = resolveExtractLimits(params.limits);
30
+ const stagedArchive = await stageArchiveFileForExtraction({
31
+ archivePath: params.archivePath,
32
+ limits,
33
+ deadline: params.deadline,
34
+ });
35
+ try {
36
+ const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
37
+ await withStagedArchiveDestination({
38
+ destinationRealDir,
39
+ run: async (stagingDir) => {
40
+ params.deadline.check();
41
+ const manifest = await params.binding
42
+ .inspectArchiveNative(stagedArchive.path, params.kind, limits.maxEntries, limits.maxMetaEntryBytes, limits.maxArchiveBytes, params.deadline.signal)
43
+ .catch(throwMappedNativeError);
44
+ params.deadline.check();
45
+ assertArchiveEntryCountWithinLimit(manifest.length, limits);
46
+ const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
47
+ const budget = createByteBudgetTracker(limits);
48
+ const plan = [];
49
+ for (const entry of manifest) {
50
+ params.deadline.check();
51
+ validateArchiveEntryPath(entry.path);
52
+ const relPath = stripArchivePath(entry.path, strip);
53
+ if (!relPath)
54
+ continue;
55
+ validateArchiveEntryPath(relPath);
56
+ assertArchiveEntryPathComponentsWithinLimit(relPath, limits);
57
+ resolveArchiveOutputPath({ rootDir: stagingDir, relPath, originalPath: entry.path });
58
+ const kind = policyKind(entry.kind);
59
+ if (!shouldExtractArchiveEntry({
60
+ filter: params.entryFilter,
61
+ onFiltered: params.onFiltered,
62
+ entry: { path: entry.path, kind, size: entry.size },
63
+ })) {
64
+ continue;
65
+ }
66
+ if (entry.kind === "sparse") {
67
+ throw new ArchiveFormatError(`GNU sparse archive entry is not supported: ${entry.path}`);
68
+ }
69
+ if (kind === "symlink") {
70
+ const label = params.kind === "zip" ? "zip" : "tar";
71
+ throw new ArchiveSecurityError("entry-link", `${label} entry is a link: ${entry.path}`);
72
+ }
73
+ if (!Number.isSafeInteger(entry.size) || entry.size < 0) {
74
+ throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
75
+ }
76
+ if (kind === "file") {
77
+ budget.startEntry();
78
+ budget.addEntrySize(entry.size);
79
+ }
80
+ if (kind === "file" || kind === "directory") {
81
+ plan.push({
82
+ index: entry.index,
83
+ path: relPath,
84
+ kind,
85
+ size: entry.size,
86
+ mode: resolveArchiveEntryMode({
87
+ kind,
88
+ archivedMode: entry.mode,
89
+ policy: params.entryModes,
90
+ }),
91
+ });
92
+ }
93
+ }
94
+ const directory = await fs.open(stagingDir, fsConstants.O_RDONLY |
95
+ (typeof fsConstants.O_DIRECTORY === "number" ? fsConstants.O_DIRECTORY : 0));
96
+ try {
97
+ params.deadline.check();
98
+ await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan, limits.maxMetaEntryBytes, params.deadline.signal);
99
+ }
100
+ finally {
101
+ await directory.close().catch(() => undefined);
102
+ }
103
+ params.deadline.check();
104
+ await mergeExtractedTreeIntoDestination({
105
+ sourceDir: stagingDir,
106
+ destinationDir: params.destDir,
107
+ destinationRealDir,
108
+ });
109
+ },
110
+ });
111
+ }
112
+ finally {
113
+ await stagedArchive.cleanup();
114
+ }
115
+ }
@@ -0,0 +1,21 @@
1
+ import type { ArchiveKind } from "./archive-kind.js";
2
+ import type { ArchiveExtractLimits } from "./archive-limits.js";
3
+ import type { ArchiveEntryFilter, ArchiveEntryModePolicy, ArchiveFilteredEntryPolicy } from "./archive-policy.js";
4
+ export type ArchiveLogger = {
5
+ info?: (message: string) => void;
6
+ warn?: (message: string) => void;
7
+ };
8
+ export type ExtractArchiveOptions = {
9
+ archivePath: string;
10
+ destDir: string;
11
+ timeoutMs: number;
12
+ kind?: ArchiveKind;
13
+ stripComponents?: number;
14
+ tarGzip?: boolean;
15
+ limits?: ArchiveExtractLimits;
16
+ logger?: ArchiveLogger;
17
+ entryModes?: ArchiveEntryModePolicy;
18
+ entryFilter?: ArchiveEntryFilter;
19
+ onFiltered?: ArchiveFilteredEntryPolicy;
20
+ };
21
+ //# sourceMappingURL=archive-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-options.d.ts","sourceRoot":"","sources":["../src/archive-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC3B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ export type ArchiveEntryKind = "file" | "directory" | "symlink" | "other";
2
+ export type ArchiveEntryModePolicy = "clamp" | "preserve";
3
+ export type ArchiveFilteredEntryPolicy = "reject-archive" | "skip-entry";
4
+ export type ArchiveEntryFilter = (entry: {
5
+ path: string;
6
+ kind: ArchiveEntryKind;
7
+ size: number;
8
+ }) => "extract" | "skip";
9
+ export declare function archiveEntryKindFromTarType(type: string): ArchiveEntryKind;
10
+ export declare function resolveArchiveEntryMode(params: {
11
+ kind: "file" | "directory";
12
+ archivedMode?: number;
13
+ policy?: ArchiveEntryModePolicy;
14
+ }): number;
15
+ export declare function shouldExtractArchiveEntry(params: {
16
+ filter?: ArchiveEntryFilter;
17
+ onFiltered?: ArchiveFilteredEntryPolicy;
18
+ entry: Parameters<ArchiveEntryFilter>[0];
19
+ }): boolean;
20
+ //# sourceMappingURL=archive-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-policy.d.ts","sourceRoot":"","sources":["../src/archive-policy.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAC1E,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,UAAU,CAAC;AAC1D,MAAM,MAAM,0BAA0B,GAAG,gBAAgB,GAAG,YAAY,CAAC;AACzE,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,KAAK,SAAS,GAAG,MAAM,CAAC;AAEzB,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAK1E;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC,GAAG,MAAM,CAST;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACxC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;CAC1C,GAAG,OAAO,CAWV"}
@@ -0,0 +1,29 @@
1
+ import { ArchiveSecurityError } from "./archive-errors.js";
2
+ export function archiveEntryKindFromTarType(type) {
3
+ if (type === "Directory" || type === "GNUDumpDir")
4
+ return "directory";
5
+ if (type === "File" || type === "OldFile" || type === "ContiguousFile")
6
+ return "file";
7
+ if (type === "SymbolicLink" || type === "Link")
8
+ return "symlink";
9
+ return "other";
10
+ }
11
+ export function resolveArchiveEntryMode(params) {
12
+ const archivedMode = (params.archivedMode ?? 0) & 0o777;
13
+ if (params.policy === "preserve") {
14
+ return archivedMode || (params.kind === "directory" ? 0o755 : 0o644);
15
+ }
16
+ if (params.kind === "directory") {
17
+ return 0o755;
18
+ }
19
+ return archivedMode & 0o100 ? 0o755 : 0o644;
20
+ }
21
+ export function shouldExtractArchiveEntry(params) {
22
+ if (!params.filter || params.filter(params.entry) === "extract") {
23
+ return true;
24
+ }
25
+ if ((params.onFiltered ?? "reject-archive") === "reject-archive") {
26
+ throw new ArchiveSecurityError("entry-filtered", `archive entry rejected by filter: ${params.entry.path}`);
27
+ }
28
+ return false;
29
+ }
@@ -0,0 +1,6 @@
1
+ import { type ArchiveKind } from "./archive-kind.js";
2
+ export declare function readArchiveEntry(archivePath: string, entryPath: string, options: {
3
+ maxBytes: number;
4
+ kind?: ArchiveKind;
5
+ }): Promise<Buffer>;
6
+ //# sourceMappingURL=archive-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AASA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkLzE,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,CAwEjB"}
@@ -0,0 +1,200 @@
1
+ import fsSync from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { Readable } from "node:stream";
4
+ import { readFileHandleBounded } from "./bounded-read.js";
5
+ import { ArchiveFormatError } from "./archive-errors.js";
6
+ import { normalizeArchiveEntryPath, validateArchiveEntryPath, } from "./archive-entry.js";
7
+ import { resolveArchiveKind } from "./archive-kind.js";
8
+ import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
9
+ import { readTarEntryInfo } from "./archive-tar.js";
10
+ import { preflightTarMetadata } from "./archive-tar-meta.js";
11
+ import { importOptionalTar } from "./archive-tar-runtime.js";
12
+ import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
13
+ import { FsSafeError } from "./errors.js";
14
+ import { sameFileIdentity } from "./file-identity.js";
15
+ import { getNativeBinding } from "./native.js";
16
+ import { tempFile } from "./temp-target.js";
17
+ const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
18
+ const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
19
+ function normalizedRequestedEntry(entryPath) {
20
+ validateArchiveEntryPath(entryPath, { escapeLabel: "archive root" });
21
+ const normalized = normalizeArchiveEntryPath(entryPath).replace(/^\.\//, "");
22
+ if (!normalized || normalized.endsWith("/")) {
23
+ throw new Error(`archive entry is not a file: ${entryPath}`);
24
+ }
25
+ return normalized;
26
+ }
27
+ async function readStreamBounded(stream, maxBytes) {
28
+ if (!(Symbol.asyncIterator in Object(stream))) {
29
+ return await new Promise((resolve, reject) => {
30
+ const readable = stream;
31
+ const chunks = [];
32
+ let total = 0;
33
+ readable.on("data", (chunk) => {
34
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
35
+ total += buffer.length;
36
+ if (total > maxBytes) {
37
+ readable.pause();
38
+ reject(new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT));
39
+ return;
40
+ }
41
+ chunks.push(buffer);
42
+ });
43
+ readable.once("end", () => resolve(Buffer.concat(chunks, total)));
44
+ readable.once("error", reject);
45
+ });
46
+ }
47
+ const chunks = [];
48
+ let total = 0;
49
+ for await (const chunk of stream) {
50
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
51
+ total += buffer.length;
52
+ if (total > maxBytes) {
53
+ throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
54
+ }
55
+ chunks.push(buffer);
56
+ }
57
+ return Buffer.concat(chunks, total);
58
+ }
59
+ async function stageArchiveInput(archivePath) {
60
+ const resolved = await fs.realpath(archivePath);
61
+ const before = await fs.lstat(archivePath);
62
+ if (before.isSymbolicLink() || !before.isFile()) {
63
+ throw new Error(`archive is not a regular file: ${archivePath}`);
64
+ }
65
+ const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
66
+ ? fsSync.constants.O_NOFOLLOW
67
+ : 0;
68
+ const handle = await fs.open(resolved, fsSync.constants.O_RDONLY | noFollow);
69
+ const staged = await tempFile({ prefix: "fs-safe-archive-read", fileName: "archive.bin" });
70
+ try {
71
+ const opened = await handle.stat();
72
+ const current = await fs.lstat(resolved);
73
+ if (!opened.isFile() ||
74
+ !current.isFile() ||
75
+ !sameFileIdentity(before, opened) ||
76
+ !sameFileIdentity(current, opened)) {
77
+ throw new Error("archive changed during validation");
78
+ }
79
+ const buffer = await readFileHandleBounded(handle, DEFAULT_MAX_ARCHIVE_BYTES_ZIP);
80
+ await fs.writeFile(staged.path, buffer, { flag: "wx", mode: 0o600 });
81
+ return { path: staged.path, buffer, cleanup: staged.cleanup };
82
+ }
83
+ catch (error) {
84
+ await staged.cleanup().catch(() => undefined);
85
+ throw error;
86
+ }
87
+ finally {
88
+ await handle.close().catch(() => undefined);
89
+ }
90
+ }
91
+ async function readZipEntry(buffer, entryPath, maxBytes) {
92
+ const archive = await loadZipArchiveWithPreflight(buffer, {
93
+ maxArchiveBytes: DEFAULT_MAX_ARCHIVE_BYTES_ZIP,
94
+ maxEntryBytes: maxBytes,
95
+ maxExtractedBytes: maxBytes,
96
+ });
97
+ const entry = archive.files[entryPath];
98
+ if (!entry || entry.dir) {
99
+ throw new Error(`archive entry not found: ${entryPath}`);
100
+ }
101
+ if (typeof entry.unixPermissions === "number" &&
102
+ (entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE) {
103
+ throw new Error(`archive entry is a link: ${entryPath}`);
104
+ }
105
+ const stream = typeof entry.nodeStream === "function"
106
+ ? entry.nodeStream()
107
+ : Readable.from(await entry.async("nodebuffer"));
108
+ return await readStreamBounded(stream, maxBytes);
109
+ }
110
+ async function readTarEntry(archivePath, entryPath, maxBytes) {
111
+ const tar = await importOptionalTar();
112
+ await preflightTarMetadata({
113
+ archivePath,
114
+ maxMetaEntryBytes: DEFAULT_MAX_META_ENTRY_BYTES,
115
+ });
116
+ let matched;
117
+ await tar.t({
118
+ file: archivePath,
119
+ strict: true,
120
+ maxMetaEntrySize: DEFAULT_MAX_META_ENTRY_BYTES,
121
+ onReadEntry(entry) {
122
+ const info = readTarEntryInfo(entry);
123
+ validateArchiveEntryPath(info.path, { escapeLabel: "archive root" });
124
+ const normalized = normalizeArchiveEntryPath(info.path).replace(/^\.\//, "");
125
+ if (normalized !== entryPath) {
126
+ entry.resume();
127
+ return;
128
+ }
129
+ if (info.type !== "File" && info.type !== "OldFile" && info.type !== "ContiguousFile") {
130
+ matched = Promise.reject(new Error(`archive entry is not a file: ${entryPath}`));
131
+ entry.resume();
132
+ return;
133
+ }
134
+ if (info.size > maxBytes) {
135
+ matched = Promise.reject(new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT));
136
+ entry.resume();
137
+ return;
138
+ }
139
+ matched = readStreamBounded(entry, maxBytes);
140
+ },
141
+ });
142
+ if (!matched) {
143
+ throw new Error(`archive entry not found: ${entryPath}`);
144
+ }
145
+ return await matched;
146
+ }
147
+ export async function readArchiveEntry(archivePath, entryPath, options) {
148
+ if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
149
+ throw new RangeError("maxBytes must be a non-negative safe integer");
150
+ }
151
+ const kind = options.kind ?? resolveArchiveKind(archivePath);
152
+ if (!kind) {
153
+ throw new Error(`unsupported archive: ${archivePath}`);
154
+ }
155
+ const requestedEntry = normalizedRequestedEntry(entryPath);
156
+ const staged = await stageArchiveInput(archivePath);
157
+ try {
158
+ const native = getNativeBinding();
159
+ if (native) {
160
+ try {
161
+ const signal = new AbortController().signal;
162
+ const manifest = await native.inspectArchiveNative(staged.path, kind, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, signal);
163
+ let rawEntryPath;
164
+ for (const entry of manifest) {
165
+ validateArchiveEntryPath(entry.path, { escapeLabel: "archive root" });
166
+ const normalized = normalizeArchiveEntryPath(entry.path).replace(/^\.\//, "");
167
+ if (normalized === requestedEntry) {
168
+ if (entry.kind !== "file") {
169
+ throw new Error(`archive entry is not a file: ${entryPath}`);
170
+ }
171
+ rawEntryPath = entry.path;
172
+ break;
173
+ }
174
+ }
175
+ if (!rawEntryPath)
176
+ throw new Error(`archive entry not found: ${entryPath}`);
177
+ return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, signal);
178
+ }
179
+ catch (error) {
180
+ if (error instanceof Error &&
181
+ error.message.includes(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT)) {
182
+ throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
183
+ }
184
+ if (error instanceof Error && error.message.includes("archive-header-invalid")) {
185
+ throw new ArchiveFormatError(error.message, { cause: error });
186
+ }
187
+ throw error;
188
+ }
189
+ }
190
+ if (kind === "tar-zstd" || kind === "tar-bzip2") {
191
+ throw new FsSafeError("helper-unavailable", `${kind} archives require a supported bundled native binding`);
192
+ }
193
+ return kind === "zip"
194
+ ? await readZipEntry(staged.buffer, requestedEntry, options.maxBytes)
195
+ : await readTarEntry(staged.path, requestedEntry, options.maxBytes);
196
+ }
197
+ finally {
198
+ await staged.cleanup();
199
+ }
200
+ }
@@ -1,8 +1,5 @@
1
- export type ArchiveSecurityErrorCode = "destination-not-directory" | "destination-symlink" | "destination-symlink-traversal";
2
- export declare class ArchiveSecurityError extends Error {
3
- code: ArchiveSecurityErrorCode;
4
- constructor(code: ArchiveSecurityErrorCode, message: string, options?: ErrorOptions);
5
- }
1
+ import { ArchiveSecurityError } from "./archive-errors.js";
2
+ export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-errors.js";
6
3
  export declare function prepareArchiveDestinationDir(destDir: string): Promise<string>;
7
4
  export declare function prepareArchiveOutputPath(params: {
8
5
  destinationDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,wBAAwB,GAChC,2BAA2B,GAC3B,qBAAqB,GACrB,+BAA+B,CAAC;AAEpC,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,EAAE,wBAAwB,CAAC;IAE/B,YAAY,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAIlF;CACF;AAkCD,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BnF;AA6CD,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmChB;AAyHD,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Bb;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFhB;AAED,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}
1
+ {"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAU7B,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAkC1F,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BnF;AA4DD,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuChB;AAyHD,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6Bb;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFhB;AAED,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import fsSync from "node:fs";
3
3
  import path from "node:path";
4
4
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
5
+ import { ArchiveSecurityError, } from "./archive-errors.js";
5
6
  import { FsSafeError } from "./errors.js";
6
7
  import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
7
8
  import { isNotFoundPathError, isPathInside } from "./path.js";
@@ -9,14 +10,7 @@ import { resolveSecureTempRoot } from "./secure-temp-dir.js";
9
10
  import { getFsSafeTestHooks } from "./test-hooks.js";
10
11
  const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
11
12
  const ARCHIVE_STAGING_MODE = 0o700;
12
- export class ArchiveSecurityError extends Error {
13
- code;
14
- constructor(code, message, options) {
15
- super(message, options);
16
- this.code = code;
17
- this.name = "ArchiveSecurityError";
18
- }
19
- }
13
+ export { ArchiveSecurityError } from "./archive-errors.js";
20
14
  function symlinkTraversalError(originalPath) {
21
15
  return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${originalPath}`);
22
16
  }
@@ -98,6 +92,17 @@ async function assertResolvedInsideDestination(params) {
98
92
  throw symlinkTraversalError(params.originalPath);
99
93
  }
100
94
  }
95
+ async function mkdirArchiveOutput(params) {
96
+ try {
97
+ await params.targetRoot.mkdir(params.relativePath);
98
+ }
99
+ catch (error) {
100
+ if (error instanceof FsSafeError) {
101
+ throw symlinkTraversalError(params.originalPath);
102
+ }
103
+ throw error;
104
+ }
105
+ }
101
106
  export async function prepareArchiveOutputPath(params) {
102
107
  const targetRoot = await root(params.destinationRealDir);
103
108
  const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
@@ -110,7 +115,7 @@ export async function prepareArchiveOutputPath(params) {
110
115
  if (params.isDirectory) {
111
116
  await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", params.outPath);
112
117
  await assertDirectoryIdentityGuard(destinationGuard);
113
- await targetRoot.mkdir(relPath);
118
+ await mkdirArchiveOutput({ targetRoot, relativePath: relPath, originalPath: params.originalPath });
114
119
  await assertDirectoryIdentityGuard(destinationGuard);
115
120
  await assertResolvedInsideDestination({
116
121
  destinationRealDir: params.destinationRealDir,
@@ -123,7 +128,11 @@ export async function prepareArchiveOutputPath(params) {
123
128
  if (parentRel !== ".") {
124
129
  await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", path.dirname(params.outPath));
125
130
  await assertDirectoryIdentityGuard(destinationGuard);
126
- await targetRoot.mkdir(parentRel);
131
+ await mkdirArchiveOutput({
132
+ targetRoot,
133
+ relativePath: parentRel,
134
+ originalPath: params.originalPath,
135
+ });
127
136
  await assertDirectoryIdentityGuard(destinationGuard);
128
137
  }
129
138
  await assertResolvedInsideDestination({
@@ -0,0 +1,6 @@
1
+ export declare function preflightTarMetadata(params: {
2
+ archivePath: string;
3
+ maxMetaEntryBytes: number;
4
+ signal?: AbortSignal;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=archive-tar-meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-tar-meta.d.ts","sourceRoot":"","sources":["../src/archive-tar-meta.ts"],"names":[],"mappings":"AAuIA,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}