@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,16 +1,20 @@
1
1
  import fs from "node:fs/promises";
2
- import fsSync from "node:fs";
3
2
  import path from "node:path";
4
3
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
4
+ 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
8
  import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
9
9
  import { isNotFoundPathError, isPathInside } from "./path.js";
10
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
10
11
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
11
12
  import { getFsSafeTestHooks } from "./test-hooks.js";
12
13
  const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
13
14
  const ARCHIVE_STAGING_MODE = 0o700;
15
+ function checkExtractionDeadline(deadline) {
16
+ deadline?.check();
17
+ }
14
18
  export { ArchiveSecurityError } from "./archive-errors.js";
15
19
  function symlinkTraversalError(originalPath) {
16
20
  return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${formatErrorDetail(originalPath)}`);
@@ -105,55 +109,79 @@ async function mkdirArchiveOutput(params) {
105
109
  }
106
110
  }
107
111
  export async function prepareArchiveOutputPath(params) {
112
+ checkExtractionDeadline(params.deadline);
108
113
  const targetRoot = await root(params.destinationRealDir);
114
+ checkExtractionDeadline(params.deadline);
109
115
  const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
116
+ checkExtractionDeadline(params.deadline);
110
117
  const relPath = params.relPath.split(path.sep).join(path.posix.sep);
111
118
  await assertNoSymlinkTraversal({
112
119
  rootDir: params.destinationDir,
113
120
  relPath,
114
121
  originalPath: params.originalPath,
115
122
  });
123
+ checkExtractionDeadline(params.deadline);
116
124
  if (params.isDirectory) {
117
125
  await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", params.outPath);
118
- await assertDirectoryIdentityGuard(destinationGuard);
119
- await mkdirArchiveOutput({ targetRoot, relativePath: relPath, originalPath: params.originalPath });
120
- await assertDirectoryIdentityGuard(destinationGuard);
121
- await assertResolvedInsideDestination({
122
- destinationRealDir: params.destinationRealDir,
123
- targetPath: params.outPath,
124
- originalPath: params.originalPath,
126
+ checkExtractionDeadline(params.deadline);
127
+ await ownExtractionDestinationMutation(params.deadline, async () => {
128
+ await assertDirectoryIdentityGuard(destinationGuard);
129
+ checkExtractionDeadline(params.deadline);
130
+ await mkdirArchiveOutput({
131
+ targetRoot,
132
+ relativePath: relPath,
133
+ originalPath: params.originalPath,
134
+ });
135
+ checkExtractionDeadline(params.deadline);
136
+ await assertDirectoryIdentityGuard(destinationGuard);
137
+ checkExtractionDeadline(params.deadline);
138
+ await assertResolvedInsideDestination({
139
+ destinationRealDir: params.destinationRealDir,
140
+ targetPath: params.outPath,
141
+ originalPath: params.originalPath,
142
+ });
143
+ checkExtractionDeadline(params.deadline);
125
144
  });
126
145
  return;
127
146
  }
128
147
  const parentRel = path.posix.dirname(relPath);
129
148
  if (parentRel !== ".") {
130
149
  await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", path.dirname(params.outPath));
131
- await assertDirectoryIdentityGuard(destinationGuard);
132
- await mkdirArchiveOutput({
133
- targetRoot,
134
- relativePath: parentRel,
135
- originalPath: params.originalPath,
150
+ checkExtractionDeadline(params.deadline);
151
+ await ownExtractionDestinationMutation(params.deadline, async () => {
152
+ await assertDirectoryIdentityGuard(destinationGuard);
153
+ checkExtractionDeadline(params.deadline);
154
+ await mkdirArchiveOutput({
155
+ targetRoot,
156
+ relativePath: parentRel,
157
+ originalPath: params.originalPath,
158
+ });
159
+ checkExtractionDeadline(params.deadline);
160
+ await assertDirectoryIdentityGuard(destinationGuard);
161
+ checkExtractionDeadline(params.deadline);
136
162
  });
137
- await assertDirectoryIdentityGuard(destinationGuard);
138
163
  }
139
164
  await assertResolvedInsideDestination({
140
165
  destinationRealDir: params.destinationRealDir,
141
166
  targetPath: path.dirname(params.outPath),
142
167
  originalPath: params.originalPath,
143
168
  });
169
+ checkExtractionDeadline(params.deadline);
144
170
  }
145
171
  async function chmodInsideDestinationBestEffort(params) {
146
172
  await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", params.destinationPath);
173
+ checkExtractionDeadline(params.deadline);
147
174
  const destinationGuard = await createDirectoryIdentityGuard(params.destinationRealDir);
175
+ checkExtractionDeadline(params.deadline);
148
176
  await assertDirectoryIdentityGuard(destinationGuard);
149
- const noFollowFlag = process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants
150
- ? fsSync.constants.O_NOFOLLOW
151
- : 0;
177
+ checkExtractionDeadline(params.deadline);
152
178
  const handle = await fs
153
- .open(params.destinationPath, fsSync.constants.O_RDONLY | noFollowFlag)
179
+ .open(params.destinationPath, resolveReadOpenFlags())
154
180
  .catch(() => null);
181
+ checkExtractionDeadline(params.deadline);
155
182
  if (!handle) {
156
183
  const stat = await fs.lstat(params.destinationPath).catch(() => null);
184
+ checkExtractionDeadline(params.deadline);
157
185
  if (stat?.isSymbolicLink()) {
158
186
  throw symlinkTraversalError(params.originalPath);
159
187
  }
@@ -161,35 +189,45 @@ async function chmodInsideDestinationBestEffort(params) {
161
189
  }
162
190
  try {
163
191
  const stat = await handle.stat();
192
+ checkExtractionDeadline(params.deadline);
164
193
  if (!stat.isDirectory() && !stat.isFile()) {
165
194
  return;
166
195
  }
167
196
  const realPath = await resolveOpenedFileRealPathForHandle(handle, params.destinationPath);
197
+ checkExtractionDeadline(params.deadline);
168
198
  if (!isPathInside(params.destinationRealDir, realPath)) {
169
199
  throw symlinkTraversalError(params.originalPath);
170
200
  }
171
- await handle.chmod(params.mode).catch(() => undefined);
172
- await assertDirectoryIdentityGuard(destinationGuard);
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
+ });
173
207
  }
174
208
  finally {
175
209
  await handle.close().catch(() => undefined);
176
210
  }
177
211
  }
178
212
  async function applyStagedEntryMode(params) {
213
+ checkExtractionDeadline(params.deadline);
179
214
  const destinationPath = path.join(params.destinationRealDir, params.relPath);
180
215
  await assertResolvedInsideDestination({
181
216
  destinationRealDir: params.destinationRealDir,
182
217
  targetPath: destinationPath,
183
218
  originalPath: params.originalPath,
184
219
  });
220
+ checkExtractionDeadline(params.deadline);
185
221
  if (params.mode !== 0) {
186
222
  await chmodInsideDestinationBestEffort({
187
223
  destinationRealDir: params.destinationRealDir,
188
224
  destinationPath,
189
225
  mode: params.mode,
190
226
  originalPath: params.originalPath,
227
+ deadline: params.deadline,
191
228
  });
192
229
  }
230
+ checkExtractionDeadline(params.deadline);
193
231
  }
194
232
  async function assertExtractedFileHasNoHardlinkAlias(params) {
195
233
  const destinationPath = path.join(params.destinationRealDir, params.relPath);
@@ -296,6 +334,7 @@ export async function mergeExtractedTreeIntoDestination(params) {
296
334
  outPath: destinationPath,
297
335
  originalPath,
298
336
  isDirectory: true,
337
+ deadline: params.deadline,
299
338
  });
300
339
  await walk(sourcePath);
301
340
  await applyStagedEntryMode({
@@ -303,6 +342,7 @@ export async function mergeExtractedTreeIntoDestination(params) {
303
342
  relPath,
304
343
  mode: sourceStat.mode & 0o777,
305
344
  originalPath,
345
+ deadline: params.deadline,
306
346
  });
307
347
  continue;
308
348
  }
@@ -316,31 +356,39 @@ export async function mergeExtractedTreeIntoDestination(params) {
316
356
  outPath: destinationPath,
317
357
  originalPath,
318
358
  isDirectory: false,
359
+ deadline: params.deadline,
319
360
  });
320
- try {
321
- await targetRoot.copyIn(relPath, sourcePath, { mkdir: true });
322
- await assertExtractedFileHasNoHardlinkAlias({
323
- destinationRealDir: params.destinationRealDir,
324
- relPath,
325
- originalPath,
326
- });
327
- await applyStagedEntryMode({
328
- destinationRealDir: params.destinationRealDir,
329
- relPath,
330
- mode: sourceStat.mode & 0o777,
331
- originalPath,
332
- });
333
- }
334
- catch (err) {
335
- await removeExtractedDestinationFile({
336
- destinationRealDir: params.destinationRealDir,
337
- relPath,
338
- });
339
- if (err instanceof FsSafeError && (err.code === "hardlink" || err.code === "path-alias")) {
340
- throw symlinkTraversalError(originalPath);
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);
341
379
  }
342
- throw err;
343
- }
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
+ });
344
392
  }
345
393
  };
346
394
  await walk(params.sourceDir);
@@ -0,0 +1,7 @@
1
+ import type { TarEntryInfo } from "./archive-tar.js";
2
+ export declare function rawTarMember(header: Buffer, size: number, effectivePath?: string): TarEntryInfo;
3
+ export declare function createTarAdmissionPlan(manifest: readonly TarEntryInfo[], check: (entry: TarEntryInfo) => boolean, strip: number): {
4
+ consume(entry: TarEntryInfo): string | null;
5
+ finish(): void;
6
+ };
7
+ //# sourceMappingURL=archive-tar-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-tar-admission.d.ts","sourceRoot":"","sources":["../src/archive-tar-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAYrD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,CAS/F;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,EACvC,KAAK,EAAE,MAAM,GACZ;IAAE,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,IAAI,IAAI,CAAA;CAAE,CAmBjE"}
@@ -0,0 +1,43 @@
1
+ import { ArchiveFormatError } from "./archive-errors.js";
2
+ import { stripArchivePath, validateArchiveEntryPath } from "./archive-entry.js";
3
+ import { readTarHeaderPaths } from "./archive-tar-header.js";
4
+ // node-tar's normalFsTypes/ReadEntry switch. All other non-metadata types
5
+ // bypass both its filter and entry event; they still belong to our manifest.
6
+ // Header.decode normalizes NUL to "0" before choosing the emitted File type.
7
+ const visibleTypes = new Map([
8
+ [0, "File"], [0x30, "File"], [0x31, "Link"], [0x32, "SymbolicLink"],
9
+ [0x33, "CharacterDevice"], [0x34, "BlockDevice"], [0x35, "Directory"],
10
+ [0x36, "FIFO"], [0x37, "ContiguousFile"], [0x44, "GNUDumpDir"],
11
+ ]);
12
+ export function rawTarMember(header, size, effectivePath) {
13
+ const { name, prefix } = readTarHeaderPaths(header);
14
+ validateArchiveEntryPath(name);
15
+ validateArchiveEntryPath(prefix);
16
+ const rawPath = prefix ? `${prefix}/${name}` : name;
17
+ validateArchiveEntryPath(rawPath);
18
+ const path = effectivePath ?? rawPath;
19
+ validateArchiveEntryPath(path);
20
+ return { path, size, type: visibleTypes.get(header[156]) ?? "Unsupported" };
21
+ }
22
+ export function createTarAdmissionPlan(manifest, check, strip) {
23
+ const visible = [];
24
+ for (const entry of manifest) {
25
+ const accepted = check(entry);
26
+ if (entry.type !== "Unsupported") {
27
+ visible.push({ entry, output: accepted ? stripArchivePath(entry.path, strip) : null });
28
+ }
29
+ }
30
+ let index = 0;
31
+ const mismatch = () => new ArchiveFormatError("invalid TAR header: parser disagrees with raw admission");
32
+ return {
33
+ consume(actual) {
34
+ const expected = visible[index++];
35
+ if (!expected || expected.entry.type !== actual.type || expected.entry.size !== actual.size ||
36
+ stripArchivePath(expected.entry.path, 0) !== stripArchivePath(actual.path, 0))
37
+ throw mismatch();
38
+ return expected.output;
39
+ },
40
+ finish() { if (index !== visible.length)
41
+ throw mismatch(); },
42
+ };
43
+ }
@@ -0,0 +1,2 @@
1
+ export declare function validateGnuMetadata(body: Buffer, type: "L" | "K"): string;
2
+ //# sourceMappingURL=archive-tar-gnu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-tar-gnu.d.ts","sourceRoot":"","sources":["../src/archive-tar-gnu.ts"],"names":[],"mappings":"AAKA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,CAazE"}
@@ -0,0 +1,20 @@
1
+ import { ArchiveFormatError } from "./archive-errors.js";
2
+ import { validateArchiveEntryPath } from "./archive-entry.js";
3
+ // Keep the byte grammar aligned with native/src/tar_meter.rs. Parsers differ
4
+ // on embedded NULs and malformed UTF-8, so validate before either sees a name.
5
+ export function validateGnuMetadata(body, type) {
6
+ const value = body.at(-1) === 0 ? body.subarray(0, -1) : body;
7
+ if (!value.length || value.includes(0)) {
8
+ throw new ArchiveFormatError("invalid GNU metadata: empty name or embedded NUL");
9
+ }
10
+ let name;
11
+ try {
12
+ name = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(value);
13
+ }
14
+ catch {
15
+ throw new ArchiveFormatError("invalid GNU metadata: name is not valid UTF-8");
16
+ }
17
+ if (type === "L")
18
+ validateArchiveEntryPath(name);
19
+ return name;
20
+ }
@@ -0,0 +1,8 @@
1
+ export declare function validateTarChecksum(header: Buffer): void;
2
+ export declare function readTarHeaderPaths(header: Buffer): {
3
+ name: string;
4
+ prefix: string;
5
+ linkname: string;
6
+ };
7
+ export declare function validateTarHeader(header: Buffer): void;
8
+ //# sourceMappingURL=archive-tar-header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-tar-header.d.ts","sourceRoot":"","sources":["../src/archive-tar-header.ts"],"names":[],"mappings":"AAGA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAWxD;AAcD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CASrG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAUtD"}
@@ -0,0 +1,47 @@
1
+ import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
+ const utf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
3
+ export function validateTarChecksum(header) {
4
+ const field = header.subarray(148, 156);
5
+ const end = field.indexOf(0);
6
+ const digits = field.subarray(0, end < 0 ? field.length : end).toString("latin1").replace(/^ +| +$/g, "");
7
+ const sum = header.reduce((total, byte, index) => total + (index >= 148 && index < 156 ? 32 : byte), 0);
8
+ // Require an in-field delimiter: node-tar otherwise reads into the typeflag.
9
+ if (!/^[0-7]+$/.test(digits) || ![0, 32].includes(field[7]) ||
10
+ (end >= 0 && field.subarray(end).some((byte) => byte !== 0 && byte !== 32)) ||
11
+ Number.parseInt(digits, 8) !== sum) {
12
+ throw new ArchiveFormatError("invalid TAR header: checksum failure");
13
+ }
14
+ }
15
+ function fixedField(field) {
16
+ const zero = field.indexOf(0);
17
+ if (zero >= 0 && field.subarray(zero).some((byte) => byte !== 0)) {
18
+ throw new ArchiveSecurityError("entry-path", "tar entry path contains bytes after NUL");
19
+ }
20
+ try {
21
+ return utf8.decode(field.subarray(0, zero < 0 ? field.length : zero));
22
+ }
23
+ catch {
24
+ throw new ArchiveSecurityError("entry-path", "tar entry path is not valid UTF-8");
25
+ }
26
+ }
27
+ export function readTarHeaderPaths(header) {
28
+ // node-tar's star layout uses a 130-byte prefix followed by atime/ctime.
29
+ const prefixEnd = header[475] === 0 ? 475 : 500;
30
+ return {
31
+ name: fixedField(header.subarray(0, 100)),
32
+ prefix: header.subarray(257, 265).equals(Buffer.from("ustar\0" + "00"))
33
+ ? fixedField(header.subarray(345, prefixEnd)) : "",
34
+ linkname: fixedField(header.subarray(157, 257)),
35
+ };
36
+ }
37
+ export function validateTarHeader(header) {
38
+ validateTarChecksum(header);
39
+ const { linkname } = readTarHeaderPaths(header);
40
+ const isLink = header[156] === 0x31 || header[156] === 0x32;
41
+ if (isLink && !linkname) {
42
+ throw new ArchiveFormatError("invalid TAR header: linkname required on a link header");
43
+ }
44
+ if (!isLink && linkname) {
45
+ throw new ArchiveFormatError("invalid TAR header: linkname forbidden on a non-link header");
46
+ }
47
+ }
@@ -1,6 +1,34 @@
1
+ import { Transform } from "node:stream";
2
+ import { type TarMeterLimits } from "./archive-limits.js";
3
+ import type { TarEntryInfo } from "./archive-tar.js";
4
+ export declare class TarMetadataMeter extends Transform {
5
+ private readonly limits;
6
+ private readonly onMember?;
7
+ private readonly block;
8
+ private blockLength;
9
+ private state;
10
+ private pendingPax;
11
+ private readonly pendingGnu;
12
+ private pendingGnuPath;
13
+ private zeroBlocks;
14
+ private entries;
15
+ private remainingDecodedBytes;
16
+ private manifestBytes;
17
+ constructor(limits: TarMeterLimits, onMember?: ((entry: TarEntryInfo) => void) | undefined);
18
+ private countMember;
19
+ private invalid;
20
+ private paddedSize;
21
+ private parseSize;
22
+ private finishHeader;
23
+ private finishSparseHeader;
24
+ private meter;
25
+ _transform(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null, data?: Buffer) => void): void;
26
+ _flush(callback: (error?: Error | null) => void): void;
27
+ }
1
28
  export declare function preflightTarMetadata(params: {
2
29
  archivePath: string;
3
- maxMetaEntryBytes: number;
30
+ limits: TarMeterLimits;
4
31
  signal?: AbortSignal;
32
+ onMember?: (entry: TarEntryInfo) => void;
5
33
  }): Promise<void>;
6
34
  //# sourceMappingURL=archive-tar-meta.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-meta.d.ts","sourceRoot":"","sources":["../src/archive-tar-meta.ts"],"names":[],"mappings":"AAqJA,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"}
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"}