@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,143 @@
1
+ import fs from "node:fs";
2
+ import { Transform, Writable } from "node:stream";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { createGunzip } from "node:zlib";
5
+ import { ArchiveFormatError } from "./archive-errors.js";
6
+ import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError } from "./archive-limits.js";
7
+ class TarMetadataMeter extends Transform {
8
+ maxMetaEntryBytes;
9
+ block = Buffer.alloc(512);
10
+ blockLength = 0;
11
+ state = { kind: "header" };
12
+ constructor(maxMetaEntryBytes) {
13
+ super();
14
+ this.maxMetaEntryBytes = maxMetaEntryBytes;
15
+ }
16
+ invalid(message) {
17
+ return new ArchiveFormatError(`invalid TAR header: ${message}`);
18
+ }
19
+ parseSize() {
20
+ const field = this.block.subarray(124, 136);
21
+ if ((field[0] ?? 0) & 0x80) {
22
+ const value = field.readBigUInt64BE(4);
23
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
24
+ throw this.invalid("base-256 size exceeds the safe integer range");
25
+ }
26
+ return Number(value);
27
+ }
28
+ const zero = field.indexOf(0);
29
+ const text = field.subarray(0, zero < 0 ? field.length : zero).toString("ascii").trim();
30
+ if (!text || !/^[0-7]+$/.test(text))
31
+ throw this.invalid("size is not valid octal");
32
+ const value = Number.parseInt(text, 8);
33
+ if (!Number.isSafeInteger(value))
34
+ throw this.invalid("octal size exceeds the safe integer range");
35
+ return value;
36
+ }
37
+ finishHeader() {
38
+ if (this.block.every((byte) => byte === 0)) {
39
+ this.blockLength = 0;
40
+ this.state = { kind: "header" };
41
+ return;
42
+ }
43
+ const size = this.parseSize();
44
+ const type = this.block[156];
45
+ if ([0x78, 0x67, 0x4c, 0x4b, 0x58].includes(type ?? -1) && size > this.maxMetaEntryBytes) {
46
+ throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.META_ENTRY_SIZE_EXCEEDS_LIMIT);
47
+ }
48
+ if (type === 0x78 || type === 0x67 || type === 0x58) {
49
+ throw this.invalid("PAX metadata is unmeterable without interpreting content");
50
+ }
51
+ const padded = Math.ceil(size / 512) * 512;
52
+ if (!Number.isSafeInteger(padded))
53
+ throw this.invalid("entry padding exceeds the safe integer range");
54
+ if (type === 0x53) {
55
+ if (this.block[482] !== 0 && this.block[482] !== 1) {
56
+ throw this.invalid("GNU sparse extension flag is not 0 or 1");
57
+ }
58
+ if (this.block[482] === 0) {
59
+ throw this.invalid("GNU sparse entries are not supported");
60
+ }
61
+ this.state = { kind: "sparse", dataRemaining: padded, metaBytes: 0 };
62
+ }
63
+ else {
64
+ this.state = padded === 0 ? { kind: "header" } : { kind: "data", remaining: padded };
65
+ }
66
+ this.blockLength = 0;
67
+ }
68
+ finishSparseHeader(state) {
69
+ const metaBytes = state.metaBytes + 512;
70
+ if (metaBytes > this.maxMetaEntryBytes) {
71
+ throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.META_ENTRY_SIZE_EXCEEDS_LIMIT);
72
+ }
73
+ if (this.block[504] !== 0 && this.block[504] !== 1) {
74
+ throw this.invalid("GNU sparse extension flag is not 0 or 1");
75
+ }
76
+ if (this.block[504] === 1) {
77
+ this.state = { ...state, metaBytes };
78
+ }
79
+ else {
80
+ throw this.invalid("GNU sparse entries are not supported");
81
+ }
82
+ this.blockLength = 0;
83
+ }
84
+ meter(chunk) {
85
+ let offset = 0;
86
+ while (offset < chunk.length) {
87
+ if (this.state.kind === "data") {
88
+ const take = Math.min(this.state.remaining, chunk.length - offset);
89
+ offset += take;
90
+ const remaining = this.state.remaining - take;
91
+ this.state = remaining === 0 ? { kind: "header" } : { kind: "data", remaining };
92
+ continue;
93
+ }
94
+ const take = Math.min(512 - this.blockLength, chunk.length - offset);
95
+ chunk.copy(this.block, this.blockLength, offset, offset + take);
96
+ this.blockLength += take;
97
+ offset += take;
98
+ if (this.blockLength === 512) {
99
+ if (this.state.kind === "sparse")
100
+ this.finishSparseHeader(this.state);
101
+ else
102
+ this.finishHeader();
103
+ }
104
+ }
105
+ }
106
+ _transform(chunk, _encoding, callback) {
107
+ try {
108
+ this.meter(chunk);
109
+ callback(null, chunk);
110
+ }
111
+ catch (error) {
112
+ callback(error instanceof Error ? error : new Error(String(error)));
113
+ }
114
+ }
115
+ _flush(callback) {
116
+ if (this.state.kind === "header" && this.blockLength === 0)
117
+ callback();
118
+ else
119
+ callback(this.invalid(this.state.kind === "header" ? "truncated TAR header" : "truncated TAR entry"));
120
+ }
121
+ }
122
+ async function isGzip(filePath) {
123
+ const handle = await fs.promises.open(filePath, "r");
124
+ try {
125
+ const magic = Buffer.alloc(2);
126
+ const { bytesRead } = await handle.read(magic, 0, 2, 0);
127
+ return bytesRead === 2 && magic[0] === 0x1f && magic[1] === 0x8b;
128
+ }
129
+ finally {
130
+ await handle.close();
131
+ }
132
+ }
133
+ export async function preflightTarMetadata(params) {
134
+ const input = fs.createReadStream(params.archivePath);
135
+ const meter = new TarMetadataMeter(params.maxMetaEntryBytes);
136
+ const sink = new Writable({ write(_chunk, _encoding, callback) { callback(); } });
137
+ if (await isGzip(params.archivePath)) {
138
+ await pipeline(input, createGunzip(), meter, sink, { signal: params.signal });
139
+ }
140
+ else {
141
+ await pipeline(input, meter, sink, { signal: params.signal });
142
+ }
143
+ }
@@ -0,0 +1,43 @@
1
+ export type TarParserEntry = {
2
+ meta?: boolean;
3
+ size: number;
4
+ type?: string;
5
+ resume(): void;
6
+ };
7
+ export type TarParser = NodeJS.WritableStream & {
8
+ abort(error: Error): void;
9
+ on(event: "ignoredEntry", listener: (entry: TarParserEntry) => void): TarParser;
10
+ on(event: "entry", listener: (entry: TarParserEntry) => void): TarParser;
11
+ on(event: "meta", listener: (metadata: string) => void): TarParser;
12
+ on(event: "error", listener: (error: Error) => void): TarParser;
13
+ on(event: "end", listener: () => void): TarParser;
14
+ };
15
+ export type TarModule = {
16
+ Parser: new (options: {
17
+ strict: true;
18
+ maxMetaEntrySize: number;
19
+ }) => TarParser;
20
+ x(options: {
21
+ cwd: string;
22
+ strip: number;
23
+ gzip?: boolean;
24
+ signal?: AbortSignal;
25
+ preservePaths: false;
26
+ noChmod: true;
27
+ preserveOwner: false;
28
+ strict: true;
29
+ maxMetaEntrySize: number;
30
+ filter?(this: TarParser, entryPath: string, entry: unknown): boolean;
31
+ onReadEntry(this: unknown, entry: unknown): void;
32
+ }): TarParser;
33
+ t(options: {
34
+ file: string;
35
+ strict: true;
36
+ maxMetaEntrySize: number;
37
+ onReadEntry(entry: AsyncIterable<unknown> & {
38
+ resume(): void;
39
+ }): void;
40
+ }): Promise<unknown>;
41
+ };
42
+ export declare function importOptionalTar(): Promise<TarModule>;
43
+ //# sourceMappingURL=archive-tar-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-tar-runtime.d.ts","sourceRoot":"","sources":["../src/archive-tar-runtime.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,GAAG;IAC9C,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC1B,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IAChF,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,SAAS,CAAC;IACzE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC;IACnE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC;IAChE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,SAAS,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,KAAK,OAAO,EAAE;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAC;IAC/E,CAAC,CAAC,OAAO,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,aAAa,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC;QACd,aAAa,EAAE,KAAK,CAAC;QACrB,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,MAAM,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QACrE,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;KAClD,GAAG,SAAS,CAAC;IACd,CAAC,CAAC,OAAO,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,IAAI,CAAC;QACb,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG;YAAE,MAAM,IAAI,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;KACvE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB,CAAC;AAEF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,CAAC,CAS5D"}
@@ -0,0 +1,8 @@
1
+ export async function importOptionalTar() {
2
+ try {
3
+ return await import("tar");
4
+ }
5
+ catch (cause) {
6
+ throw new Error('Optional archive dependency "tar" is not installed. Install it to use TAR archive helpers from @openclaw/fs-safe/archive.', { cause });
7
+ }
8
+ }
@@ -1,8 +1,10 @@
1
1
  import { type ArchiveExtractLimits } from "./archive-limits.js";
2
+ import { type ArchiveEntryFilter, type ArchiveFilteredEntryPolicy } from "./archive-policy.js";
2
3
  export type TarEntryInfo = {
3
4
  path: string;
4
5
  type: string;
5
6
  size: number;
7
+ mode?: number;
6
8
  };
7
9
  export declare function readTarEntryInfo(entry: unknown): TarEntryInfo;
8
10
  export declare function createTarEntryPreflightChecker(params: {
@@ -10,5 +12,7 @@ export declare function createTarEntryPreflightChecker(params: {
10
12
  stripComponents?: number;
11
13
  limits?: ArchiveExtractLimits;
12
14
  escapeLabel?: string;
13
- }): (entry: TarEntryInfo) => void;
15
+ entryFilter?: ArchiveEntryFilter;
16
+ onFiltered?: ArchiveFilteredEntryPolicy;
17
+ }): (entry: TarEntryInfo) => boolean;
14
18
  //# sourceMappingURL=archive-tar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAWxE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAkB7D;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CA6BhC"}
1
+ {"version":3,"file":"archive-tar.d.ts","sourceRoot":"","sources":["../src/archive-tar.ts"],"names":[],"mappings":"AAKA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAWvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAyB7D;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CA2CnC"}
@@ -1,5 +1,7 @@
1
1
  import { resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
2
- import { assertArchiveEntryCountWithinLimit, createByteBudgetTracker, resolveExtractLimits, } from "./archive-limits.js";
2
+ import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, resolveExtractLimits, } from "./archive-limits.js";
3
+ import { archiveEntryKindFromTarType, shouldExtractArchiveEntry, } from "./archive-policy.js";
4
+ import { ArchiveSecurityError } from "./archive-errors.js";
3
5
  const BLOCKED_TAR_ENTRY_TYPES = new Set([
4
6
  "SymbolicLink",
5
7
  "Link",
@@ -22,7 +24,13 @@ export function readTarEntryInfo(entry) {
22
24
  Number.isFinite(entry.size)
23
25
  ? Math.max(0, Math.floor(entry.size))
24
26
  : 0;
25
- return { path: p, type: t, size: s };
27
+ const mode = typeof entry === "object" &&
28
+ entry !== null &&
29
+ "mode" in entry &&
30
+ typeof entry.mode === "number"
31
+ ? entry.mode
32
+ : undefined;
33
+ return { path: p, type: t, size: s, mode };
26
34
  }
27
35
  export function createTarEntryPreflightChecker(params) {
28
36
  const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
@@ -33,20 +41,30 @@ export function createTarEntryPreflightChecker(params) {
33
41
  validateArchiveEntryPath(entry.path, { escapeLabel: params.escapeLabel });
34
42
  const relPath = stripArchivePath(entry.path, strip);
35
43
  if (!relPath) {
36
- return;
44
+ return false;
37
45
  }
38
46
  validateArchiveEntryPath(relPath, { escapeLabel: params.escapeLabel });
47
+ assertArchiveEntryPathComponentsWithinLimit(relPath, limits);
39
48
  resolveArchiveOutputPath({
40
49
  rootDir: params.rootDir,
41
50
  relPath,
42
51
  originalPath: entry.path,
43
52
  escapeLabel: params.escapeLabel,
44
53
  });
45
- if (BLOCKED_TAR_ENTRY_TYPES.has(entry.type)) {
46
- throw new Error(`tar entry is a link: ${entry.path}`);
47
- }
48
54
  entryCount += 1;
49
55
  assertArchiveEntryCountWithinLimit(entryCount, limits);
56
+ const kind = archiveEntryKindFromTarType(entry.type);
57
+ if (!shouldExtractArchiveEntry({
58
+ filter: params.entryFilter,
59
+ onFiltered: params.onFiltered,
60
+ entry: { path: entry.path, kind, size: entry.size },
61
+ })) {
62
+ return false;
63
+ }
64
+ if (BLOCKED_TAR_ENTRY_TYPES.has(entry.type)) {
65
+ throw new ArchiveSecurityError("entry-link", `tar entry is a link: ${entry.path}`);
66
+ }
50
67
  budget.addEntrySize(entry.size);
68
+ return true;
51
69
  };
52
70
  }
@@ -0,0 +1,15 @@
1
+ import { type ArchiveEntryModePolicy } from "./archive-policy.js";
2
+ export type ZipEntry = {
3
+ name: string;
4
+ dir: boolean;
5
+ unixPermissions?: number;
6
+ _data?: {
7
+ uncompressedSize?: number;
8
+ };
9
+ nodeStream?: () => NodeJS.ReadableStream;
10
+ async: (type: "nodebuffer") => Promise<Buffer>;
11
+ };
12
+ export declare function isZipSymlinkEntry(entry: ZipEntry): boolean;
13
+ export declare function zipEntryMode(entry: ZipEntry, policy: ArchiveEntryModePolicy | undefined): number;
14
+ export declare function zipEntryDeclaredSize(entry: ZipEntry): number;
15
+ //# sourceMappingURL=archive-zip-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-zip-entry.d.ts","sourceRoot":"","sources":["../src/archive-zip-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,cAAc,CAAC;IACzC,KAAK,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChD,CAAC;AAKF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAK1D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,MAAM,CAMR;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,17 @@
1
+ import { resolveArchiveEntryMode, } from "./archive-policy.js";
2
+ const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
3
+ const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
4
+ export function isZipSymlinkEntry(entry) {
5
+ return (typeof entry.unixPermissions === "number" &&
6
+ (entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE);
7
+ }
8
+ export function zipEntryMode(entry, policy) {
9
+ return resolveArchiveEntryMode({
10
+ kind: entry.dir ? "directory" : "file",
11
+ archivedMode: entry.unixPermissions,
12
+ policy,
13
+ });
14
+ }
15
+ export function zipEntryDeclaredSize(entry) {
16
+ return Math.max(0, Math.floor(entry._data?.uncompressedSize ?? 0));
17
+ }
package/dist/archive.d.ts CHANGED
@@ -1,24 +1,14 @@
1
- import { type ArchiveExtractLimits } from "./archive-limits.js";
2
- import { type ArchiveKind } from "./archive-kind.js";
3
- export type ArchiveLogger = {
4
- info?: (message: string) => void;
5
- warn?: (message: string) => void;
6
- };
1
+ import type { ExtractArchiveOptions } from "./archive-options.js";
2
+ export type { ArchiveLogger, ExtractArchiveOptions } from "./archive-options.js";
3
+ export type { ArchiveEntryFilter, ArchiveEntryKind, ArchiveEntryModePolicy, ArchiveFilteredEntryPolicy, } from "./archive-policy.js";
7
4
  export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
8
5
  export { resolveArchiveKind, resolvePackedRootDir, type ArchiveKind } from "./archive-kind.js";
9
- export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, type ArchiveExtractLimits, type ArchiveLimitErrorCode, } from "./archive-limits.js";
6
+ export { readArchiveEntry } from "./archive-read.js";
7
+ export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, type ArchiveExtractLimits, type ArchiveLimitErrorCode, } from "./archive-limits.js";
8
+ export { ArchiveFormatError, type ArchiveFormatErrorCode } from "./archive-errors.js";
10
9
  export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-staging.js";
11
10
  export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
12
11
  export { createTarEntryPreflightChecker, type TarEntryInfo } from "./archive-tar.js";
13
12
  export { loadZipArchiveWithPreflight, readZipCentralDirectoryEntryCount, type ZipArchiveWithFiles, } from "./archive-zip-preflight.js";
14
- export declare function extractArchive(params: {
15
- archivePath: string;
16
- destDir: string;
17
- timeoutMs: number;
18
- kind?: ArchiveKind;
19
- stripComponents?: number;
20
- tarGzip?: boolean;
21
- limits?: ArchiveExtractLimits;
22
- logger?: ArchiveLogger;
23
- }): Promise<void>;
13
+ export declare function extractArchive(params: ExtractArchiveOptions): Promise<void>;
24
14
  //# sourceMappingURL=archive.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AAkBA,OAAO,EAOL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAiBzE,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,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAuTpC,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,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;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6EhB"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AAuDA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiKjF"}