@openclaw/fs-safe 0.4.7 → 0.5.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 (239) hide show
  1. package/CHANGELOG.md +45 -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/durability.d.ts +2 -0
  56. package/dist/durability.d.ts.map +1 -1
  57. package/dist/durability.js +2 -0
  58. package/dist/errors.d.ts +4 -1
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +2 -0
  61. package/dist/file-hash.d.ts +10 -0
  62. package/dist/file-hash.d.ts.map +1 -0
  63. package/dist/file-hash.js +73 -0
  64. package/dist/file-lock-sync.d.ts +35 -0
  65. package/dist/file-lock-sync.d.ts.map +1 -0
  66. package/dist/file-lock-sync.js +233 -0
  67. package/dist/file-lock.d.ts +2 -0
  68. package/dist/file-lock.d.ts.map +1 -1
  69. package/dist/file-lock.js +1 -0
  70. package/dist/filename.d.ts.map +1 -1
  71. package/dist/filename.js +4 -1
  72. package/dist/guarded-mkdir.d.ts +7 -1
  73. package/dist/guarded-mkdir.d.ts.map +1 -1
  74. package/dist/guarded-mkdir.js +45 -3
  75. package/dist/index.d.ts +4 -3
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +1 -1
  78. package/dist/json-document-store.d.ts.map +1 -1
  79. package/dist/json-document-store.js +30 -15
  80. package/dist/move-path.d.ts.map +1 -1
  81. package/dist/move-path.js +101 -17
  82. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  83. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  84. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  85. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  86. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  87. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  88. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
  89. package/dist/native-binding.d.ts +79 -0
  90. package/dist/native-binding.d.ts.map +1 -0
  91. package/dist/native-binding.js +1 -0
  92. package/dist/native-config.d.ts +17 -0
  93. package/dist/native-config.d.ts.map +1 -0
  94. package/dist/native-config.js +71 -0
  95. package/dist/native-operations.d.ts +21 -0
  96. package/dist/native-operations.d.ts.map +1 -0
  97. package/dist/native-operations.js +115 -0
  98. package/dist/native-pinned-write.d.ts +5 -0
  99. package/dist/native-pinned-write.d.ts.map +1 -0
  100. package/dist/native-pinned-write.js +115 -0
  101. package/dist/native.d.ts +14 -0
  102. package/dist/native.d.ts.map +1 -0
  103. package/dist/native.js +204 -0
  104. package/dist/output-sibling.d.ts +8 -0
  105. package/dist/output-sibling.d.ts.map +1 -0
  106. package/dist/output-sibling.js +122 -0
  107. package/dist/output.d.ts +2 -0
  108. package/dist/output.d.ts.map +1 -1
  109. package/dist/output.js +32 -5
  110. package/dist/owner-dacl.d.ts +31 -0
  111. package/dist/owner-dacl.d.ts.map +1 -0
  112. package/dist/owner-dacl.js +27 -0
  113. package/dist/path-policy.js +1 -1
  114. package/dist/permissions-public.d.ts +2 -0
  115. package/dist/permissions-public.d.ts.map +1 -1
  116. package/dist/permissions-public.js +2 -0
  117. package/dist/permissions.d.ts.map +1 -1
  118. package/dist/permissions.js +6 -0
  119. package/dist/pinned-operation.d.ts +2 -0
  120. package/dist/pinned-operation.d.ts.map +1 -0
  121. package/dist/pinned-operation.js +35 -0
  122. package/dist/pinned-write.d.ts +3 -16
  123. package/dist/pinned-write.d.ts.map +1 -1
  124. package/dist/pinned-write.js +20 -85
  125. package/dist/private-directory.d.ts +5 -0
  126. package/dist/private-directory.d.ts.map +1 -0
  127. package/dist/private-directory.js +13 -0
  128. package/dist/private-temp-workspace.d.ts +6 -2
  129. package/dist/private-temp-workspace.d.ts.map +1 -1
  130. package/dist/private-temp-workspace.js +40 -13
  131. package/dist/publish-file-failure.d.ts +27 -0
  132. package/dist/publish-file-failure.d.ts.map +1 -0
  133. package/dist/publish-file-failure.js +21 -0
  134. package/dist/publish-file.d.ts +21 -0
  135. package/dist/publish-file.d.ts.map +1 -0
  136. package/dist/publish-file.js +351 -0
  137. package/dist/read-opened-file.d.ts +3 -0
  138. package/dist/read-opened-file.d.ts.map +1 -1
  139. package/dist/read-opened-file.js +1 -0
  140. package/dist/replace-file-copy-fallback.d.ts +34 -0
  141. package/dist/replace-file-copy-fallback.d.ts.map +1 -0
  142. package/dist/replace-file-copy-fallback.js +339 -0
  143. package/dist/replace-file.d.ts +6 -2
  144. package/dist/replace-file.d.ts.map +1 -1
  145. package/dist/replace-file.js +39 -90
  146. package/dist/root-context.d.ts +4 -1
  147. package/dist/root-context.d.ts.map +1 -1
  148. package/dist/root-context.js +20 -1
  149. package/dist/root-impl.d.ts +5 -0
  150. package/dist/root-impl.d.ts.map +1 -1
  151. package/dist/root-impl.js +128 -225
  152. package/dist/root-path-existing.d.ts +3 -0
  153. package/dist/root-path-existing.d.ts.map +1 -0
  154. package/dist/root-path-existing.js +69 -0
  155. package/dist/root-path.d.ts +1 -2
  156. package/dist/root-path.d.ts.map +1 -1
  157. package/dist/root-path.js +67 -81
  158. package/dist/root-walk.d.ts +41 -0
  159. package/dist/root-walk.d.ts.map +1 -0
  160. package/dist/root-walk.js +114 -0
  161. package/dist/root.d.ts +2 -0
  162. package/dist/root.d.ts.map +1 -1
  163. package/dist/secret-file.d.ts +5 -2
  164. package/dist/secret-file.d.ts.map +1 -1
  165. package/dist/secret-file.js +20 -2
  166. package/dist/secret-read-async.d.ts +4 -0
  167. package/dist/secret-read-async.d.ts.map +1 -0
  168. package/dist/secret-read-async.js +95 -0
  169. package/dist/secret.d.ts +2 -1
  170. package/dist/secret.d.ts.map +1 -1
  171. package/dist/secret.js +2 -1
  172. package/dist/sibling-temp.d.ts.map +1 -1
  173. package/dist/sibling-temp.js +1 -0
  174. package/dist/sidecar-lock-handle.d.ts +20 -0
  175. package/dist/sidecar-lock-handle.d.ts.map +1 -0
  176. package/dist/sidecar-lock-handle.js +28 -0
  177. package/dist/sidecar-lock-policy.d.ts +10 -0
  178. package/dist/sidecar-lock-policy.d.ts.map +1 -0
  179. package/dist/sidecar-lock-policy.js +29 -0
  180. package/dist/sidecar-lock-reclaim.d.ts +22 -6
  181. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  182. package/dist/sidecar-lock-reclaim.js +106 -20
  183. package/dist/sidecar-lock-types.d.ts +56 -0
  184. package/dist/sidecar-lock-types.d.ts.map +1 -0
  185. package/dist/sidecar-lock-types.js +1 -0
  186. package/dist/sidecar-lock.d.ts +2 -45
  187. package/dist/sidecar-lock.d.ts.map +1 -1
  188. package/dist/sidecar-lock.js +94 -55
  189. package/dist/temp-cleanup.d.ts +7 -1
  190. package/dist/temp-cleanup.d.ts.map +1 -1
  191. package/dist/temp-cleanup.js +35 -5
  192. package/dist/temp.d.ts +2 -1
  193. package/dist/temp.d.ts.map +1 -1
  194. package/dist/test-hooks.d.ts +3 -0
  195. package/dist/test-hooks.d.ts.map +1 -1
  196. package/dist/windows-permissions-native.d.ts +9 -0
  197. package/dist/windows-permissions-native.d.ts.map +1 -0
  198. package/dist/windows-permissions-native.js +31 -0
  199. package/docs/archive.md +131 -13
  200. package/docs/atomic.md +30 -1
  201. package/docs/config.md +51 -21
  202. package/docs/durability.md +211 -1
  203. package/docs/errors.md +36 -6
  204. package/docs/filename.md +0 -0
  205. package/docs/index.md +11 -8
  206. package/docs/install.md +27 -26
  207. package/docs/json-store.md +18 -3
  208. package/docs/migrating-to-0.5.md +196 -0
  209. package/docs/native-helper.md +85 -0
  210. package/docs/native.md +134 -0
  211. package/docs/output.md +37 -11
  212. package/docs/path.md +1 -1
  213. package/docs/permissions.md +84 -1
  214. package/docs/quickstart.md +5 -2
  215. package/docs/reading.md +4 -4
  216. package/docs/root.md +31 -15
  217. package/docs/secret-file.md +50 -1
  218. package/docs/security-model.md +29 -8
  219. package/docs/sidecar-lock.md +92 -7
  220. package/docs/temp.md +24 -1
  221. package/docs/test-hooks.md +22 -2
  222. package/docs/testing.md +18 -7
  223. package/docs/types.md +3 -1
  224. package/docs/walk.md +55 -0
  225. package/docs/writing.md +3 -3
  226. package/package.json +8 -2
  227. package/dist/pinned-helper.d.ts +0 -9
  228. package/dist/pinned-helper.d.ts.map +0 -1
  229. package/dist/pinned-helper.js +0 -19
  230. package/dist/pinned-path.d.ts +0 -7
  231. package/dist/pinned-path.d.ts.map +0 -1
  232. package/dist/pinned-path.js +0 -21
  233. package/dist/pinned-python-config.d.ts +0 -9
  234. package/dist/pinned-python-config.d.ts.map +0 -1
  235. package/dist/pinned-python-config.js +0 -38
  236. package/dist/pinned-python.d.ts +0 -12
  237. package/dist/pinned-python.d.ts.map +0 -1
  238. package/dist/pinned-python.js +0 -688
  239. 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"}