@cassiomc1/forgeloop 0.1.14 → 0.1.15
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/.forgeloop/forgeloop.gitignore +1 -0
- package/LOOP_ENGINEERING.md +29 -6
- package/PROTOCOL_INTEGRATION.md +33 -0
- package/README.md +21 -10
- package/THREAT_MODEL.md +5 -0
- package/package.json +1 -1
- package/schemas/check.schema.json +2 -0
- package/schemas/execution.schema.json +63 -0
- package/src/cli.js +64 -12
- package/src/commands/run-check.js +83 -0
- package/src/commands/validate-protocol.js +18 -0
- package/src/core/artifacts.js +8 -0
- package/src/core/bundles.js +72 -0
- package/src/core/checks.js +16 -0
- package/src/core/completion-artifacts.js +229 -47
- package/src/core/completion.js +19 -1
- package/src/core/evidence-readiness.js +26 -1
- package/src/core/execution.js +185 -0
- package/src/core/schema-validation.js +1 -0
- package/src/core/templates.js +1 -0
- package/src/core/verification-capability.js +629 -18
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
1
2
|
import {
|
|
2
3
|
E_AUTHORITY_UNTRUSTED_SOURCE,
|
|
3
4
|
resolveTrustedAuthority,
|
|
@@ -11,6 +12,7 @@ export {
|
|
|
11
12
|
|
|
12
13
|
export const E_VERIFICATION_TOOL_UNAVAILABLE = "E_VERIFICATION_TOOL_UNAVAILABLE";
|
|
13
14
|
export const E_INSTALLATION_AUTHORITY_REQUIRED = "E_INSTALLATION_AUTHORITY_REQUIRED";
|
|
15
|
+
export const E_COMMAND_RESOLUTION_AMBIGUOUS = "E_COMMAND_RESOLUTION_AMBIGUOUS";
|
|
14
16
|
export const E_AUTHORITY_INVALID = "E_AUTHORITY_INVALID";
|
|
15
17
|
export const E_AUTHORITY_SCOPE_MISMATCH = "E_AUTHORITY_SCOPE_MISMATCH";
|
|
16
18
|
|
|
@@ -155,8 +157,397 @@ function extractToolFromArgs(args) {
|
|
|
155
157
|
return null;
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
+
function extractNpmExecTool(args) {
|
|
161
|
+
if (!Array.isArray(args) || args.length === 0) return null;
|
|
162
|
+
for (let idx = 0; idx < args.length; idx++) {
|
|
163
|
+
const arg = args[idx];
|
|
164
|
+
if (arg === "-p" || arg === "--package") {
|
|
165
|
+
if (args[idx + 1] && !args[idx + 1].startsWith("-")) {
|
|
166
|
+
return args[idx + 1];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (arg.startsWith("--package=")) {
|
|
170
|
+
return arg.slice("--package=".length);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
let afterDoubleDash = false;
|
|
174
|
+
for (let idx = 0; idx < args.length; idx++) {
|
|
175
|
+
const arg = args[idx];
|
|
176
|
+
if (arg === "--") {
|
|
177
|
+
afterDoubleDash = true;
|
|
178
|
+
if (args[idx + 1] && !args[idx + 1].startsWith("-")) {
|
|
179
|
+
return args[idx + 1];
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!afterDoubleDash && !arg.startsWith("-")) {
|
|
184
|
+
return arg;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function normalizeExecutableName(binaryToken) {
|
|
191
|
+
const base = binaryToken.split(/[\\/]/u).pop() ?? binaryToken;
|
|
192
|
+
return base.toLowerCase().replace(/\.(?:cmd|bat|exe)$/u, "");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const NPM_INSTALL_COMMANDS = new Set([
|
|
196
|
+
"install", "add", "i", "in", "ins", "inst", "insta", "instal", "isnt", "isnta", "isntal", "isntall",
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
const NPM_CI_COMMANDS = new Set([
|
|
200
|
+
"ci", "clean-install", "ic", "install-clean", "isntall-clean",
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
const NPM_INSTALL_TEST_COMMANDS = new Set([
|
|
204
|
+
"install-test", "it",
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
const NPM_INSTALL_CI_TEST_COMMANDS = new Set([
|
|
208
|
+
"install-ci-test", "cit", "clean-install-test", "sit",
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
const NPM_EXEC_COMMANDS = new Set([
|
|
212
|
+
"exec", "x",
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
const NPM_INIT_COMMANDS = new Set([
|
|
216
|
+
"init", "create", "innit",
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
const NPM_SCRIPT_COMMANDS = new Set([
|
|
220
|
+
"test", "t", "tst", "start", "stop", "restart", "run", "run-script", "rum", "urn",
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const NPM_KNOWN_NON_INSTALLING_COMMANDS = new Set([
|
|
224
|
+
"v", "view", "info", "show", "list", "ls", "outdated", "config", "help", "help-search", "doctor", "ping", "root", "prefix", "bin", "whoami",
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
const NPM_KNOWN_BOOLEAN_OPTIONS = new Set([
|
|
228
|
+
"--silent", "--json", "--long", "--parseable", "--global", "-g", "--force", "--yes", "-y", "--no",
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
const NPM_OPTIONS_WITH_VALUE = new Set([
|
|
232
|
+
"--workspace",
|
|
233
|
+
"-w",
|
|
234
|
+
"--loglevel",
|
|
235
|
+
"--prefix",
|
|
236
|
+
"-C",
|
|
237
|
+
"--userconfig",
|
|
238
|
+
"--registry",
|
|
239
|
+
"--cache",
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
export function parseNpmInvocationArgs(rest) {
|
|
243
|
+
if (!Array.isArray(rest)) {
|
|
244
|
+
return {
|
|
245
|
+
subcommand: null,
|
|
246
|
+
subcommandIndex: -1,
|
|
247
|
+
args: [],
|
|
248
|
+
leadingOptions: [],
|
|
249
|
+
workspace: null,
|
|
250
|
+
workspaces: false,
|
|
251
|
+
ambiguous: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const leadingOptions = [];
|
|
256
|
+
let workspace = null;
|
|
257
|
+
let workspaces = false;
|
|
258
|
+
|
|
259
|
+
let i = 0;
|
|
260
|
+
while (i < rest.length) {
|
|
261
|
+
const arg = rest[i];
|
|
262
|
+
|
|
263
|
+
if (arg === "--") {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!arg.startsWith("-")) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (arg === "--workspaces" || arg === "--ws") {
|
|
272
|
+
workspaces = true;
|
|
273
|
+
leadingOptions.push(arg);
|
|
274
|
+
i += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (arg.startsWith("--workspace=")) {
|
|
279
|
+
workspace = arg.slice("--workspace=".length) || null;
|
|
280
|
+
leadingOptions.push(arg);
|
|
281
|
+
i += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (arg.startsWith("-w=")) {
|
|
286
|
+
workspace = arg.slice(3) || null;
|
|
287
|
+
leadingOptions.push(arg);
|
|
288
|
+
i += 1;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (arg === "--workspace" || arg === "-w") {
|
|
293
|
+
leadingOptions.push(arg);
|
|
294
|
+
const value = rest[i + 1] ?? null;
|
|
295
|
+
if (value !== null && !value.startsWith("-")) {
|
|
296
|
+
workspace = value;
|
|
297
|
+
leadingOptions.push(value);
|
|
298
|
+
i += 2;
|
|
299
|
+
} else {
|
|
300
|
+
workspace = value;
|
|
301
|
+
i += 1;
|
|
302
|
+
}
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (NPM_OPTIONS_WITH_VALUE.has(arg)) {
|
|
307
|
+
leadingOptions.push(arg);
|
|
308
|
+
const value = rest[i + 1] ?? null;
|
|
309
|
+
if (value !== null && !value.startsWith("-")) {
|
|
310
|
+
leadingOptions.push(value);
|
|
311
|
+
i += 2;
|
|
312
|
+
} else {
|
|
313
|
+
i += 1;
|
|
314
|
+
}
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (/^--[^=]+=/.test(arg)) {
|
|
319
|
+
leadingOptions.push(arg);
|
|
320
|
+
i += 1;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (NPM_KNOWN_BOOLEAN_OPTIONS.has(arg)) {
|
|
325
|
+
leadingOptions.push(arg);
|
|
326
|
+
i += 1;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (/^--/.test(arg)) {
|
|
331
|
+
const next = rest[i + 1];
|
|
332
|
+
if (next && !next.startsWith("-")) {
|
|
333
|
+
return {
|
|
334
|
+
subcommand: null,
|
|
335
|
+
subcommandIndex: -1,
|
|
336
|
+
args: [],
|
|
337
|
+
leadingOptions,
|
|
338
|
+
workspace,
|
|
339
|
+
workspaces,
|
|
340
|
+
ambiguous: true,
|
|
341
|
+
reason: "NPM_OPTION_VALUE_AMBIGUOUS",
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
leadingOptions.push(arg);
|
|
345
|
+
i += 1;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (arg.startsWith("-")) {
|
|
350
|
+
leadingOptions.push(arg);
|
|
351
|
+
i += 1;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const subcommand = rest[i] ? rest[i].toLowerCase() : null;
|
|
357
|
+
const trailingArgs = subcommand ? rest.slice(i + 1) : [];
|
|
358
|
+
|
|
359
|
+
for (let j = 0; j < trailingArgs.length; j++) {
|
|
360
|
+
const tArg = trailingArgs[j];
|
|
361
|
+
if (tArg === "--") break;
|
|
362
|
+
if (tArg === "--workspaces" || tArg === "--ws") {
|
|
363
|
+
workspaces = true;
|
|
364
|
+
} else if (tArg.startsWith("--workspace=")) {
|
|
365
|
+
workspace = tArg.slice("--workspace=".length) || null;
|
|
366
|
+
} else if (tArg.startsWith("-w=")) {
|
|
367
|
+
workspace = tArg.slice(3) || null;
|
|
368
|
+
} else if (tArg === "--workspace" || tArg === "-w") {
|
|
369
|
+
const val = trailingArgs[j + 1];
|
|
370
|
+
if (val && !val.startsWith("-")) {
|
|
371
|
+
workspace = val;
|
|
372
|
+
j += 1;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const ambiguous = subcommand === null;
|
|
378
|
+
return {
|
|
379
|
+
subcommand,
|
|
380
|
+
subcommandIndex: subcommand ? i : -1,
|
|
381
|
+
args: trailingArgs,
|
|
382
|
+
leadingOptions,
|
|
383
|
+
workspace,
|
|
384
|
+
workspaces,
|
|
385
|
+
ambiguous,
|
|
386
|
+
...(ambiguous ? { reason: "NPM_SUBCOMMAND_AMBIGUOUS" } : {}),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function parseNpmInvocation(argv) {
|
|
391
|
+
const tokens = unwrapCommandArgv(Array.isArray(argv) ? argv : tokenizeCommand(argv));
|
|
392
|
+
if (!tokens || tokens.length === 0) {
|
|
393
|
+
return {
|
|
394
|
+
subcommand: null,
|
|
395
|
+
subcommandIndex: -1,
|
|
396
|
+
args: [],
|
|
397
|
+
leadingOptions: [],
|
|
398
|
+
workspace: null,
|
|
399
|
+
workspaces: false,
|
|
400
|
+
ambiguous: true,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const binary = normalizeExecutableName(tokens[0]);
|
|
404
|
+
if (binary !== "npm") {
|
|
405
|
+
return {
|
|
406
|
+
subcommand: null,
|
|
407
|
+
subcommandIndex: -1,
|
|
408
|
+
args: [],
|
|
409
|
+
leadingOptions: [],
|
|
410
|
+
workspace: null,
|
|
411
|
+
workspaces: false,
|
|
412
|
+
ambiguous: true,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return parseNpmInvocationArgs(tokens.slice(1));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function npmWorkspaceSelection(npmInvocation) {
|
|
419
|
+
return {
|
|
420
|
+
scoped: Boolean(npmInvocation?.workspace || npmInvocation?.workspaces),
|
|
421
|
+
workspace: npmInvocation?.workspace ?? null,
|
|
422
|
+
allWorkspaces: npmInvocation?.workspaces === true,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function classifyNpmInvocation(npm) {
|
|
427
|
+
const sub = npm.subcommand;
|
|
428
|
+
|
|
429
|
+
if (npm.ambiguous) {
|
|
430
|
+
return {
|
|
431
|
+
resolutionMode: "UNKNOWN",
|
|
432
|
+
mayInstall: true,
|
|
433
|
+
installer: "npm",
|
|
434
|
+
tool: null,
|
|
435
|
+
reason: npm.reason || "NPM_SUBCOMMAND_AMBIGUOUS",
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (NPM_EXEC_COMMANDS.has(sub)) {
|
|
440
|
+
return {
|
|
441
|
+
resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
|
|
442
|
+
mayInstall: true,
|
|
443
|
+
installer: `npm ${sub}`,
|
|
444
|
+
tool: extractNpmExecTool(npm.args),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (NPM_INSTALL_COMMANDS.has(sub) || NPM_CI_COMMANDS.has(sub) || NPM_INSTALL_TEST_COMMANDS.has(sub) || NPM_INSTALL_CI_TEST_COMMANDS.has(sub)) {
|
|
449
|
+
let tool = null;
|
|
450
|
+
let installer = `npm ${sub}`;
|
|
451
|
+
if (NPM_INSTALL_COMMANDS.has(sub)) {
|
|
452
|
+
tool = extractToolFromArgs(npm.args);
|
|
453
|
+
installer = "npm";
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
resolutionMode: "EXPLICIT_INSTALLATION",
|
|
457
|
+
mayInstall: true,
|
|
458
|
+
installer,
|
|
459
|
+
tool,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (NPM_INIT_COMMANDS.has(sub)) {
|
|
464
|
+
let firstMeaningful = null;
|
|
465
|
+
for (let i = 0; i < npm.args.length; i++) {
|
|
466
|
+
const arg = npm.args[i];
|
|
467
|
+
if (arg === "--") {
|
|
468
|
+
const next = npm.args[i + 1];
|
|
469
|
+
firstMeaningful = next && !next.startsWith("-") ? next : null;
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
if (!arg.startsWith("-")) {
|
|
473
|
+
firstMeaningful = arg;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (firstMeaningful) {
|
|
478
|
+
return {
|
|
479
|
+
resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
|
|
480
|
+
mayInstall: true,
|
|
481
|
+
installer: `npm ${sub}`,
|
|
482
|
+
tool: firstMeaningful,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
resolutionMode: "LOCAL_PACKAGE_BINARY",
|
|
487
|
+
mayInstall: false,
|
|
488
|
+
installer: null,
|
|
489
|
+
tool: null,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (sub === "update" || sub === "up" || sub === "upgrade") {
|
|
494
|
+
return {
|
|
495
|
+
resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
|
|
496
|
+
mayInstall: true,
|
|
497
|
+
installer: `npm ${sub}`,
|
|
498
|
+
tool: extractToolFromArgs(npm.args),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (sub === "audit") {
|
|
503
|
+
if (npm.args.includes("fix")) {
|
|
504
|
+
return {
|
|
505
|
+
resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
|
|
506
|
+
mayInstall: true,
|
|
507
|
+
installer: "npm audit fix",
|
|
508
|
+
tool: null,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
resolutionMode: "LOCAL_PACKAGE_BINARY",
|
|
513
|
+
mayInstall: false,
|
|
514
|
+
installer: null,
|
|
515
|
+
tool: null,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (NPM_SCRIPT_COMMANDS.has(sub)) {
|
|
520
|
+
return {
|
|
521
|
+
resolutionMode: "LOCAL_PACKAGE_BINARY",
|
|
522
|
+
mayInstall: false,
|
|
523
|
+
installer: null,
|
|
524
|
+
tool: null,
|
|
525
|
+
dispatch: {
|
|
526
|
+
kind: "npm-script-command",
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (NPM_KNOWN_NON_INSTALLING_COMMANDS.has(sub)) {
|
|
532
|
+
return {
|
|
533
|
+
resolutionMode: "LOCAL_PACKAGE_BINARY",
|
|
534
|
+
mayInstall: false,
|
|
535
|
+
installer: null,
|
|
536
|
+
tool: null,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
resolutionMode: "UNKNOWN",
|
|
542
|
+
mayInstall: true,
|
|
543
|
+
installer: "npm",
|
|
544
|
+
tool: null,
|
|
545
|
+
reason: "NPM_COMMAND_UNCLASSIFIED",
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function classifySingleCommand(commandInput) {
|
|
550
|
+
const tokens = Array.isArray(commandInput) ? [...commandInput] : tokenizeCommand(commandInput);
|
|
160
551
|
if (tokens.length === 0) {
|
|
161
552
|
return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
|
|
162
553
|
}
|
|
@@ -170,9 +561,13 @@ function classifySingleCommand(commandString) {
|
|
|
170
561
|
}
|
|
171
562
|
|
|
172
563
|
const binaryToken = tokens[i];
|
|
173
|
-
const binary =
|
|
564
|
+
const binary = normalizeExecutableName(binaryToken);
|
|
174
565
|
const rest = tokens.slice(i + 1);
|
|
175
566
|
|
|
567
|
+
if (binary === "call") {
|
|
568
|
+
return classifyCommandResolution(rest);
|
|
569
|
+
}
|
|
570
|
+
|
|
176
571
|
if (binaryToken.includes("node_modules/.bin/") || binaryToken.startsWith("./node_modules/")) {
|
|
177
572
|
return { resolutionMode: "LOCAL_PACKAGE_BINARY", mayInstall: false, installer: null, tool: binary };
|
|
178
573
|
}
|
|
@@ -300,15 +695,8 @@ function classifySingleCommand(commandString) {
|
|
|
300
695
|
|
|
301
696
|
// Check npm
|
|
302
697
|
if (binary === "npm") {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
resolutionMode: "EXPLICIT_INSTALLATION",
|
|
306
|
-
mayInstall: true,
|
|
307
|
-
installer: "npm",
|
|
308
|
-
tool: extractToolFromArgs(rest.slice(1)),
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
return { resolutionMode: "LOCAL_PACKAGE_BINARY", mayInstall: false, installer: null, tool: null };
|
|
698
|
+
const npm = parseNpmInvocationArgs(rest);
|
|
699
|
+
return classifyNpmInvocation(npm);
|
|
312
700
|
}
|
|
313
701
|
|
|
314
702
|
// Check python/pip
|
|
@@ -360,12 +748,30 @@ function classifySingleCommand(commandString) {
|
|
|
360
748
|
return { resolutionMode: "LOCAL_EXECUTABLE", mayInstall: false, installer: null, tool: null };
|
|
361
749
|
}
|
|
362
750
|
|
|
363
|
-
export function classifyCommandResolution(
|
|
364
|
-
if (
|
|
751
|
+
export function classifyCommandResolution(commandInput) {
|
|
752
|
+
if (Array.isArray(commandInput)) {
|
|
753
|
+
if (commandInput.length === 0 || commandInput.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
754
|
+
return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
|
|
755
|
+
}
|
|
756
|
+
const binary = normalizeExecutableName(commandInput[0]);
|
|
757
|
+
if (["sh", "bash", "zsh", "dash", "ksh"].includes(binary)) {
|
|
758
|
+
const shellFlagIndex = commandInput.findIndex((item, index) => index > 0 && /^-.*c/.test(item));
|
|
759
|
+
const shellCommand = shellFlagIndex >= 0 ? commandInput[shellFlagIndex + 1] : null;
|
|
760
|
+
if (shellCommand) return classifyCommandResolution(shellCommand);
|
|
761
|
+
}
|
|
762
|
+
if (binary === "cmd") {
|
|
763
|
+
const shellFlagIndex = commandInput.findIndex((item, index) => index > 0 && /^\/c$/iu.test(item));
|
|
764
|
+
const shellCommand = shellFlagIndex >= 0 ? commandInput.slice(shellFlagIndex + 1).join(" ") : null;
|
|
765
|
+
if (shellCommand) return classifyCommandResolution(shellCommand);
|
|
766
|
+
}
|
|
767
|
+
return classifySingleCommand(commandInput);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (typeof commandInput !== "string" || commandInput.trim() === "") {
|
|
365
771
|
return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
|
|
366
772
|
}
|
|
367
773
|
|
|
368
|
-
const subcommands = splitCommandPipeline(
|
|
774
|
+
const subcommands = splitCommandPipeline(commandInput);
|
|
369
775
|
for (const subcommand of subcommands) {
|
|
370
776
|
const res = classifySingleCommand(subcommand);
|
|
371
777
|
if (res.mayInstall) {
|
|
@@ -373,7 +779,211 @@ export function classifyCommandResolution(commandString) {
|
|
|
373
779
|
}
|
|
374
780
|
}
|
|
375
781
|
|
|
376
|
-
return classifySingleCommand(subcommands[0] ||
|
|
782
|
+
return classifySingleCommand(subcommands[0] || commandInput);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function unwrapCommandArgv(argv) {
|
|
786
|
+
if (!Array.isArray(argv) || argv.length === 0) return null;
|
|
787
|
+
let i = 0;
|
|
788
|
+
while (i < argv.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(argv[i])) {
|
|
789
|
+
i++;
|
|
790
|
+
}
|
|
791
|
+
if (i >= argv.length) return null;
|
|
792
|
+
const binary = normalizeExecutableName(argv[i]);
|
|
793
|
+
if (["sh", "bash", "zsh", "dash", "ksh"].includes(binary)) {
|
|
794
|
+
const shellFlagIndex = argv.findIndex((item, index) => index > i && /^-.*c/.test(item));
|
|
795
|
+
const shellCommand = shellFlagIndex >= 0 ? argv[shellFlagIndex + 1] : null;
|
|
796
|
+
if (shellCommand) return tokenizeCommand(shellCommand);
|
|
797
|
+
}
|
|
798
|
+
if (binary === "cmd") {
|
|
799
|
+
const shellFlagIndex = argv.findIndex((item, index) => index > i && /^\/c$/iu.test(item));
|
|
800
|
+
const shellCommand = shellFlagIndex >= 0 ? argv.slice(shellFlagIndex + 1).join(" ") : null;
|
|
801
|
+
if (shellCommand) return tokenizeCommand(shellCommand);
|
|
802
|
+
}
|
|
803
|
+
if (binary === "call") {
|
|
804
|
+
return argv.slice(i + 1);
|
|
805
|
+
}
|
|
806
|
+
return argv.slice(i);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
export function getNpmScriptName(argv) {
|
|
810
|
+
const npm = parseNpmInvocation(argv);
|
|
811
|
+
if (npm.ambiguous || !npm.subcommand) {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const sub = npm.subcommand;
|
|
816
|
+
const args = npm.args;
|
|
817
|
+
|
|
818
|
+
if (["test", "t", "tst"].includes(sub)) return "test";
|
|
819
|
+
if (["start", "stop", "restart"].includes(sub)) return sub;
|
|
820
|
+
if (["run", "run-script", "rum", "urn"].includes(sub)) {
|
|
821
|
+
let afterDoubleDash = false;
|
|
822
|
+
for (let idx = 0; idx < args.length; idx++) {
|
|
823
|
+
const arg = args[idx];
|
|
824
|
+
if (arg === "--") {
|
|
825
|
+
afterDoubleDash = true;
|
|
826
|
+
if (args[idx + 1] && !args[idx + 1].startsWith("-")) {
|
|
827
|
+
return args[idx + 1];
|
|
828
|
+
}
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (!afterDoubleDash && !arg.startsWith("-")) {
|
|
832
|
+
if (idx > 0 && (args[idx - 1] === "-w" || args[idx - 1] === "--workspace" || NPM_OPTIONS_WITH_VALUE.has(args[idx - 1]))) {
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
return arg;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
export function getNpmLifecycleCandidates({ scriptName, scripts = {} } = {}) {
|
|
843
|
+
if (scriptName === "restart") {
|
|
844
|
+
if (typeof scripts?.restart === "string" && scripts.restart.trim() !== "") {
|
|
845
|
+
return ["prerestart", "restart", "postrestart"];
|
|
846
|
+
}
|
|
847
|
+
return [
|
|
848
|
+
"prerestart",
|
|
849
|
+
"prestop",
|
|
850
|
+
"stop",
|
|
851
|
+
"poststop",
|
|
852
|
+
"prestart",
|
|
853
|
+
"start",
|
|
854
|
+
"poststart",
|
|
855
|
+
"postrestart",
|
|
856
|
+
];
|
|
857
|
+
}
|
|
858
|
+
return [`pre${scriptName}`, scriptName, `post${scriptName}`];
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
export const MAX_NPM_SCRIPT_DEPTH = 16;
|
|
862
|
+
|
|
863
|
+
async function resolveNpmScriptRisk({
|
|
864
|
+
scriptName,
|
|
865
|
+
packageJson,
|
|
866
|
+
visited = new Set(),
|
|
867
|
+
depth = 0,
|
|
868
|
+
} = {}) {
|
|
869
|
+
if (depth > MAX_NPM_SCRIPT_DEPTH) {
|
|
870
|
+
return {
|
|
871
|
+
resolutionMode: "UNKNOWN",
|
|
872
|
+
mayInstall: true,
|
|
873
|
+
installer: "npm-script",
|
|
874
|
+
tool: null,
|
|
875
|
+
reason: "MAX_SCRIPT_DEPTH",
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (visited.has(scriptName)) {
|
|
880
|
+
return {
|
|
881
|
+
resolutionMode: "UNKNOWN",
|
|
882
|
+
mayInstall: true,
|
|
883
|
+
installer: "npm-script",
|
|
884
|
+
tool: null,
|
|
885
|
+
reason: "SCRIPT_CYCLE",
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const nextVisited = new Set(visited);
|
|
890
|
+
nextVisited.add(scriptName);
|
|
891
|
+
|
|
892
|
+
const candidates = getNpmLifecycleCandidates({
|
|
893
|
+
scriptName,
|
|
894
|
+
scripts: packageJson?.scripts ?? {},
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
for (const candidate of candidates) {
|
|
898
|
+
const scriptBody = packageJson?.scripts?.[candidate];
|
|
899
|
+
if (typeof scriptBody !== "string" || scriptBody.trim() === "") {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const direct = classifyCommandResolution(scriptBody);
|
|
904
|
+
if (direct.mayInstall) {
|
|
905
|
+
return {
|
|
906
|
+
resolutionMode: direct.resolutionMode,
|
|
907
|
+
mayInstall: true,
|
|
908
|
+
installer: direct.installer ?? "npm-script",
|
|
909
|
+
tool: direct.tool,
|
|
910
|
+
dispatch: {
|
|
911
|
+
kind: "npm-script",
|
|
912
|
+
scriptName: candidate,
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const subcommands = splitCommandPipeline(scriptBody);
|
|
918
|
+
for (const subcommand of subcommands) {
|
|
919
|
+
const nestedScriptName = getNpmScriptName(subcommand);
|
|
920
|
+
if (nestedScriptName) {
|
|
921
|
+
const nested = await resolveNpmScriptRisk({
|
|
922
|
+
scriptName: nestedScriptName,
|
|
923
|
+
packageJson,
|
|
924
|
+
visited: nextVisited,
|
|
925
|
+
depth: depth + 1,
|
|
926
|
+
});
|
|
927
|
+
if (nested?.mayInstall) {
|
|
928
|
+
return nested;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async function readPackageJsonIfPresent(cwd) {
|
|
938
|
+
if (!cwd || typeof cwd !== "string") return null;
|
|
939
|
+
try {
|
|
940
|
+
const pkgPath = path.resolve(cwd, "package.json");
|
|
941
|
+
const raw = await fs.readFile(pkgPath, "utf8");
|
|
942
|
+
return JSON.parse(raw);
|
|
943
|
+
} catch {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export async function resolveExecutionResolution({ argv, cwd } = {}) {
|
|
949
|
+
const direct = classifyCommandResolution(argv);
|
|
950
|
+
if (direct.mayInstall && direct.resolutionMode !== "UNKNOWN") return direct;
|
|
951
|
+
|
|
952
|
+
const npmInvocation = parseNpmInvocation(argv);
|
|
953
|
+
const scriptName = getNpmScriptName(argv);
|
|
954
|
+
|
|
955
|
+
if (scriptName && (npmInvocation.workspace || npmInvocation.workspaces)) {
|
|
956
|
+
return {
|
|
957
|
+
resolutionMode: "UNKNOWN",
|
|
958
|
+
mayInstall: true,
|
|
959
|
+
installer: "npm-workspace",
|
|
960
|
+
tool: null,
|
|
961
|
+
reason: "NPM_WORKSPACE_SCRIPT_UNRESOLVED",
|
|
962
|
+
dispatch: {
|
|
963
|
+
kind: "npm-workspace-script",
|
|
964
|
+
scriptName,
|
|
965
|
+
},
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (direct.mayInstall) return direct;
|
|
970
|
+
if (!scriptName) return direct;
|
|
971
|
+
|
|
972
|
+
const packageJson = await readPackageJsonIfPresent(cwd);
|
|
973
|
+
if (!packageJson || typeof packageJson.scripts !== "object" || packageJson.scripts === null) {
|
|
974
|
+
return direct;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const nestedRisk = await resolveNpmScriptRisk({
|
|
978
|
+
scriptName,
|
|
979
|
+
packageJson,
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
if (nestedRisk?.mayInstall) {
|
|
983
|
+
return nestedRisk;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return direct;
|
|
377
987
|
}
|
|
378
988
|
|
|
379
989
|
export function getInstallationAuthorityRef(check) {
|
|
@@ -489,16 +1099,17 @@ export function validateAuthorityGrant({ authority, taskId, type = "SOFTWARE_INS
|
|
|
489
1099
|
}
|
|
490
1100
|
|
|
491
1101
|
export function validateVerificationAuthority(check, options = {}) {
|
|
1102
|
+
const canonicalResolution = check?.execution?.resolution ?? check?.details?.execution?.resolution;
|
|
492
1103
|
const command = check?.details?.command
|
|
493
1104
|
?? (check?.kind === "command" && typeof check?.source === "string" && !check.source.startsWith("check:")
|
|
494
1105
|
? check.source
|
|
495
1106
|
: null);
|
|
496
1107
|
|
|
497
|
-
if (!command || typeof command !== "string") {
|
|
1108
|
+
if (!canonicalResolution && (!command || typeof command !== "string")) {
|
|
498
1109
|
return { valid: true, error: null };
|
|
499
1110
|
}
|
|
500
1111
|
|
|
501
|
-
const classification = classifyCommandResolution(command);
|
|
1112
|
+
const classification = canonicalResolution ?? classifyCommandResolution(command);
|
|
502
1113
|
if (!classification.mayInstall) {
|
|
503
1114
|
return { valid: true, error: null };
|
|
504
1115
|
}
|