@openclaw/fs-safe 0.5.6 → 0.7.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 (289) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +23 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts +2 -0
  7. package/dist/archive-crc32.d.ts.map +1 -0
  8. package/dist/archive-crc32.js +14 -0
  9. package/dist/archive-deadline.d.ts +3 -0
  10. package/dist/archive-deadline.d.ts.map +1 -1
  11. package/dist/archive-deadline.js +44 -8
  12. package/dist/archive-entry.d.ts.map +1 -1
  13. package/dist/archive-entry.js +1 -0
  14. package/dist/archive-errors.d.ts +1 -0
  15. package/dist/archive-errors.d.ts.map +1 -1
  16. package/dist/archive-errors.js +3 -0
  17. package/dist/archive-input.d.ts.map +1 -1
  18. package/dist/archive-input.js +26 -16
  19. package/dist/archive-kind.js +2 -2
  20. package/dist/archive-limits.d.ts +11 -3
  21. package/dist/archive-limits.d.ts.map +1 -1
  22. package/dist/archive-limits.js +24 -0
  23. package/dist/archive-native.d.ts +3 -2
  24. package/dist/archive-native.d.ts.map +1 -1
  25. package/dist/archive-native.js +31 -9
  26. package/dist/archive-policy.d.ts +2 -0
  27. package/dist/archive-policy.d.ts.map +1 -1
  28. package/dist/archive-policy.js +9 -1
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +88 -41
  31. package/dist/archive-staging.d.ts +3 -0
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +91 -43
  34. package/dist/archive-tar-admission.d.ts +7 -0
  35. package/dist/archive-tar-admission.d.ts.map +1 -0
  36. package/dist/archive-tar-admission.js +43 -0
  37. package/dist/archive-tar-gnu.d.ts +2 -0
  38. package/dist/archive-tar-gnu.d.ts.map +1 -0
  39. package/dist/archive-tar-gnu.js +20 -0
  40. package/dist/archive-tar-header.d.ts +8 -0
  41. package/dist/archive-tar-header.d.ts.map +1 -0
  42. package/dist/archive-tar-header.js +47 -0
  43. package/dist/archive-tar-meta.d.ts +29 -1
  44. package/dist/archive-tar-meta.d.ts.map +1 -1
  45. package/dist/archive-tar-meta.js +135 -19
  46. package/dist/archive-tar-pax.d.ts +8 -0
  47. package/dist/archive-tar-pax.d.ts.map +1 -0
  48. package/dist/archive-tar-pax.js +100 -0
  49. package/dist/archive-tar-runtime.d.ts +4 -0
  50. package/dist/archive-tar-runtime.d.ts.map +1 -1
  51. package/dist/archive-tar-runtime.js +3 -0
  52. package/dist/archive-tar.d.ts.map +1 -1
  53. package/dist/archive-tar.js +8 -2
  54. package/dist/archive-zip-admission.d.ts +7 -0
  55. package/dist/archive-zip-admission.d.ts.map +1 -0
  56. package/dist/archive-zip-admission.js +60 -0
  57. package/dist/archive-zip-count.d.ts +2 -0
  58. package/dist/archive-zip-count.d.ts.map +1 -0
  59. package/dist/archive-zip-count.js +140 -0
  60. package/dist/archive-zip-directory.d.ts +8 -0
  61. package/dist/archive-zip-directory.d.ts.map +1 -0
  62. package/dist/archive-zip-directory.js +224 -0
  63. package/dist/archive-zip-integrity.d.ts.map +1 -1
  64. package/dist/archive-zip-integrity.js +1 -14
  65. package/dist/archive-zip-names.d.ts +12 -0
  66. package/dist/archive-zip-names.d.ts.map +1 -0
  67. package/dist/archive-zip-names.js +99 -0
  68. package/dist/archive-zip-preflight.d.ts +1 -1
  69. package/dist/archive-zip-preflight.d.ts.map +1 -1
  70. package/dist/archive-zip-preflight.js +8 -145
  71. package/dist/archive.d.ts.map +1 -1
  72. package/dist/archive.js +63 -42
  73. package/dist/atomic.d.ts +1 -1
  74. package/dist/atomic.d.ts.map +1 -1
  75. package/dist/bounded-read-stream.d.ts.map +1 -1
  76. package/dist/bounded-read-stream.js +2 -4
  77. package/dist/bounded-read.d.ts.map +1 -1
  78. package/dist/bounded-read.js +3 -10
  79. package/dist/byte-budget.d.ts +5 -0
  80. package/dist/byte-budget.d.ts.map +1 -0
  81. package/dist/byte-budget.js +9 -0
  82. package/dist/file-hash.d.ts.map +1 -1
  83. package/dist/file-hash.js +25 -19
  84. package/dist/file-lock-sync.d.ts.map +1 -1
  85. package/dist/file-lock-sync.js +26 -9
  86. package/dist/file-store-boundary.d.ts.map +1 -1
  87. package/dist/file-store-boundary.js +9 -5
  88. package/dist/file-store-limit.d.ts +2 -0
  89. package/dist/file-store-limit.d.ts.map +1 -0
  90. package/dist/file-store-limit.js +8 -0
  91. package/dist/file-store-sync-write.d.ts.map +1 -1
  92. package/dist/file-store-sync-write.js +38 -8
  93. package/dist/file-store.d.ts.map +1 -1
  94. package/dist/file-store.js +26 -26
  95. package/dist/json-durable-queue-directory.d.ts +2 -0
  96. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  97. package/dist/json-durable-queue-directory.js +20 -0
  98. package/dist/json-durable-queue-ownership.d.ts +14 -0
  99. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  100. package/dist/json-durable-queue-ownership.js +168 -0
  101. package/dist/json-durable-queue-retirement.d.ts +9 -0
  102. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  103. package/dist/json-durable-queue-retirement.js +126 -0
  104. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  105. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  106. package/dist/json-durable-queue-transfer-lock.js +19 -0
  107. package/dist/json-durable-queue.d.ts +1 -0
  108. package/dist/json-durable-queue.d.ts.map +1 -1
  109. package/dist/json-durable-queue.js +90 -57
  110. package/dist/json.d.ts.map +1 -1
  111. package/dist/json.js +27 -8
  112. package/dist/local-roots.d.ts.map +1 -1
  113. package/dist/local-roots.js +4 -2
  114. package/dist/native-binding.d.ts +15 -3
  115. package/dist/native-binding.d.ts.map +1 -1
  116. package/dist/native-operations.d.ts +4 -1
  117. package/dist/native-operations.d.ts.map +1 -1
  118. package/dist/native-operations.js +22 -6
  119. package/dist/native-pinned-write-windows.d.ts +8 -0
  120. package/dist/native-pinned-write-windows.d.ts.map +1 -0
  121. package/dist/native-pinned-write-windows.js +92 -0
  122. package/dist/native-pinned-write.d.ts.map +1 -1
  123. package/dist/native-pinned-write.js +136 -127
  124. package/dist/native-staged-file.d.ts +24 -0
  125. package/dist/native-staged-file.d.ts.map +1 -0
  126. package/dist/native-staged-file.js +337 -0
  127. package/dist/native.d.ts.map +1 -1
  128. package/dist/native.js +4 -4
  129. package/dist/opened-realpath.d.ts +2 -0
  130. package/dist/opened-realpath.d.ts.map +1 -1
  131. package/dist/opened-realpath.js +12 -7
  132. package/dist/output-sibling.d.ts.map +1 -1
  133. package/dist/output-sibling.js +11 -110
  134. package/dist/output.d.ts.map +1 -1
  135. package/dist/output.js +4 -2
  136. package/dist/owner-dacl.d.ts.map +1 -1
  137. package/dist/owner-dacl.js +2 -1
  138. package/dist/permission-exec.d.ts +19 -0
  139. package/dist/permission-exec.d.ts.map +1 -1
  140. package/dist/permission-exec.js +57 -11
  141. package/dist/permissions-public.d.ts +1 -1
  142. package/dist/permissions-public.d.ts.map +1 -1
  143. package/dist/permissions-windows.d.ts +3 -0
  144. package/dist/permissions-windows.d.ts.map +1 -1
  145. package/dist/permissions-windows.js +16 -5
  146. package/dist/permissions.d.ts +5 -0
  147. package/dist/permissions.d.ts.map +1 -1
  148. package/dist/pinned-open.d.ts.map +1 -1
  149. package/dist/pinned-open.js +27 -48
  150. package/dist/pinned-write.d.ts +6 -0
  151. package/dist/pinned-write.d.ts.map +1 -1
  152. package/dist/pinned-write.js +26 -37
  153. package/dist/private-directory.d.ts.map +1 -1
  154. package/dist/private-directory.js +3 -2
  155. package/dist/private-temp-workspace.d.ts +3 -1
  156. package/dist/private-temp-workspace.d.ts.map +1 -1
  157. package/dist/private-temp-workspace.js +81 -56
  158. package/dist/publish-file.d.ts.map +1 -1
  159. package/dist/publish-file.js +2 -4
  160. package/dist/read-opened-file.d.ts.map +1 -1
  161. package/dist/read-opened-file.js +6 -4
  162. package/dist/regular-file.d.ts.map +1 -1
  163. package/dist/regular-file.js +136 -82
  164. package/dist/replace-file-copy-fallback.d.ts +3 -1
  165. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  166. package/dist/replace-file-copy-fallback.js +21 -36
  167. package/dist/replace-file-copy-source.d.ts +21 -0
  168. package/dist/replace-file-copy-source.d.ts.map +1 -0
  169. package/dist/replace-file-copy-source.js +112 -0
  170. package/dist/replace-file-descriptor.d.ts +13 -3
  171. package/dist/replace-file-descriptor.d.ts.map +1 -1
  172. package/dist/replace-file-descriptor.js +32 -7
  173. package/dist/replace-file-rename-policy.d.ts +7 -0
  174. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  175. package/dist/replace-file-rename-policy.js +30 -0
  176. package/dist/replace-file-temp-owner.d.ts +46 -0
  177. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  178. package/dist/replace-file-temp-owner.js +346 -0
  179. package/dist/replace-file.d.ts +6 -1
  180. package/dist/replace-file.d.ts.map +1 -1
  181. package/dist/replace-file.js +72 -58
  182. package/dist/root-impl.d.ts.map +1 -1
  183. package/dist/root-impl.js +118 -98
  184. package/dist/root-paths.d.ts +11 -14
  185. package/dist/root-paths.d.ts.map +1 -1
  186. package/dist/root-paths.js +36 -27
  187. package/dist/root-write-verification.d.ts +11 -0
  188. package/dist/root-write-verification.d.ts.map +1 -0
  189. package/dist/root-write-verification.js +91 -0
  190. package/dist/secret-file.d.ts +1 -6
  191. package/dist/secret-file.d.ts.map +1 -1
  192. package/dist/secret-file.js +49 -120
  193. package/dist/secret-read-async.d.ts +1 -1
  194. package/dist/secret-read-async.d.ts.map +1 -1
  195. package/dist/secret-read-async.js +51 -72
  196. package/dist/secret-read-policy.d.ts +13 -0
  197. package/dist/secret-read-policy.d.ts.map +1 -0
  198. package/dist/secret-read-policy.js +28 -0
  199. package/dist/secret.d.ts +2 -1
  200. package/dist/secret.d.ts.map +1 -1
  201. package/dist/secret.js +2 -1
  202. package/dist/secure-file.d.ts.map +1 -1
  203. package/dist/secure-file.js +42 -29
  204. package/dist/sibling-staged-file.d.ts +15 -0
  205. package/dist/sibling-staged-file.d.ts.map +1 -0
  206. package/dist/sibling-staged-file.js +148 -0
  207. package/dist/sibling-temp.d.ts +3 -0
  208. package/dist/sibling-temp.d.ts.map +1 -1
  209. package/dist/sibling-temp.js +30 -74
  210. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  211. package/dist/sidecar-lock-acquire.js +48 -27
  212. package/dist/sidecar-lock-handle.d.ts +6 -2
  213. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  214. package/dist/sidecar-lock-handle.js +17 -3
  215. package/dist/sidecar-lock-policy.d.ts +2 -0
  216. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  217. package/dist/sidecar-lock-policy.js +29 -0
  218. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  219. package/dist/sidecar-lock-reclaim.js +29 -7
  220. package/dist/sidecar-lock.d.ts.map +1 -1
  221. package/dist/sidecar-lock.js +28 -16
  222. package/dist/staged-directory.d.ts +16 -0
  223. package/dist/staged-directory.d.ts.map +1 -0
  224. package/dist/staged-directory.js +60 -0
  225. package/dist/staged-file-types.d.ts +56 -0
  226. package/dist/staged-file-types.d.ts.map +1 -0
  227. package/dist/staged-file-types.js +1 -0
  228. package/dist/staged-file.d.ts +10 -0
  229. package/dist/staged-file.d.ts.map +1 -0
  230. package/dist/staged-file.js +15 -0
  231. package/dist/strict-file-identity.d.ts +6 -0
  232. package/dist/strict-file-identity.d.ts.map +1 -0
  233. package/dist/strict-file-identity.js +48 -0
  234. package/dist/suppressed-error.d.ts +6 -0
  235. package/dist/suppressed-error.d.ts.map +1 -0
  236. package/dist/suppressed-error.js +15 -0
  237. package/dist/temp-cleanup.d.ts +2 -0
  238. package/dist/temp-cleanup.d.ts.map +1 -1
  239. package/dist/temp-cleanup.js +25 -10
  240. package/dist/temp-workspace-owner.d.ts +23 -0
  241. package/dist/temp-workspace-owner.d.ts.map +1 -0
  242. package/dist/temp-workspace-owner.js +320 -0
  243. package/dist/temp.d.ts +1 -1
  244. package/dist/temp.d.ts.map +1 -1
  245. package/dist/test-hooks.d.ts +5 -0
  246. package/dist/test-hooks.d.ts.map +1 -1
  247. package/dist/windows-owner.d.ts +3 -0
  248. package/dist/windows-owner.d.ts.map +1 -1
  249. package/dist/windows-owner.js +10 -2
  250. package/docs/advanced.md +19 -2
  251. package/docs/archive.md +250 -35
  252. package/docs/atomic.md +11 -2
  253. package/docs/config.md +7 -0
  254. package/docs/contributing.md +45 -7
  255. package/docs/durability.md +18 -5
  256. package/docs/errors.md +16 -1
  257. package/docs/file-store.md +2 -0
  258. package/docs/index.md +3 -1
  259. package/docs/install.md +20 -8
  260. package/docs/json.md +8 -4
  261. package/docs/migrating-to-0.5.md +7 -7
  262. package/docs/migrating-to-0.6.md +43 -0
  263. package/docs/native-helper.md +30 -8
  264. package/docs/native.md +73 -17
  265. package/docs/output.md +10 -0
  266. package/docs/path-scope.md +28 -2
  267. package/docs/permissions.md +13 -2
  268. package/docs/public-api.md +6 -3
  269. package/docs/quickstart.md +1 -1
  270. package/docs/reading.md +1 -1
  271. package/docs/regular-file.md +9 -2
  272. package/docs/root.md +3 -1
  273. package/docs/secret-file.md +12 -0
  274. package/docs/secure-file.md +21 -3
  275. package/docs/security-model.md +33 -1
  276. package/docs/sidecar-lock.md +15 -1
  277. package/docs/staged-file.md +178 -0
  278. package/docs/store.md +13 -0
  279. package/docs/temp.md +128 -14
  280. package/docs/testing.md +1 -1
  281. package/docs/writing.md +19 -0
  282. package/package.json +16 -9
  283. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  284. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  285. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  286. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  287. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  288. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  289. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
@@ -1,12 +1,13 @@
1
1
  import { constants as fsConstants } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
- import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
3
+ import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, isArchiveTarPathErrorMessage, } from "./archive-errors.js";
4
4
  import { formatErrorDetail } from "./error-detail.js";
5
5
  import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
6
6
  import { stageArchiveFileForExtraction } from "./archive-input.js";
7
- import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, resolveExtractLimits, } from "./archive-limits.js";
7
+ import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, } from "./archive-limits.js";
8
8
  import { resolveArchiveEntryMode, shouldExtractArchiveEntry } from "./archive-policy.js";
9
9
  import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, withStagedArchiveDestination, } from "./archive-staging.js";
10
+ import { admitZipFile } from "./archive-zip-admission.js";
10
11
  function policyKind(kind) {
11
12
  if (kind === "file" || kind === "directory")
12
13
  return kind;
@@ -16,6 +17,9 @@ function policyKind(kind) {
16
17
  }
17
18
  function throwMappedNativeError(error) {
18
19
  if (error instanceof Error) {
20
+ if (isArchiveTarPathErrorMessage(error.message)) {
21
+ throw new ArchiveSecurityError("entry-path", error.message, { cause: error });
22
+ }
19
23
  for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
20
24
  if (error.message.includes(code))
21
25
  throw new ArchiveLimitError(code);
@@ -30,23 +34,35 @@ function throwMappedNativeError(error) {
30
34
  throw error;
31
35
  }
32
36
  export async function extractNativeArchive(params) {
33
- const limits = resolveExtractLimits(params.limits);
37
+ const { limits, tarLimits } = params;
34
38
  const stagedArchive = await stageArchiveFileForExtraction({
35
39
  archivePath: params.archivePath,
36
40
  limits,
37
41
  deadline: params.deadline,
38
42
  });
39
43
  try {
44
+ const physicalCount = params.kind === "zip"
45
+ ? await admitZipFile(stagedArchive.path, limits, params.deadline)
46
+ : undefined;
40
47
  const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
41
48
  await withStagedArchiveDestination({
42
49
  destinationRealDir,
43
50
  run: async (stagingDir) => {
44
51
  params.deadline.check();
45
52
  const manifest = await params.binding
46
- .inspectArchiveNative(stagedArchive.path, params.kind, limits.maxEntries, limits.maxMetaEntryBytes, limits.maxArchiveBytes, params.deadline.signal)
53
+ .inspectArchiveNative(stagedArchive.path, params.kind, tarLimits, params.deadline.signal)
47
54
  .catch(throwMappedNativeError);
48
55
  params.deadline.check();
49
56
  assertArchiveEntryCountWithinLimit(manifest.length, limits);
57
+ if (physicalCount !== undefined && manifest.length !== physicalCount) {
58
+ throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
59
+ }
60
+ // Recheck the native manifest at the shared policy boundary before
61
+ // any caller callback observes an entry.
62
+ if (params.kind !== "zip") {
63
+ for (const entry of manifest)
64
+ validateArchiveEntryPath(entry.path);
65
+ }
50
66
  const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
51
67
  const budget = createByteBudgetTracker(limits);
52
68
  const trackOutputPath = createArchiveOutputPathTracker();
@@ -54,7 +70,10 @@ export async function extractNativeArchive(params) {
54
70
  for (const entry of manifest) {
55
71
  params.deadline.check();
56
72
  validateArchiveEntryPath(entry.path);
57
- const relPath = stripArchivePath(entry.path, strip);
73
+ const canonicalPath = stripArchivePath(entry.path, 0);
74
+ if (!canonicalPath)
75
+ continue;
76
+ const relPath = stripArchivePath(canonicalPath, strip);
58
77
  if (!relPath)
59
78
  continue;
60
79
  validateArchiveEntryPath(relPath);
@@ -65,21 +84,22 @@ export async function extractNativeArchive(params) {
65
84
  if (!shouldExtractArchiveEntry({
66
85
  filter: params.entryFilter,
67
86
  onFiltered: params.onFiltered,
68
- entry: { path: entry.path, kind, size: entry.size },
87
+ entry: { path: canonicalPath, kind, size: entry.size },
69
88
  })) {
70
89
  continue;
71
90
  }
72
91
  if (entry.kind === "sparse") {
73
92
  throw new ArchiveFormatError(`GNU sparse archive entry is not supported: ${formatErrorDetail(entry.path)}`);
74
93
  }
75
- if (kind === "symlink") {
94
+ if (kind === "symlink" || entry.kind === "blocked") {
76
95
  const label = params.kind === "zip" ? "zip" : "tar";
77
96
  throw new ArchiveSecurityError("entry-link", `${label} entry is a link: ${formatErrorDetail(entry.path)}`);
78
97
  }
79
98
  if (!Number.isSafeInteger(entry.size) || entry.size < 0) {
80
99
  throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
81
100
  }
82
- if (kind === "file") {
101
+ // GNUDumpDir can carry a body; charge accepted TAR sizes as JS does.
102
+ if (kind === "file" || (kind === "directory" && params.kind !== "zip")) {
83
103
  budget.startEntry();
84
104
  budget.addEntrySize(entry.size);
85
105
  }
@@ -101,7 +121,7 @@ export async function extractNativeArchive(params) {
101
121
  (typeof fsConstants.O_DIRECTORY === "number" ? fsConstants.O_DIRECTORY : 0));
102
122
  try {
103
123
  params.deadline.check();
104
- await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan, limits.maxMetaEntryBytes, params.deadline.signal).catch(throwMappedNativeError);
124
+ await params.binding.extractArchiveNative(stagedArchive.path, params.kind, directory.fd, plan, tarLimits, params.deadline.signal).catch(throwMappedNativeError);
105
125
  }
106
126
  finally {
107
127
  await directory.close().catch(() => undefined);
@@ -111,7 +131,9 @@ export async function extractNativeArchive(params) {
111
131
  sourceDir: stagingDir,
112
132
  destinationDir: params.destDir,
113
133
  destinationRealDir,
134
+ deadline: params.deadline,
114
135
  });
136
+ params.deadline.check();
115
137
  },
116
138
  });
117
139
  }
@@ -2,6 +2,7 @@ export type ArchiveEntryKind = "file" | "directory" | "symlink" | "other";
2
2
  export type ArchiveEntryModePolicy = "clamp" | "preserve";
3
3
  export type ArchiveFilteredEntryPolicy = "reject-archive" | "skip-entry";
4
4
  export type ArchiveEntryFilter = (entry: {
5
+ /** Validated canonical archive path before stripping: / separators, no empty or . components. */
5
6
  path: string;
6
7
  kind: ArchiveEntryKind;
7
8
  size: number;
@@ -12,6 +13,7 @@ export declare function resolveArchiveEntryMode(params: {
12
13
  archivedMode?: number;
13
14
  policy?: ArchiveEntryModePolicy;
14
15
  }): number;
16
+ export declare function resolveArchiveFilteredEntryPolicy(value: unknown): ArchiveFilteredEntryPolicy;
15
17
  export declare function shouldExtractArchiveEntry(params: {
16
18
  filter?: ArchiveEntryFilter;
17
19
  onFiltered?: ArchiveFilteredEntryPolicy;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-policy.d.ts","sourceRoot":"","sources":["../src/archive-policy.ts"],"names":[],"mappings":"AAGA,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"}
1
+ {"version":3,"file":"archive-policy.d.ts","sourceRoot":"","sources":["../src/archive-policy.ts"],"names":[],"mappings":"AAGA,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,iGAAiG;IACjG,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,iCAAiC,CAC/C,KAAK,EAAE,OAAO,GACb,0BAA0B,CAM5B;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,CAYV"}
@@ -19,11 +19,19 @@ export function resolveArchiveEntryMode(params) {
19
19
  }
20
20
  return archivedMode & 0o100 ? 0o755 : 0o644;
21
21
  }
22
+ export function resolveArchiveFilteredEntryPolicy(value) {
23
+ if (value === undefined || value === "reject-archive")
24
+ return "reject-archive";
25
+ if (value === "skip-entry")
26
+ return "skip-entry";
27
+ throw new RangeError('archive onFiltered must be "reject-archive" or "skip-entry"');
28
+ }
22
29
  export function shouldExtractArchiveEntry(params) {
30
+ const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
23
31
  if (!params.filter || params.filter(params.entry) === "extract") {
24
32
  return true;
25
33
  }
26
- if ((params.onFiltered ?? "reject-archive") === "reject-archive") {
34
+ if (onFiltered === "reject-archive") {
27
35
  throw new ArchiveSecurityError("entry-filtered", `archive entry rejected by filter: ${formatErrorDetail(params.entry.path)}`);
28
36
  }
29
37
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqMzE,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,CAmFjB"}
1
+ {"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgOzE,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,CAuFjB"}
@@ -1,27 +1,32 @@
1
- import fsSync from "node:fs";
2
1
  import fs from "node:fs/promises";
3
2
  import { Readable } from "node:stream";
4
3
  import { readFileHandleBounded } from "./bounded-read.js";
5
- import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
4
+ import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, isArchiveTarPathErrorMessage, } from "./archive-errors.js";
6
5
  import { formatErrorDetail } from "./error-detail.js";
7
- import { normalizeArchiveEntryPath, validateArchiveEntryPath, } from "./archive-entry.js";
6
+ import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
8
7
  import { resolveArchiveKind } from "./archive-kind.js";
9
- import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
8
+ import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_META_ENTRY_BYTES, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
10
9
  import { readTarEntryInfo } from "./archive-tar.js";
11
10
  import { preflightTarMetadata } from "./archive-tar-meta.js";
12
- import { importOptionalTar, normalizeTarParserError } from "./archive-tar-runtime.js";
11
+ import { importOptionalTar, NODE_TAR_RATIO_DISABLED, normalizeTarParserError } from "./archive-tar-runtime.js";
13
12
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
14
13
  import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
15
14
  import { FsSafeError } from "./errors.js";
16
- import { sameFileIdentity } from "./file-identity.js";
15
+ import { inspectFileIdentity } from "./strict-file-identity.js";
16
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
17
17
  import { getNativeBinding } from "./native.js";
18
+ import { admitZipBuffer } from "./archive-zip-admission.js";
19
+ import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
18
20
  import { tempFile } from "./temp-target.js";
19
21
  const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
20
22
  const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
21
- function normalizedRequestedEntry(entryPath) {
23
+ function canonicalEntryPath(entryPath) {
22
24
  validateArchiveEntryPath(entryPath, { escapeLabel: "archive root" });
23
- const normalized = normalizeArchiveEntryPath(entryPath).replace(/^\.\//, "");
24
- if (!normalized || normalized.endsWith("/")) {
25
+ return stripArchivePath(entryPath, 0) ?? "";
26
+ }
27
+ function normalizedRequestedEntry(entryPath) {
28
+ const normalized = canonicalEntryPath(entryPath);
29
+ if (!normalized || /[/\\]$/.test(entryPath)) {
25
30
  throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
26
31
  }
27
32
  return normalized;
@@ -60,30 +65,37 @@ async function readStreamBounded(stream, maxBytes) {
60
65
  }
61
66
  async function stageArchiveInput(archivePath) {
62
67
  const resolved = await fs.realpath(archivePath);
63
- const before = await fs.lstat(archivePath);
64
- if (before.isSymbolicLink() || !before.isFile()) {
65
- throw new Error(`archive is not a regular file: ${archivePath}`);
66
- }
67
- const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
68
- ? fsSync.constants.O_NOFOLLOW
69
- : 0;
70
- const handle = await fs.open(resolved, fsSync.constants.O_RDONLY | noFollow);
68
+ const before = await inspectFileIdentity(async () => {
69
+ const stat = await fs.lstat(archivePath, { bigint: true });
70
+ if (stat.isSymbolicLink() || !stat.isFile()) {
71
+ throw new Error(`archive is not a regular file: ${archivePath}`);
72
+ }
73
+ return stat;
74
+ });
75
+ const handle = await fs.open(resolved, resolveReadOpenFlags());
71
76
  const staged = await tempFile({ prefix: "fs-safe-archive-read", fileName: "archive.bin" });
72
77
  try {
73
- const opened = await handle.stat();
74
- const current = await fs.lstat(resolved);
75
- if (!opened.isFile() ||
76
- !current.isFile() ||
77
- !sameFileIdentity(before, opened) ||
78
- !sameFileIdentity(current, opened)) {
79
- throw new Error("archive changed during validation");
80
- }
78
+ const opened = await inspectFileIdentity(async () => {
79
+ const stat = await handle.stat({ bigint: true });
80
+ if (!stat.isFile())
81
+ throw new Error("archive changed during validation");
82
+ return stat;
83
+ }, before);
84
+ await inspectFileIdentity(async () => {
85
+ const stat = await fs.lstat(resolved, { bigint: true });
86
+ if (stat.isSymbolicLink() || !stat.isFile())
87
+ throw new Error("archive changed during validation");
88
+ return stat;
89
+ }, opened);
81
90
  const buffer = await readFileHandleBounded(handle, DEFAULT_MAX_ARCHIVE_BYTES_ZIP);
82
91
  await fs.writeFile(staged.path, buffer, { flag: "wx", mode: 0o600 });
83
92
  return { path: staged.path, buffer, cleanup: staged.cleanup };
84
93
  }
85
94
  catch (error) {
86
95
  await staged.cleanup().catch(() => undefined);
96
+ if (error instanceof FsSafeError && error.code === "path-mismatch") {
97
+ throw new FsSafeError("path-mismatch", "archive changed during validation", { cause: error });
98
+ }
87
99
  throw error;
88
100
  }
89
101
  finally {
@@ -96,7 +108,17 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
96
108
  maxEntryBytes: maxBytes,
97
109
  maxExtractedBytes: maxBytes,
98
110
  });
99
- const entry = archive.files[entryPath];
111
+ let entry;
112
+ // JSZip keys retain some aliases and may use Unicode Path metadata. Scan the
113
+ // effective entries once, after raw ZIP admission has rejected collisions.
114
+ for (const candidate of Object.values(archive.files)) {
115
+ if (canonicalEntryPath(candidate.name) !== entryPath)
116
+ continue;
117
+ if (entry) {
118
+ throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(entryPath)}`);
119
+ }
120
+ entry = candidate;
121
+ }
100
122
  if (!entry || entry.dir) {
101
123
  throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
102
124
  }
@@ -113,28 +135,41 @@ async function readZipEntry(buffer, entryPath, maxBytes) {
113
135
  }
114
136
  async function readTarEntry(archivePath, entryPath, maxBytes) {
115
137
  const tar = await importOptionalTar();
138
+ const seenPaths = new Set();
116
139
  await preflightTarMetadata({
117
140
  archivePath,
118
- maxMetaEntryBytes: DEFAULT_MAX_META_ENTRY_BYTES,
141
+ limits: resolveTarMeterLimits(),
142
+ onMember(info) {
143
+ const normalized = canonicalEntryPath(info.path);
144
+ if (seenPaths.has(normalized)) {
145
+ throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
146
+ }
147
+ seenPaths.add(normalized);
148
+ if (normalized === entryPath && !["File", "OldFile", "ContiguousFile"].includes(info.type)) {
149
+ throw new Error(`archive entry is not a file: ${formatErrorDetail(entryPath)}`);
150
+ }
151
+ },
119
152
  });
120
153
  let matched;
121
154
  let entryError;
122
- const seenPaths = new Set();
123
155
  try {
124
156
  await tar.t({
125
157
  file: archivePath,
126
158
  strict: true,
127
159
  maxMetaEntrySize: DEFAULT_MAX_META_ENTRY_BYTES,
160
+ maxDecompressionRatio: NODE_TAR_RATIO_DISABLED,
128
161
  onReadEntry(entry) {
129
162
  const info = readTarEntryInfo(entry);
130
- validateArchiveEntryPath(info.path, { escapeLabel: "archive root" });
131
- const normalized = normalizeArchiveEntryPath(info.path).replace(/^\.\//, "");
132
- if (seenPaths.has(normalized)) {
133
- entryError ??= new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
163
+ let normalized;
164
+ try {
165
+ normalized = canonicalEntryPath(info.path);
166
+ }
167
+ catch (error) {
168
+ // Throws escaping node-tar's callback do not reject its promise.
169
+ entryError ??= error instanceof Error ? error : new Error(String(error));
134
170
  entry.resume();
135
171
  return;
136
172
  }
137
- seenPaths.add(normalized);
138
173
  if (normalized !== entryPath) {
139
174
  entry.resume();
140
175
  return;
@@ -175,16 +210,22 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
175
210
  const requestedEntry = normalizedRequestedEntry(entryPath);
176
211
  const staged = await stageArchiveInput(archivePath);
177
212
  try {
213
+ const physicalCount = kind === "zip"
214
+ ? admitZipBuffer(staged.buffer, resolveExtractLimits())
215
+ : undefined;
178
216
  const native = getNativeBinding();
179
217
  if (native) {
180
218
  try {
181
219
  const signal = new AbortController().signal;
182
- const manifest = await native.inspectArchiveNative(staged.path, kind, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, signal);
220
+ const limits = resolveTarMeterLimits();
221
+ const manifest = await native.inspectArchiveNative(staged.path, kind, limits, signal);
183
222
  let rawEntryPath;
223
+ if (physicalCount !== undefined && manifest.length !== physicalCount) {
224
+ throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
225
+ }
184
226
  const seenPaths = new Set();
185
227
  for (const entry of manifest) {
186
- validateArchiveEntryPath(entry.path, { escapeLabel: "archive root" });
187
- const normalized = normalizeArchiveEntryPath(entry.path).replace(/^\.\//, "");
228
+ const normalized = canonicalEntryPath(entry.path);
188
229
  if (seenPaths.has(normalized)) {
189
230
  throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
190
231
  }
@@ -199,12 +240,17 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
199
240
  if (!rawEntryPath) {
200
241
  throw new Error(`archive entry not found: ${formatErrorDetail(entryPath)}`);
201
242
  }
202
- return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_META_ENTRY_BYTES, signal);
243
+ return await native.readArchiveEntryNative(staged.path, kind, rawEntryPath, options.maxBytes, limits, signal);
203
244
  }
204
245
  catch (error) {
205
- if (error instanceof Error &&
206
- error.message.includes(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT)) {
207
- throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
246
+ if (error instanceof Error && isArchiveTarPathErrorMessage(error.message)) {
247
+ throw new ArchiveSecurityError("entry-path", error.message, { cause: error });
248
+ }
249
+ if (error instanceof Error) {
250
+ for (const code of Object.values(ARCHIVE_LIMIT_ERROR_CODE)) {
251
+ if (error.message.includes(code))
252
+ throw new ArchiveLimitError(code);
253
+ }
208
254
  }
209
255
  if (error instanceof Error && isArchiveFormatErrorMessage(error.message)) {
210
256
  throw new ArchiveFormatError(error.message, { cause: error });
@@ -213,7 +259,8 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
213
259
  }
214
260
  }
215
261
  if (kind === "tar-zstd" || kind === "tar-bzip2") {
216
- throw new FsSafeError("helper-unavailable", `${kind} archives require a supported bundled native binding`);
262
+ throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
263
+ "install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
217
264
  }
218
265
  return kind === "zip"
219
266
  ? await readZipEntry(staged.buffer, requestedEntry, options.maxBytes)
@@ -1,3 +1,4 @@
1
+ import { type ExtractionDeadline } from "./archive-deadline.js";
1
2
  import { ArchiveSecurityError } from "./archive-errors.js";
2
3
  export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-errors.js";
3
4
  export declare function prepareArchiveDestinationDir(destDir: string): Promise<string>;
@@ -8,6 +9,7 @@ export declare function prepareArchiveOutputPath(params: {
8
9
  outPath: string;
9
10
  originalPath: string;
10
11
  isDirectory: boolean;
12
+ deadline?: ExtractionDeadline;
11
13
  }): Promise<void>;
12
14
  export declare function withStagedArchiveDestination<T>(params: {
13
15
  destinationRealDir: string;
@@ -18,6 +20,7 @@ export declare function mergeExtractedTreeIntoDestination(params: {
18
20
  sourceDir: string;
19
21
  destinationDir: string;
20
22
  destinationRealDir: string;
23
+ deadline?: ExtractionDeadline;
21
24
  }): Promise<void>;
22
25
  export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
23
26
  //# sourceMappingURL=archive-staging.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAW7B,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,CAkFhB;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":"AAOA,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EAErB,MAAM,qBAAqB,CAAC;AAgB7B,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;IACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DhB;AAsID,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;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FhB;AAED,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}