@microck/canonfig 2.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.
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/dist/agent/agent-resolution.errors.js +42 -0
- package/dist/agent/agent-resolution.layer.js +204 -0
- package/dist/agent/agent-resolution.service.js +2259 -0
- package/dist/agent/agent-resolution.types.js +1 -0
- package/dist/agent/controlled-executor.js +704 -0
- package/dist/agent/harness-adapters.js +85 -0
- package/dist/cli/cli.js +618 -0
- package/dist/cli/exit-codes.js +28 -0
- package/dist/cli/follower-commands.js +3 -0
- package/dist/cli/render.js +56 -0
- package/dist/cli/source-commands.js +5 -0
- package/dist/domain/brand.js +29 -0
- package/dist/domain/identity.js +31 -0
- package/dist/domain/npm-package-spec.js +186 -0
- package/dist/domain/profile.js +950 -0
- package/dist/domain/recipe-versions.js +297 -0
- package/dist/domain/resource.js +259 -0
- package/dist/domain/synchronization.js +346 -0
- package/dist/enrollment/enrollment.errors.js +43 -0
- package/dist/enrollment/enrollment.layer.js +724 -0
- package/dist/enrollment/enrollment.service.js +3 -0
- package/dist/enrollment/enrollment.types.js +59 -0
- package/dist/enrollment/follower-client.js +585 -0
- package/dist/enrollment/source-server.js +313 -0
- package/dist/machine/linux.layer.js +1183 -0
- package/dist/machine/machine-state.errors.js +52 -0
- package/dist/machine/machine-state.service.js +3 -0
- package/dist/machine/machine-state.types.js +1 -0
- package/dist/machine/macos.layer.js +470 -0
- package/dist/machine/windows.layer.js +879 -0
- package/dist/profile/discovery.js +740 -0
- package/dist/profile/profile-catalog.errors.js +50 -0
- package/dist/profile/profile-catalog.layer.js +20 -0
- package/dist/profile/profile-catalog.service.js +7 -0
- package/dist/profile/profile-codec.js +153 -0
- package/dist/profile/publication.js +298 -0
- package/dist/profile/tool-catalog.js +384 -0
- package/dist/runtime/doctor.js +306 -0
- package/dist/runtime/layers.js +706 -0
- package/dist/runtime/main.js +38 -0
- package/dist/schedule/linux-schedule.js +24 -0
- package/dist/schedule/macos-schedule.js +25 -0
- package/dist/schedule/schedule-manager.errors.js +17 -0
- package/dist/schedule/schedule-manager.layer.js +205 -0
- package/dist/schedule/schedule-manager.service.js +3 -0
- package/dist/schedule/schedule-manager.types.js +114 -0
- package/dist/schedule/windows-schedule.js +25 -0
- package/dist/state/state-repository.errors.js +55 -0
- package/dist/state/state-repository.layer.js +1507 -0
- package/dist/state/state-repository.service.js +3 -0
- package/dist/state/state-repository.types.js +1 -0
- package/dist/state/state-schema.js +298 -0
- package/dist/synchronization/config-codec.js +97 -0
- package/dist/synchronization/executor.js +700 -0
- package/dist/synchronization/follower-orchestration.js +939 -0
- package/dist/synchronization/follower-sync-config.js +81 -0
- package/dist/synchronization/npm-artifact.js +670 -0
- package/dist/synchronization/planner.js +378 -0
- package/dist/synchronization/recovery.js +397 -0
- package/dist/synchronization/resource-executors.js +1198 -0
- package/dist/synchronization/resource-plans.js +645 -0
- package/dist/synchronization/synchronization.errors.js +102 -0
- package/dist/synchronization/synchronization.layer.js +97 -0
- package/dist/synchronization/synchronization.service.js +11 -0
- package/dist/synchronization/synchronization.types.js +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { lstat, open, realpath } from "node:fs/promises";
|
|
4
|
+
import { win32 } from "node:path";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { AgentExecutionCancelledError, AgentExecutionTimeoutError, AgentInputLimitError, AgentOutputLimitError, AgentProcessError, } from "./agent-resolution.errors.js";
|
|
7
|
+
const decoder = new TextDecoder();
|
|
8
|
+
const terminateProcessTree = (child) => {
|
|
9
|
+
const processId = child.pid;
|
|
10
|
+
if (processId === undefined) {
|
|
11
|
+
child.kill("SIGKILL");
|
|
12
|
+
return Promise.resolve();
|
|
13
|
+
}
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
return new Promise((resolveTermination) => {
|
|
16
|
+
let finished = false;
|
|
17
|
+
let timer;
|
|
18
|
+
const finish = () => {
|
|
19
|
+
if (finished)
|
|
20
|
+
return;
|
|
21
|
+
finished = true;
|
|
22
|
+
if (timer !== undefined)
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
child.kill("SIGKILL");
|
|
25
|
+
resolveTermination();
|
|
26
|
+
};
|
|
27
|
+
const killer = spawn(win32.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"), ["/pid", String(processId), "/t", "/f"], {
|
|
28
|
+
shell: false,
|
|
29
|
+
stdio: "ignore",
|
|
30
|
+
windowsHide: true,
|
|
31
|
+
});
|
|
32
|
+
killer.once("error", finish);
|
|
33
|
+
killer.once("close", finish);
|
|
34
|
+
timer = setTimeout(() => {
|
|
35
|
+
killer.kill();
|
|
36
|
+
finish();
|
|
37
|
+
}, 5_000);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
process.kill(-processId, "SIGKILL");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
child.kill("SIGKILL");
|
|
45
|
+
}
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
};
|
|
48
|
+
const sameRequirementIdentity = (left, right) => left.dev === right.dev
|
|
49
|
+
&& left.ino === right.ino
|
|
50
|
+
&& left.size === right.size;
|
|
51
|
+
const pipRequirementFilesUnchanged = async (files) => {
|
|
52
|
+
for (const expected of files) {
|
|
53
|
+
let handle;
|
|
54
|
+
try {
|
|
55
|
+
const before = await lstat(expected.path);
|
|
56
|
+
if (!before.isFile() || !sameRequirementIdentity(before, expected.identity)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (await realpath(expected.path) !== expected.canonicalPath)
|
|
60
|
+
return false;
|
|
61
|
+
handle = await open(expected.path, "r");
|
|
62
|
+
const opened = await handle.stat();
|
|
63
|
+
if (!sameRequirementIdentity(opened, expected.identity))
|
|
64
|
+
return false;
|
|
65
|
+
const buffer = Buffer.alloc(expected.identity.size + 1);
|
|
66
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
|
|
67
|
+
if (bytesRead !== expected.identity.size)
|
|
68
|
+
return false;
|
|
69
|
+
const after = await lstat(expected.path);
|
|
70
|
+
if (!sameRequirementIdentity(after, expected.identity))
|
|
71
|
+
return false;
|
|
72
|
+
if (createHash("sha256")
|
|
73
|
+
.update(buffer.subarray(0, bytesRead))
|
|
74
|
+
.digest("hex") !== expected.digest)
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (handle !== undefined)
|
|
82
|
+
await handle.close().catch(() => undefined);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
};
|
|
87
|
+
export const redactText = (value, secrets) => {
|
|
88
|
+
let redacted = value;
|
|
89
|
+
const ordered = [...new Set(secrets)]
|
|
90
|
+
.filter((secret) => secret.length > 0)
|
|
91
|
+
.sort((left, right) => right.length - left.length);
|
|
92
|
+
for (const secret of ordered)
|
|
93
|
+
redacted = redacted.replaceAll(secret, "[REDACTED]");
|
|
94
|
+
return redacted;
|
|
95
|
+
};
|
|
96
|
+
const packageManagerName = (value) => {
|
|
97
|
+
const name = value
|
|
98
|
+
.replaceAll("\\", "/")
|
|
99
|
+
.split("/")
|
|
100
|
+
.at(-1)
|
|
101
|
+
?.toLowerCase()
|
|
102
|
+
.replace(/\.(?:cmd|exe|bat|com|ps1)$/u, "")
|
|
103
|
+
.replace(/^(?:npm)-cli\.js$/u, "npm")
|
|
104
|
+
.replace(/^(?:pnpm)\.(?:cjs|js)$/u, "pnpm")
|
|
105
|
+
.replace(/^(?:yarn)\.js$/u, "yarn")
|
|
106
|
+
?? "";
|
|
107
|
+
return /^pip(?:3(?:\.\d+(?:\.\d+)*)?|-3(?:\.\d+(?:\.\d+)*)?)?$/u.test(name)
|
|
108
|
+
? "pip"
|
|
109
|
+
: name;
|
|
110
|
+
};
|
|
111
|
+
const isRegistryPackageManager = (manager) => manager === "npm"
|
|
112
|
+
|| manager === "pnpm"
|
|
113
|
+
|| manager === "yarn"
|
|
114
|
+
|| manager === "bun"
|
|
115
|
+
|| manager === "pip"
|
|
116
|
+
|| manager === "uv";
|
|
117
|
+
const canonicalRegistryOrigin = (value) => {
|
|
118
|
+
return canonicalRegistryUrl(value)?.origin;
|
|
119
|
+
};
|
|
120
|
+
const hasUnsafeUrlCharacter = (value) => [...value].some((character) => {
|
|
121
|
+
const code = character.codePointAt(0) ?? 0;
|
|
122
|
+
return code <= 0x20
|
|
123
|
+
|| (code >= 0x7f && code <= 0x9f)
|
|
124
|
+
|| "\"'<>\\ ".includes(character);
|
|
125
|
+
});
|
|
126
|
+
const hasExplicitUrlCredentialOrFragment = (value) => {
|
|
127
|
+
if (value.includes("#"))
|
|
128
|
+
return true;
|
|
129
|
+
const authority = /^https?:\/\/([^/?#]*)/iu.exec(value)?.[1];
|
|
130
|
+
return authority?.includes("@") ?? false;
|
|
131
|
+
};
|
|
132
|
+
const canonicalRegistryUrl = (value) => {
|
|
133
|
+
if (value.trim() !== value
|
|
134
|
+
|| hasUnsafeUrlCharacter(value)
|
|
135
|
+
|| hasExplicitUrlCredentialOrFragment(value))
|
|
136
|
+
return undefined;
|
|
137
|
+
try {
|
|
138
|
+
const url = new URL(value);
|
|
139
|
+
if (url.protocol !== "https:"
|
|
140
|
+
|| url.username.length > 0
|
|
141
|
+
|| url.password.length > 0
|
|
142
|
+
|| url.hash.length > 0
|
|
143
|
+
|| url.hostname.length === 0)
|
|
144
|
+
return undefined;
|
|
145
|
+
return {
|
|
146
|
+
url: url.pathname === "/" && url.search.length === 0
|
|
147
|
+
? url.origin
|
|
148
|
+
: url.href,
|
|
149
|
+
origin: url.origin,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const packageOperationRequiresRegistry = (executable, arguments_) => {
|
|
157
|
+
const manager = packageManagerName(executable);
|
|
158
|
+
if (!isRegistryPackageManager(manager))
|
|
159
|
+
return false;
|
|
160
|
+
const optionsWithValues = manager === "uv"
|
|
161
|
+
? uvCommandOptionsWithValues
|
|
162
|
+
: manager === "pip"
|
|
163
|
+
? new Set([
|
|
164
|
+
"--cache-dir",
|
|
165
|
+
"--cert",
|
|
166
|
+
"--client-cert",
|
|
167
|
+
"--config-settings",
|
|
168
|
+
"--config-setting",
|
|
169
|
+
"--constraint",
|
|
170
|
+
"-c",
|
|
171
|
+
"--extra-index-url",
|
|
172
|
+
"--find-links",
|
|
173
|
+
"-f",
|
|
174
|
+
"-i",
|
|
175
|
+
"--index-url",
|
|
176
|
+
"--proxy",
|
|
177
|
+
"--requirement",
|
|
178
|
+
"-r",
|
|
179
|
+
"--trusted-host",
|
|
180
|
+
])
|
|
181
|
+
: manager === "bun"
|
|
182
|
+
? new Set(["--config", "--cwd", "--filter", "--registry"])
|
|
183
|
+
: new Set([
|
|
184
|
+
"-C",
|
|
185
|
+
"--cache",
|
|
186
|
+
"--config-dir",
|
|
187
|
+
"--dir",
|
|
188
|
+
"--global-bin-dir",
|
|
189
|
+
"--global-dir",
|
|
190
|
+
"--prefix",
|
|
191
|
+
"--registry",
|
|
192
|
+
"--store-dir",
|
|
193
|
+
"--userconfig",
|
|
194
|
+
"--virtual-store-dir",
|
|
195
|
+
"--workspace-dir",
|
|
196
|
+
]);
|
|
197
|
+
let command;
|
|
198
|
+
let commandIndex = -1;
|
|
199
|
+
for (let index = 0; index < arguments_.length; index += 1) {
|
|
200
|
+
const argument = arguments_[index];
|
|
201
|
+
if (argument === "--")
|
|
202
|
+
return false;
|
|
203
|
+
if (!argument.startsWith("-") || argument === "-") {
|
|
204
|
+
command = argument.toLowerCase();
|
|
205
|
+
commandIndex = index;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
if (!argument.includes("=")
|
|
209
|
+
&& optionsWithValues.has(argument.split("=", 1)[0].toLowerCase()))
|
|
210
|
+
index += 1;
|
|
211
|
+
}
|
|
212
|
+
if (manager === "uv") {
|
|
213
|
+
return ((command === "tool" || command === "pip")
|
|
214
|
+
&& arguments_[commandIndex + 1]?.toLowerCase() === "install");
|
|
215
|
+
}
|
|
216
|
+
if (manager === "pip")
|
|
217
|
+
return command === "install";
|
|
218
|
+
if (manager === "bun") {
|
|
219
|
+
return command !== undefined && ["add", "i", "install", "update"].includes(command);
|
|
220
|
+
}
|
|
221
|
+
return command !== undefined && [
|
|
222
|
+
"add",
|
|
223
|
+
"ci",
|
|
224
|
+
"i",
|
|
225
|
+
"in",
|
|
226
|
+
"ins",
|
|
227
|
+
"inst",
|
|
228
|
+
"insta",
|
|
229
|
+
"instal",
|
|
230
|
+
"install",
|
|
231
|
+
"info",
|
|
232
|
+
"list",
|
|
233
|
+
"ls",
|
|
234
|
+
"outdated",
|
|
235
|
+
"prefix",
|
|
236
|
+
"root",
|
|
237
|
+
"search",
|
|
238
|
+
"view",
|
|
239
|
+
"why",
|
|
240
|
+
].includes(command);
|
|
241
|
+
};
|
|
242
|
+
const isUvFindLinksOption = (argument) => {
|
|
243
|
+
const lower = argument.toLowerCase();
|
|
244
|
+
return lower === "--find-links"
|
|
245
|
+
|| lower.startsWith("--find-links=")
|
|
246
|
+
|| (lower.startsWith("-")
|
|
247
|
+
&& !lower.startsWith("--")
|
|
248
|
+
&& lower.slice(1).includes("f"));
|
|
249
|
+
};
|
|
250
|
+
const uvRequirementFileOptions = new Set([
|
|
251
|
+
"--build-constraints",
|
|
252
|
+
"--constraint",
|
|
253
|
+
"--constraints",
|
|
254
|
+
"--excludes",
|
|
255
|
+
"--overrides",
|
|
256
|
+
"--requirement",
|
|
257
|
+
"--requirements",
|
|
258
|
+
"--with-requirements",
|
|
259
|
+
]);
|
|
260
|
+
const uvCommandOptionsWithValues = new Set([
|
|
261
|
+
"--cache-dir",
|
|
262
|
+
"--color",
|
|
263
|
+
"--config-file",
|
|
264
|
+
"--default-index",
|
|
265
|
+
"--directory",
|
|
266
|
+
"--extra-index-url",
|
|
267
|
+
"--find-links",
|
|
268
|
+
"--index",
|
|
269
|
+
"--index-url",
|
|
270
|
+
"--project",
|
|
271
|
+
"-f",
|
|
272
|
+
]);
|
|
273
|
+
const uvInstallCommand = (arguments_) => {
|
|
274
|
+
let command;
|
|
275
|
+
for (let index = 0; index < arguments_.length; index += 1) {
|
|
276
|
+
const argument = arguments_[index];
|
|
277
|
+
if (!argument.startsWith("-") || argument === "-") {
|
|
278
|
+
command = argument.toLowerCase();
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
if (!argument.includes("=")
|
|
282
|
+
&& uvCommandOptionsWithValues.has(argument.split("=", 1)[0].toLowerCase()))
|
|
283
|
+
index += 1;
|
|
284
|
+
}
|
|
285
|
+
return command === "pip" || command === "tool" ? command : undefined;
|
|
286
|
+
};
|
|
287
|
+
const hasUvRequirementFileShortOption = (argument, command) => {
|
|
288
|
+
if (!argument.startsWith("-") || argument.startsWith("--"))
|
|
289
|
+
return false;
|
|
290
|
+
const optionsWithoutValues = new Set([
|
|
291
|
+
"h",
|
|
292
|
+
"n",
|
|
293
|
+
"q",
|
|
294
|
+
"U",
|
|
295
|
+
"v",
|
|
296
|
+
...(command === "tool" ? ["e"] : []),
|
|
297
|
+
]);
|
|
298
|
+
const requirementOptions = new Set([
|
|
299
|
+
"b",
|
|
300
|
+
"c",
|
|
301
|
+
...(command === "pip" ? ["r"] : []),
|
|
302
|
+
]);
|
|
303
|
+
for (const option of argument.slice(1)) {
|
|
304
|
+
if (requirementOptions.has(option))
|
|
305
|
+
return true;
|
|
306
|
+
if (!optionsWithoutValues.has(option))
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
};
|
|
311
|
+
const isUvRequirementFileOption = (argument, command) => {
|
|
312
|
+
const name = argument.split("=", 1)[0].toLowerCase();
|
|
313
|
+
return uvRequirementFileOptions.has(name)
|
|
314
|
+
|| hasUvRequirementFileShortOption(argument, command);
|
|
315
|
+
};
|
|
316
|
+
const hasUvRequirementFileOption = (arguments_) => {
|
|
317
|
+
const command = uvInstallCommand(arguments_);
|
|
318
|
+
return arguments_.some((argument) => isUvRequirementFileOption(argument, command));
|
|
319
|
+
};
|
|
320
|
+
const uvInsecureHostOptions = new Set([
|
|
321
|
+
"--allow-insecure-host",
|
|
322
|
+
"--trusted-host",
|
|
323
|
+
]);
|
|
324
|
+
const isUvInsecureHostOption = (argument) => uvInsecureHostOptions.has(argument.split("=", 1)[0].toLowerCase());
|
|
325
|
+
const packageRegistryInvocationIsSafe = (executable, arguments_, packageRegistryOrigin) => {
|
|
326
|
+
const manager = packageManagerName(executable);
|
|
327
|
+
const registry = canonicalRegistryOrigin(packageRegistryOrigin);
|
|
328
|
+
if (registry === undefined)
|
|
329
|
+
return false;
|
|
330
|
+
if (arguments_.includes("--"))
|
|
331
|
+
return false;
|
|
332
|
+
if (manager === "uv" && hasUvRequirementFileOption(arguments_))
|
|
333
|
+
return false;
|
|
334
|
+
const indexOptions = manager === "uv"
|
|
335
|
+
? new Set([
|
|
336
|
+
"--default-index",
|
|
337
|
+
"--index-url",
|
|
338
|
+
"--extra-index-url",
|
|
339
|
+
"-f",
|
|
340
|
+
"--find-links",
|
|
341
|
+
"--index",
|
|
342
|
+
])
|
|
343
|
+
: manager === "pip"
|
|
344
|
+
? new Set(["-i", "--index-url", "--extra-index-url", "-f", "--find-links"])
|
|
345
|
+
: new Set();
|
|
346
|
+
const unsafeOptions = manager === "uv"
|
|
347
|
+
? new Set(["--config-file"])
|
|
348
|
+
: manager === "pip"
|
|
349
|
+
? new Set([
|
|
350
|
+
"--cert",
|
|
351
|
+
"--client-cert",
|
|
352
|
+
"--config-setting",
|
|
353
|
+
"--config-settings",
|
|
354
|
+
"--proxy",
|
|
355
|
+
"--trusted-host",
|
|
356
|
+
])
|
|
357
|
+
: new Set();
|
|
358
|
+
for (let index = 0; index < arguments_.length; index += 1) {
|
|
359
|
+
const argument = arguments_[index];
|
|
360
|
+
const separator = argument.indexOf("=");
|
|
361
|
+
const name = (separator > 0 ? argument.slice(0, separator) : argument)
|
|
362
|
+
.toLowerCase();
|
|
363
|
+
if (unsafeOptions.has(name))
|
|
364
|
+
return false;
|
|
365
|
+
if (manager === "uv"
|
|
366
|
+
&& isUvInsecureHostOption(argument))
|
|
367
|
+
return false;
|
|
368
|
+
if (manager === "uv"
|
|
369
|
+
&& (isUvFindLinksOption(argument)
|
|
370
|
+
|| ["--extra-index-url", "--index"].includes(name)))
|
|
371
|
+
return false;
|
|
372
|
+
if ((manager === "uv" && name === "--no-config")
|
|
373
|
+
|| (manager === "pip" && name === "--isolated")) {
|
|
374
|
+
if (separator > 0 && argument.slice(separator + 1).toLowerCase() !== "true") {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!indexOptions.has(name))
|
|
379
|
+
continue;
|
|
380
|
+
const value = separator > 0
|
|
381
|
+
? argument.slice(separator + 1)
|
|
382
|
+
: arguments_[index + 1];
|
|
383
|
+
if (separator === -1)
|
|
384
|
+
index += 1;
|
|
385
|
+
if (value === undefined || canonicalRegistryOrigin(value) !== registry) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return true;
|
|
390
|
+
};
|
|
391
|
+
const hasPipRequirementFileOption = (arguments_) => arguments_.some((argument) => {
|
|
392
|
+
const name = argument.split("=", 1)[0].toLowerCase();
|
|
393
|
+
return name === "-r"
|
|
394
|
+
|| name === "-c"
|
|
395
|
+
|| name === "--requirement"
|
|
396
|
+
|| name === "--constraint"
|
|
397
|
+
|| (argument.length > 2
|
|
398
|
+
&& (argument.startsWith("-r") || argument.startsWith("-c")));
|
|
399
|
+
});
|
|
400
|
+
const protectedPackageEnvironment = (name) => {
|
|
401
|
+
const lower = name.toLowerCase();
|
|
402
|
+
return lower.startsWith("npm_config_")
|
|
403
|
+
|| lower.startsWith("pnpm_config_")
|
|
404
|
+
|| lower.startsWith("bun_config_")
|
|
405
|
+
|| lower.startsWith("yarn_")
|
|
406
|
+
|| lower.startsWith("uv_")
|
|
407
|
+
|| lower.startsWith("pip_")
|
|
408
|
+
|| lower === "http_proxy"
|
|
409
|
+
|| lower === "https_proxy"
|
|
410
|
+
|| lower === "ftp_proxy"
|
|
411
|
+
|| lower === "all_proxy"
|
|
412
|
+
|| lower === "no_proxy"
|
|
413
|
+
|| lower === "netrc"
|
|
414
|
+
|| lower === "requests_ca_bundle"
|
|
415
|
+
|| lower === "curl_ca_bundle"
|
|
416
|
+
|| lower === "ssl_cert_file"
|
|
417
|
+
|| lower === "ssl_cert_dir"
|
|
418
|
+
|| lower === "node_tls_reject_unauthorized"
|
|
419
|
+
|| lower === "node_extra_ca_certs";
|
|
420
|
+
};
|
|
421
|
+
const packageManagerConfigurationEnvironment = (executable) => {
|
|
422
|
+
const manager = packageManagerName(executable);
|
|
423
|
+
const emptyConfiguration = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
424
|
+
if (manager === "npm" || manager === "pnpm") {
|
|
425
|
+
return [
|
|
426
|
+
{ name: "NPM_CONFIG_USERCONFIG", value: emptyConfiguration },
|
|
427
|
+
{ name: "NPM_CONFIG_GLOBALCONFIG", value: emptyConfiguration },
|
|
428
|
+
{ name: "NPM_CONFIG_LOCATION", value: "global" },
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
if (manager === "bun") {
|
|
432
|
+
return [{ name: "BUN_CONFIG_FILE", value: emptyConfiguration }];
|
|
433
|
+
}
|
|
434
|
+
if (manager === "uv") {
|
|
435
|
+
return [
|
|
436
|
+
{ name: "UV_CONFIG_FILE", value: emptyConfiguration },
|
|
437
|
+
{ name: "PIP_CONFIG_FILE", value: emptyConfiguration },
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
if (manager === "pip") {
|
|
441
|
+
return [{ name: "PIP_CONFIG_FILE", value: emptyConfiguration }];
|
|
442
|
+
}
|
|
443
|
+
if (manager === "yarn") {
|
|
444
|
+
return [{ name: "YARN_RC_FILENAME", value: emptyConfiguration }];
|
|
445
|
+
}
|
|
446
|
+
return [];
|
|
447
|
+
};
|
|
448
|
+
export const sanitizedPackageManagerEnvironment = (executable, environment = [], packageRegistryOrigin, packageRegistryScopes = []) => {
|
|
449
|
+
const manager = packageManagerName(executable);
|
|
450
|
+
if (!isRegistryPackageManager(manager))
|
|
451
|
+
return environment;
|
|
452
|
+
const registry = packageRegistryOrigin === undefined
|
|
453
|
+
? undefined
|
|
454
|
+
: canonicalRegistryUrl(packageRegistryOrigin)?.url;
|
|
455
|
+
return [
|
|
456
|
+
...environment.filter((entry) => !protectedPackageEnvironment(entry.name)),
|
|
457
|
+
...packageManagerConfigurationEnvironment(executable),
|
|
458
|
+
...(registry === undefined
|
|
459
|
+
? []
|
|
460
|
+
: manager === "npm" || manager === "pnpm"
|
|
461
|
+
? [
|
|
462
|
+
{ name: "NPM_CONFIG_REGISTRY", value: registry },
|
|
463
|
+
...(manager === "pnpm"
|
|
464
|
+
? [{ name: "PNPM_CONFIG_REGISTRY", value: registry }]
|
|
465
|
+
: []),
|
|
466
|
+
...packageRegistryScopes.map((scope) => ({
|
|
467
|
+
name: `npm_config_${scope}:registry`,
|
|
468
|
+
value: registry,
|
|
469
|
+
})),
|
|
470
|
+
...(manager === "pnpm"
|
|
471
|
+
? packageRegistryScopes.map((scope) => ({
|
|
472
|
+
name: `pnpm_config_${scope}:registry`,
|
|
473
|
+
value: registry,
|
|
474
|
+
}))
|
|
475
|
+
: []),
|
|
476
|
+
]
|
|
477
|
+
: manager === "bun"
|
|
478
|
+
? [{ name: "BUN_CONFIG_REGISTRY", value: registry }]
|
|
479
|
+
: manager === "uv"
|
|
480
|
+
? [
|
|
481
|
+
{ name: "UV_DEFAULT_INDEX", value: registry },
|
|
482
|
+
{ name: "UV_INDEX_URL", value: registry },
|
|
483
|
+
{ name: "PIP_INDEX_URL", value: registry },
|
|
484
|
+
]
|
|
485
|
+
: manager === "pip"
|
|
486
|
+
? [{ name: "PIP_INDEX_URL", value: registry }]
|
|
487
|
+
: manager === "yarn"
|
|
488
|
+
? [{ name: "YARN_NPM_REGISTRY_SERVER", value: registry }]
|
|
489
|
+
: []),
|
|
490
|
+
];
|
|
491
|
+
};
|
|
492
|
+
export const controlledEnvironment = (entries, unset = []) => {
|
|
493
|
+
const environment = { ...process.env };
|
|
494
|
+
const blocked = new Set(unset.map((name) => name.toLowerCase()));
|
|
495
|
+
for (const name of Object.keys(environment)) {
|
|
496
|
+
if (blocked.has(name.toLowerCase()))
|
|
497
|
+
delete environment[name];
|
|
498
|
+
}
|
|
499
|
+
for (const entry of entries) {
|
|
500
|
+
if (entry.name.includes("=") || entry.name.length === 0)
|
|
501
|
+
continue;
|
|
502
|
+
if (process.platform === "win32") {
|
|
503
|
+
const existing = Object.keys(environment).find((name) => name.toLowerCase() === entry.name.toLowerCase());
|
|
504
|
+
if (existing !== undefined)
|
|
505
|
+
delete environment[existing];
|
|
506
|
+
}
|
|
507
|
+
environment[entry.name] = entry.value;
|
|
508
|
+
}
|
|
509
|
+
return environment;
|
|
510
|
+
};
|
|
511
|
+
const cancelled = (input) => new AgentExecutionCancelledError({ executable: input.executable });
|
|
512
|
+
export const executeControlledProcess = (input) => {
|
|
513
|
+
const inputBytes = input.standardInput?.byteLength ?? 0;
|
|
514
|
+
if (inputBytes > input.maximumInputBytes) {
|
|
515
|
+
return Effect.fail(new AgentInputLimitError({
|
|
516
|
+
actualBytes: inputBytes,
|
|
517
|
+
maximumBytes: input.maximumInputBytes,
|
|
518
|
+
}));
|
|
519
|
+
}
|
|
520
|
+
if (input.signal?.aborted === true)
|
|
521
|
+
return Effect.fail(cancelled(input));
|
|
522
|
+
if (isRegistryPackageManager(packageManagerName(input.executable))
|
|
523
|
+
&& input.arguments.includes("--")) {
|
|
524
|
+
return Effect.fail(new AgentProcessError({
|
|
525
|
+
executable: input.executable,
|
|
526
|
+
message: "package-manager separator form is not authorized",
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
if (packageManagerName(input.executable) === "uv"
|
|
530
|
+
&& hasUvRequirementFileOption(input.arguments)) {
|
|
531
|
+
return Effect.fail(new AgentProcessError({
|
|
532
|
+
executable: input.executable,
|
|
533
|
+
message: "uv requirement, constraint, or override files are not authorized",
|
|
534
|
+
}));
|
|
535
|
+
}
|
|
536
|
+
if (packageManagerName(input.executable) === "uv"
|
|
537
|
+
&& input.arguments.some(isUvInsecureHostOption)) {
|
|
538
|
+
return Effect.fail(new AgentProcessError({
|
|
539
|
+
executable: input.executable,
|
|
540
|
+
message: "uv insecure-host overrides are not authorized",
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
543
|
+
if (packageManagerName(input.executable) === "pip"
|
|
544
|
+
&& hasPipRequirementFileOption(input.arguments)
|
|
545
|
+
&& input.pipRequirementFiles === undefined) {
|
|
546
|
+
return Effect.fail(new AgentProcessError({
|
|
547
|
+
executable: input.executable,
|
|
548
|
+
message: "pip requirement files are not authorized by the resolution boundary",
|
|
549
|
+
}));
|
|
550
|
+
}
|
|
551
|
+
if (packageManagerName(input.executable) === "pip"
|
|
552
|
+
&& hasPipRequirementFileOption(input.arguments)
|
|
553
|
+
&& input.pipRequirementFiles !== undefined
|
|
554
|
+
&& input.pipRequirementFiles.length === 0) {
|
|
555
|
+
return Effect.fail(new AgentProcessError({
|
|
556
|
+
executable: input.executable,
|
|
557
|
+
message: "pip requirement files are not authorized by the resolution boundary",
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
if (packageOperationRequiresRegistry(input.executable, input.arguments)
|
|
561
|
+
&& (input.packageRegistryOrigin === undefined
|
|
562
|
+
|| !packageRegistryInvocationIsSafe(input.executable, input.arguments, input.packageRegistryOrigin))) {
|
|
563
|
+
return Effect.fail(new AgentProcessError({
|
|
564
|
+
executable: input.executable,
|
|
565
|
+
message: "package-manager registry origin is not explicitly authorized",
|
|
566
|
+
}));
|
|
567
|
+
}
|
|
568
|
+
return Effect.tryPromise({
|
|
569
|
+
try: async (effectSignal) => {
|
|
570
|
+
if (packageManagerName(input.executable) === "pip"
|
|
571
|
+
&& input.pipRequirementFiles !== undefined
|
|
572
|
+
&& !(await pipRequirementFilesUnchanged(input.pipRequirementFiles))) {
|
|
573
|
+
throw new AgentProcessError({
|
|
574
|
+
executable: input.executable,
|
|
575
|
+
message: "pip requirement or constraint input changed after authorization",
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return new Promise((resolve, reject) => {
|
|
579
|
+
const output = [];
|
|
580
|
+
const errors = [];
|
|
581
|
+
let capturedBytes = 0;
|
|
582
|
+
let settled = false;
|
|
583
|
+
let limitExceeded = false;
|
|
584
|
+
let timedOut = false;
|
|
585
|
+
let wasCancelled = false;
|
|
586
|
+
let termination;
|
|
587
|
+
const child = spawn(input.executable, [...input.arguments], {
|
|
588
|
+
cwd: input.workingDirectory,
|
|
589
|
+
detached: process.platform !== "win32",
|
|
590
|
+
env: (() => {
|
|
591
|
+
const manager = packageManagerName(input.executable);
|
|
592
|
+
const packageManager = isRegistryPackageManager(manager);
|
|
593
|
+
const inherited = input.environment ?? [];
|
|
594
|
+
const protectedInput = inherited
|
|
595
|
+
.filter((entry) => protectedPackageEnvironment(entry.name))
|
|
596
|
+
.map((entry) => entry.name);
|
|
597
|
+
const environment = sanitizedPackageManagerEnvironment(input.executable, inherited, input.packageRegistryOrigin, input.packageRegistryScopes);
|
|
598
|
+
return controlledEnvironment(environment, [
|
|
599
|
+
...(input.environmentUnset ?? []),
|
|
600
|
+
...protectedInput,
|
|
601
|
+
...(input.environmentUnsetPrefixes ?? []).flatMap((prefix) => Object.keys(process.env).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase()))),
|
|
602
|
+
...(packageManager
|
|
603
|
+
? Object.keys(process.env).filter(protectedPackageEnvironment)
|
|
604
|
+
: []),
|
|
605
|
+
]);
|
|
606
|
+
})(),
|
|
607
|
+
shell: false,
|
|
608
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
609
|
+
});
|
|
610
|
+
const finishWith = (cause) => {
|
|
611
|
+
if (settled)
|
|
612
|
+
return;
|
|
613
|
+
settled = true;
|
|
614
|
+
reject(cause);
|
|
615
|
+
};
|
|
616
|
+
const terminate = () => {
|
|
617
|
+
termination ??= terminateProcessTree(child);
|
|
618
|
+
};
|
|
619
|
+
const capture = (target) => (chunk) => {
|
|
620
|
+
capturedBytes += chunk.byteLength;
|
|
621
|
+
if (capturedBytes > input.maximumOutputBytes) {
|
|
622
|
+
limitExceeded = true;
|
|
623
|
+
terminate();
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
target.push(chunk);
|
|
627
|
+
};
|
|
628
|
+
child.stdout.on("data", capture(output));
|
|
629
|
+
child.stderr.on("data", capture(errors));
|
|
630
|
+
child.once("error", (cause) => {
|
|
631
|
+
finishWith(new AgentProcessError({
|
|
632
|
+
executable: input.executable,
|
|
633
|
+
message: redactText(String(cause), input.secrets),
|
|
634
|
+
}));
|
|
635
|
+
});
|
|
636
|
+
const timer = setTimeout(() => {
|
|
637
|
+
timedOut = true;
|
|
638
|
+
terminate();
|
|
639
|
+
}, input.timeoutMilliseconds);
|
|
640
|
+
const abort = () => {
|
|
641
|
+
wasCancelled = true;
|
|
642
|
+
terminate();
|
|
643
|
+
};
|
|
644
|
+
effectSignal.addEventListener("abort", abort, { once: true });
|
|
645
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
646
|
+
child.once("close", (exitCode, signal) => {
|
|
647
|
+
const complete = () => {
|
|
648
|
+
clearTimeout(timer);
|
|
649
|
+
effectSignal.removeEventListener("abort", abort);
|
|
650
|
+
input.signal?.removeEventListener("abort", abort);
|
|
651
|
+
if (settled)
|
|
652
|
+
return;
|
|
653
|
+
if (wasCancelled) {
|
|
654
|
+
finishWith(cancelled(input));
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (timedOut) {
|
|
658
|
+
finishWith(new AgentExecutionTimeoutError({
|
|
659
|
+
executable: input.executable,
|
|
660
|
+
timeoutMilliseconds: input.timeoutMilliseconds,
|
|
661
|
+
}));
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (limitExceeded) {
|
|
665
|
+
finishWith(new AgentOutputLimitError({
|
|
666
|
+
executable: input.executable,
|
|
667
|
+
maximumBytes: input.maximumOutputBytes,
|
|
668
|
+
}));
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
settled = true;
|
|
672
|
+
resolve({
|
|
673
|
+
executable: input.executable,
|
|
674
|
+
arguments: input.arguments,
|
|
675
|
+
exitCode,
|
|
676
|
+
signal,
|
|
677
|
+
outputBytes: capturedBytes,
|
|
678
|
+
stdout: redactText(decoder.decode(Buffer.concat(output)), input.secrets),
|
|
679
|
+
stderr: redactText(decoder.decode(Buffer.concat(errors)), input.secrets),
|
|
680
|
+
});
|
|
681
|
+
};
|
|
682
|
+
if (termination === undefined)
|
|
683
|
+
complete();
|
|
684
|
+
else
|
|
685
|
+
void termination.then(complete);
|
|
686
|
+
});
|
|
687
|
+
if (input.standardInput === undefined)
|
|
688
|
+
child.stdin.end();
|
|
689
|
+
else
|
|
690
|
+
child.stdin.end(input.standardInput);
|
|
691
|
+
});
|
|
692
|
+
},
|
|
693
|
+
catch: (cause) => cause instanceof AgentInputLimitError
|
|
694
|
+
|| cause instanceof AgentExecutionCancelledError
|
|
695
|
+
|| cause instanceof AgentExecutionTimeoutError
|
|
696
|
+
|| cause instanceof AgentOutputLimitError
|
|
697
|
+
|| cause instanceof AgentProcessError
|
|
698
|
+
? cause
|
|
699
|
+
: new AgentProcessError({
|
|
700
|
+
executable: input.executable,
|
|
701
|
+
message: redactText(String(cause), input.secrets),
|
|
702
|
+
}),
|
|
703
|
+
});
|
|
704
|
+
};
|