@foldrun/core 0.6.0 → 1.0.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 (98) hide show
  1. package/README.md +14 -1
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +3 -0
  4. package/dist/src/agent-import.d.ts +21 -0
  5. package/dist/src/agent-import.js +116 -0
  6. package/dist/src/api-tools.js +14 -2
  7. package/dist/src/approvals.d.ts +8 -1
  8. package/dist/src/approvals.js +10 -5
  9. package/dist/src/completions.js +1 -0
  10. package/dist/src/confine.d.ts +1 -0
  11. package/dist/src/confine.js +33 -4
  12. package/dist/src/deploy.d.ts +9 -0
  13. package/dist/src/deploy.js +31 -3
  14. package/dist/src/evals.js +22 -5
  15. package/dist/src/hooks.d.ts +114 -0
  16. package/dist/src/hooks.js +370 -0
  17. package/dist/src/host-env.d.ts +14 -0
  18. package/dist/src/host-env.js +26 -0
  19. package/dist/src/install-helper.d.ts +17 -0
  20. package/dist/src/install-helper.js +134 -0
  21. package/dist/src/install-tool.d.ts +128 -0
  22. package/dist/src/install-tool.js +133 -0
  23. package/dist/src/kinds.d.ts +1 -1
  24. package/dist/src/kinds.js +26 -0
  25. package/dist/src/library.d.ts +1 -1
  26. package/dist/src/library.js +17 -1
  27. package/dist/src/mail.js +1 -1
  28. package/dist/src/model-credential.d.ts +16 -0
  29. package/dist/src/model-credential.js +40 -0
  30. package/dist/src/model-env.d.ts +17 -0
  31. package/dist/src/model-env.js +57 -0
  32. package/dist/src/notify.d.ts +8 -0
  33. package/dist/src/notify.js +10 -1
  34. package/dist/src/package.d.ts +67 -0
  35. package/dist/src/package.js +594 -0
  36. package/dist/src/platform.d.ts +5 -0
  37. package/dist/src/platform.js +1 -0
  38. package/dist/src/provider-health.js +3 -0
  39. package/dist/src/providers.d.ts +5 -0
  40. package/dist/src/providers.js +21 -1
  41. package/dist/src/run-container.d.ts +10 -0
  42. package/dist/src/run-container.js +72 -14
  43. package/dist/src/runner.d.ts +205 -0
  44. package/dist/src/runner.js +273 -36
  45. package/dist/src/runtime.d.ts +77 -0
  46. package/dist/src/runtime.js +230 -5
  47. package/dist/src/script-tools.d.ts +2 -0
  48. package/dist/src/script-tools.js +3 -1
  49. package/dist/src/secrets.d.ts +5 -0
  50. package/dist/src/secrets.js +37 -6
  51. package/dist/src/starter.d.ts +12 -0
  52. package/dist/src/starter.js +38 -4
  53. package/dist/src/step-exec.d.ts +21 -2
  54. package/dist/src/step-exec.js +165 -7
  55. package/dist/src/storage.d.ts +7 -1
  56. package/dist/src/storage.js +14 -1
  57. package/dist/src/store.d.ts +31 -1
  58. package/dist/src/store.js +90 -8
  59. package/dist/src/tool-names.d.ts +3 -1
  60. package/dist/src/tool-names.js +4 -2
  61. package/dist/src/tool-test.js +8 -1
  62. package/dist/src/zip.d.ts +17 -0
  63. package/dist/src/zip.js +184 -0
  64. package/index.ts +3 -0
  65. package/package.json +18 -1
  66. package/src/agent-import.ts +154 -0
  67. package/src/api-tools.ts +16 -1
  68. package/src/approvals.ts +10 -3
  69. package/src/completions.ts +1 -0
  70. package/src/confine.ts +36 -5
  71. package/src/deploy.ts +28 -3
  72. package/src/evals.ts +21 -3
  73. package/src/hooks.ts +406 -0
  74. package/src/host-env.ts +27 -0
  75. package/src/install-helper.ts +143 -0
  76. package/src/install-tool.ts +154 -0
  77. package/src/kinds.ts +28 -0
  78. package/src/library.ts +18 -2
  79. package/src/mail.ts +1 -1
  80. package/src/model-credential.ts +41 -0
  81. package/src/model-env.ts +66 -0
  82. package/src/notify.ts +11 -1
  83. package/src/package.ts +664 -0
  84. package/src/platform.ts +6 -0
  85. package/src/provider-health.ts +3 -0
  86. package/src/providers.ts +27 -1
  87. package/src/run-container.ts +75 -14
  88. package/src/runner.ts +279 -36
  89. package/src/runtime.ts +235 -5
  90. package/src/script-tools.ts +5 -1
  91. package/src/secrets.ts +34 -6
  92. package/src/starter.ts +44 -4
  93. package/src/step-exec.ts +171 -11
  94. package/src/storage.ts +13 -0
  95. package/src/store.ts +89 -7
  96. package/src/tool-names.ts +4 -2
  97. package/src/tool-test.ts +8 -1
  98. package/src/zip.ts +189 -0
package/src/zip.ts ADDED
@@ -0,0 +1,189 @@
1
+ // A small zip reader and writer — enough for an export package, no dependency.
2
+ //
3
+ // What a person downloads from "Export" has to open with a double-click on
4
+ // any machine, which is a zip, not a tarball. The format here is the plain
5
+ // one: deflate or stored entries, a central directory, no zip64, no
6
+ // encryption. The reader refuses anything outside that rather than guessing,
7
+ // and it is written to be handed untrusted bytes: every offset is bounds
8
+ // checked, sizes are capped before anything is inflated, and a path that
9
+ // climbs out (`..`, absolute, a drive letter) is an error, not a skip.
10
+
11
+ import zlib from "node:zlib";
12
+
13
+ export interface ZipEntry {
14
+ path: string;
15
+ data: Buffer;
16
+ }
17
+
18
+ export class ZipError extends Error {}
19
+
20
+ export interface UnzipLimits {
21
+ /** Most entries the archive may hold. */
22
+ maxEntries?: number;
23
+ /** Largest one file, uncompressed. */
24
+ maxFileBytes?: number;
25
+ /** All files together, uncompressed. */
26
+ maxTotalBytes?: number;
27
+ }
28
+
29
+ const CRC_TABLE = (() => {
30
+ const t = new Uint32Array(256);
31
+ for (let n = 0; n < 256; n++) {
32
+ let c = n;
33
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
34
+ t[n] = c >>> 0;
35
+ }
36
+ return t;
37
+ })();
38
+
39
+ export function crc32(buf: Buffer): number {
40
+ let c = 0xffffffff;
41
+ for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]!) & 0xff]! ^ (c >>> 8);
42
+ return (c ^ 0xffffffff) >>> 0;
43
+ }
44
+
45
+ /** DOS date and time for the entry headers. */
46
+ function dosTime(d: Date): { time: number; date: number } {
47
+ return {
48
+ time: (d.getHours() << 11) | (d.getMinutes() << 5) | Math.floor(d.getSeconds() / 2),
49
+ date: (Math.max(0, d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(),
50
+ };
51
+ }
52
+
53
+ export function zip(entries: ZipEntry[], now = new Date()): Buffer {
54
+ const { time, date } = dosTime(now);
55
+ const locals: Buffer[] = [];
56
+ const centrals: Buffer[] = [];
57
+ let offset = 0;
58
+ const seen = new Set<string>();
59
+ for (const e of entries) {
60
+ const name = safeEntryPath(e.path);
61
+ if (seen.has(name)) throw new ZipError(`duplicate entry: ${name}`);
62
+ seen.add(name);
63
+ const nameBuf = Buffer.from(name, "utf8");
64
+ const deflated = zlib.deflateRawSync(e.data);
65
+ const stored = deflated.length >= e.data.length;
66
+ const body = stored ? e.data : deflated;
67
+ const crc = crc32(e.data);
68
+
69
+ const local = Buffer.alloc(30);
70
+ local.writeUInt32LE(0x04034b50, 0);
71
+ local.writeUInt16LE(20, 4); // version needed
72
+ local.writeUInt16LE(0x0800, 6); // UTF-8 names
73
+ local.writeUInt16LE(stored ? 0 : 8, 8);
74
+ local.writeUInt16LE(time, 10);
75
+ local.writeUInt16LE(date, 12);
76
+ local.writeUInt32LE(crc, 14);
77
+ local.writeUInt32LE(body.length, 18);
78
+ local.writeUInt32LE(e.data.length, 22);
79
+ local.writeUInt16LE(nameBuf.length, 26);
80
+ local.writeUInt16LE(0, 28);
81
+ locals.push(local, nameBuf, body);
82
+
83
+ const central = Buffer.alloc(46);
84
+ central.writeUInt32LE(0x02014b50, 0);
85
+ central.writeUInt16LE(0x0314, 4); // made by: unix, 2.0
86
+ central.writeUInt16LE(20, 6);
87
+ central.writeUInt16LE(0x0800, 8);
88
+ central.writeUInt16LE(stored ? 0 : 8, 10);
89
+ central.writeUInt16LE(time, 12);
90
+ central.writeUInt16LE(date, 14);
91
+ central.writeUInt32LE(crc, 16);
92
+ central.writeUInt32LE(body.length, 20);
93
+ central.writeUInt32LE(e.data.length, 24);
94
+ central.writeUInt16LE(nameBuf.length, 28);
95
+ central.writeUInt32LE((0o100644 << 16) >>> 0, 38); // a regular file, rw-r--r--
96
+ central.writeUInt32LE(offset, 42);
97
+ centrals.push(central, nameBuf);
98
+
99
+ offset += local.length + nameBuf.length + body.length;
100
+ }
101
+ const cd = Buffer.concat(centrals);
102
+ const end = Buffer.alloc(22);
103
+ end.writeUInt32LE(0x06054b50, 0);
104
+ end.writeUInt16LE(entries.length, 8);
105
+ end.writeUInt16LE(entries.length, 10);
106
+ end.writeUInt32LE(cd.length, 12);
107
+ end.writeUInt32LE(offset, 16);
108
+ return Buffer.concat([...locals, cd, end]);
109
+ }
110
+
111
+ export function unzip(buf: Buffer, limits: UnzipLimits = {}): ZipEntry[] {
112
+ const maxEntries = limits.maxEntries ?? 2000;
113
+ const maxFile = limits.maxFileBytes ?? 1024 * 1024;
114
+ const maxTotal = limits.maxTotalBytes ?? 50 * 1024 * 1024;
115
+
116
+ // The end-of-central-directory record: within the last 22 + 65535 bytes.
117
+ let eocd = -1;
118
+ for (let i = buf.length - 22; i >= Math.max(0, buf.length - 22 - 0xffff); i--) {
119
+ if (buf.readUInt32LE(i) === 0x06054b50) {
120
+ eocd = i;
121
+ break;
122
+ }
123
+ }
124
+ if (eocd < 0) throw new ZipError("not a zip file");
125
+ const count = buf.readUInt16LE(eocd + 10);
126
+ const cdSize = buf.readUInt32LE(eocd + 12);
127
+ const cdOffset = buf.readUInt32LE(eocd + 16);
128
+ if (count === 0xffff || cdOffset === 0xffffffff) throw new ZipError("zip64 archives are not supported");
129
+ if (count > maxEntries) throw new ZipError(`too many files: ${count} (at most ${maxEntries})`);
130
+ if (cdOffset + cdSize > eocd) throw new ZipError("corrupt zip: central directory out of range");
131
+
132
+ const out: ZipEntry[] = [];
133
+ const seen = new Set<string>();
134
+ let total = 0;
135
+ let p = cdOffset;
136
+ for (let n = 0; n < count; n++) {
137
+ if (p + 46 > eocd || buf.readUInt32LE(p) !== 0x02014b50) throw new ZipError("corrupt zip: bad central directory entry");
138
+ const flags = buf.readUInt16LE(p + 8);
139
+ const method = buf.readUInt16LE(p + 10);
140
+ const crc = buf.readUInt32LE(p + 16);
141
+ const csize = buf.readUInt32LE(p + 20);
142
+ const usize = buf.readUInt32LE(p + 24);
143
+ const nameLen = buf.readUInt16LE(p + 28);
144
+ const extraLen = buf.readUInt16LE(p + 30);
145
+ const commentLen = buf.readUInt16LE(p + 32);
146
+ const localAt = buf.readUInt32LE(p + 42);
147
+ if (p + 46 + nameLen > eocd) throw new ZipError("corrupt zip: name out of range");
148
+ const rawName = buf.subarray(p + 46, p + 46 + nameLen).toString("utf8");
149
+ p += 46 + nameLen + extraLen + commentLen;
150
+
151
+ if (rawName.endsWith("/")) continue; // a directory entry
152
+ // macOS "Compress" adds these beside every file; they are not content.
153
+ if (/(^|\/)__MACOSX\//.test(rawName) || /(^|\/)\.DS_Store$/.test(rawName)) continue;
154
+ if (flags & 0x1) throw new ZipError(`${rawName}: encrypted entries are not supported`);
155
+ if (method !== 0 && method !== 8) throw new ZipError(`${rawName}: compression method ${method} is not supported`);
156
+ const name = safeEntryPath(rawName);
157
+ if (seen.has(name)) throw new ZipError(`duplicate entry: ${name}`);
158
+ seen.add(name);
159
+ if (usize > maxFile) throw new ZipError(`${name} is too large (${usize} bytes, at most ${maxFile})`);
160
+ total += usize;
161
+ if (total > maxTotal) throw new ZipError(`the files are too large together (more than ${maxTotal} bytes)`);
162
+
163
+ if (localAt + 30 > buf.length || buf.readUInt32LE(localAt) !== 0x04034b50) throw new ZipError(`${name}: corrupt local header`);
164
+ const start = localAt + 30 + buf.readUInt16LE(localAt + 26) + buf.readUInt16LE(localAt + 28);
165
+ if (start + csize > buf.length) throw new ZipError(`${name}: data out of range`);
166
+ const raw = buf.subarray(start, start + csize);
167
+ let data: Buffer;
168
+ try {
169
+ // maxOutputLength: the header's size is the attacker's to choose, so the
170
+ // inflate itself is capped too — a bomb stops here, not in memory.
171
+ data = method === 0 ? Buffer.from(raw) : zlib.inflateRawSync(raw, { maxOutputLength: maxFile + 1 });
172
+ } catch {
173
+ throw new ZipError(`${name}: could not be decompressed`);
174
+ }
175
+ if (data.length !== usize) throw new ZipError(`${name}: size does not match its header`);
176
+ if (crc32(data) !== crc) throw new ZipError(`${name}: checksum mismatch`);
177
+ out.push({ path: name, data });
178
+ }
179
+ return out;
180
+ }
181
+
182
+ /** A relative, forward-slash path that stays inside the archive's root. */
183
+ function safeEntryPath(raw: string): string {
184
+ const p = raw.replaceAll("\\", "/");
185
+ if (p.startsWith("/") || /^[a-zA-Z]:/.test(p) || p.includes("\0")) throw new ZipError(`unsafe path: ${raw}`);
186
+ const parts = p.split("/").filter((s) => s !== "" && s !== ".");
187
+ if (parts.length === 0 || parts.some((s) => s === "..")) throw new ZipError(`unsafe path: ${raw}`);
188
+ return parts.join("/");
189
+ }