@cassiomc1/forgeloop 0.1.13 → 0.1.14

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.
@@ -1,5 +1,29 @@
1
+ import {
2
+ E_AUTHORITY_UNTRUSTED_SOURCE,
3
+ resolveTrustedAuthority,
4
+ } from "./trusted-authority.js";
5
+ import path from "node:path";
6
+ export {
7
+ AUTHORITY_TRUST_MODES,
8
+ createAuthorityContext,
9
+ createForgeLoopContext,
10
+ } from "./runtime-context.js";
11
+
1
12
  export const E_VERIFICATION_TOOL_UNAVAILABLE = "E_VERIFICATION_TOOL_UNAVAILABLE";
2
13
  export const E_INSTALLATION_AUTHORITY_REQUIRED = "E_INSTALLATION_AUTHORITY_REQUIRED";
14
+ export const E_AUTHORITY_INVALID = "E_AUTHORITY_INVALID";
15
+ export const E_AUTHORITY_SCOPE_MISMATCH = "E_AUTHORITY_SCOPE_MISMATCH";
16
+
17
+ export { E_AUTHORITY_UNTRUSTED_SOURCE };
18
+
19
+ export const RESOLUTION_MODES = Object.freeze([
20
+ "LOCAL_EXECUTABLE",
21
+ "LOCAL_PACKAGE_BINARY",
22
+ "NON_INSTALLING_RESOLUTION",
23
+ "INSTALL_CAPABLE_RESOLUTION",
24
+ "EXPLICIT_INSTALLATION",
25
+ "UNKNOWN",
26
+ ]);
3
27
 
4
28
  export function classifyVerificationCapability({
5
29
  available = false,
@@ -45,3 +69,478 @@ export function classifyVerificationCapability({
45
69
  message: "Verification tool is absent and installation was not authorized.",
46
70
  };
47
71
  }
72
+
73
+ function tokenizeCommand(commandString) {
74
+ const tokens = [];
75
+ let current = "";
76
+ let inSingleQuote = false;
77
+ let inDoubleQuote = false;
78
+
79
+ for (let i = 0; i < commandString.length; i++) {
80
+ const char = commandString[i];
81
+
82
+ if (char === "'" && !inDoubleQuote) {
83
+ inSingleQuote = !inSingleQuote;
84
+ } else if (char === '"' && !inSingleQuote) {
85
+ inDoubleQuote = !inDoubleQuote;
86
+ } else if (/\s/.test(char) && !inSingleQuote && !inDoubleQuote) {
87
+ if (current.length > 0) {
88
+ tokens.push(current);
89
+ current = "";
90
+ }
91
+ } else {
92
+ current += char;
93
+ }
94
+ }
95
+
96
+ if (current.length > 0) {
97
+ tokens.push(current);
98
+ }
99
+
100
+ return tokens;
101
+ }
102
+
103
+ function splitCommandPipeline(commandString) {
104
+ const parts = [];
105
+ let current = "";
106
+ let inSingleQuote = false;
107
+ let inDoubleQuote = false;
108
+
109
+ for (let i = 0; i < commandString.length; i++) {
110
+ const char = commandString[i];
111
+ const next = commandString[i + 1];
112
+
113
+ if (char === "'" && !inDoubleQuote) {
114
+ inSingleQuote = !inSingleQuote;
115
+ current += char;
116
+ } else if (char === '"' && !inSingleQuote) {
117
+ inDoubleQuote = !inDoubleQuote;
118
+ current += char;
119
+ } else if (!inSingleQuote && !inDoubleQuote) {
120
+ if ((char === "&" && next === "&") || (char === "|" && next === "|")) {
121
+ if (current.trim().length > 0) parts.push(current.trim());
122
+ current = "";
123
+ i++; // skip next char
124
+ } else if (char === ";" || char === "|") {
125
+ if (current.trim().length > 0) parts.push(current.trim());
126
+ current = "";
127
+ } else {
128
+ current += char;
129
+ }
130
+ } else {
131
+ current += char;
132
+ }
133
+ }
134
+
135
+ if (current.trim().length > 0) parts.push(current.trim());
136
+ return parts.length > 0 ? parts : [commandString];
137
+ }
138
+
139
+ function extractToolFromArgs(args) {
140
+ if (!Array.isArray(args) || args.length === 0) return null;
141
+ for (let idx = 0; idx < args.length; idx++) {
142
+ const arg = args[idx];
143
+ if (arg === "-p" || arg === "--package") {
144
+ if (args[idx + 1] && !args[idx + 1].startsWith("-")) {
145
+ return args[idx + 1];
146
+ }
147
+ }
148
+ if (arg.startsWith("--package=")) {
149
+ return arg.split("=")[1];
150
+ }
151
+ if (!arg.startsWith("-")) {
152
+ return arg;
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+
158
+ function classifySingleCommand(commandString) {
159
+ const tokens = tokenizeCommand(commandString);
160
+ if (tokens.length === 0) {
161
+ return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
162
+ }
163
+
164
+ let i = 0;
165
+ while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) {
166
+ i++;
167
+ }
168
+ if (i >= tokens.length) {
169
+ return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
170
+ }
171
+
172
+ const binaryToken = tokens[i];
173
+ const binary = path.basename(binaryToken).toLowerCase();
174
+ const rest = tokens.slice(i + 1);
175
+
176
+ if (binaryToken.includes("node_modules/.bin/") || binaryToken.startsWith("./node_modules/")) {
177
+ return { resolutionMode: "LOCAL_PACKAGE_BINARY", mayInstall: false, installer: null, tool: binary };
178
+ }
179
+
180
+ // Check npx
181
+ if (binary === "npx") {
182
+ if (rest.some((arg) => arg === "--no-install" || arg === "--no")) {
183
+ const nonInstallArgs = rest.filter((arg) => arg !== "--no-install" && arg !== "--no");
184
+ return {
185
+ resolutionMode: "NON_INSTALLING_RESOLUTION",
186
+ mayInstall: false,
187
+ installer: "npx",
188
+ tool: extractToolFromArgs(nonInstallArgs),
189
+ };
190
+ }
191
+ return {
192
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
193
+ mayInstall: true,
194
+ installer: "npx",
195
+ tool: extractToolFromArgs(rest),
196
+ };
197
+ }
198
+
199
+ // Check pnpx, bunx, uvx
200
+ if (binary === "pnpx" || binary === "bunx" || binary === "uvx") {
201
+ return {
202
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
203
+ mayInstall: true,
204
+ installer: binary,
205
+ tool: extractToolFromArgs(rest),
206
+ };
207
+ }
208
+
209
+ // Check pnpm and yarn
210
+ if (binary === "pnpm" || binary === "yarn") {
211
+ if (rest[0] === "dlx") {
212
+ return {
213
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
214
+ mayInstall: true,
215
+ installer: `${binary} dlx`,
216
+ tool: extractToolFromArgs(rest.slice(1)),
217
+ };
218
+ }
219
+ if (["add", "install", "i"].includes(rest[0])) {
220
+ return {
221
+ resolutionMode: "EXPLICIT_INSTALLATION",
222
+ mayInstall: true,
223
+ installer: binary,
224
+ tool: extractToolFromArgs(rest.slice(1)),
225
+ };
226
+ }
227
+ return { resolutionMode: "LOCAL_PACKAGE_BINARY", mayInstall: false, installer: null, tool: null };
228
+ }
229
+
230
+ // Check bun
231
+ if (binary === "bun") {
232
+ if (rest[0] === "x") {
233
+ return {
234
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
235
+ mayInstall: true,
236
+ installer: "bun x",
237
+ tool: extractToolFromArgs(rest.slice(1)),
238
+ };
239
+ }
240
+ if (["add", "install", "i"].includes(rest[0])) {
241
+ return {
242
+ resolutionMode: "EXPLICIT_INSTALLATION",
243
+ mayInstall: true,
244
+ installer: "bun",
245
+ tool: extractToolFromArgs(rest.slice(1)),
246
+ };
247
+ }
248
+ return { resolutionMode: "LOCAL_PACKAGE_BINARY", mayInstall: false, installer: null, tool: null };
249
+ }
250
+
251
+ // Check uv
252
+ if (binary === "uv") {
253
+ if (rest[0] === "tool" && rest[1] === "run") {
254
+ return {
255
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
256
+ mayInstall: true,
257
+ installer: "uv tool run",
258
+ tool: extractToolFromArgs(rest.slice(2)),
259
+ };
260
+ }
261
+ if (rest[0] === "pip" && rest[1] === "install") {
262
+ return {
263
+ resolutionMode: "EXPLICIT_INSTALLATION",
264
+ mayInstall: true,
265
+ installer: "uv pip install",
266
+ tool: extractToolFromArgs(rest.slice(2)),
267
+ };
268
+ }
269
+ if (rest[0] === "add") {
270
+ return {
271
+ resolutionMode: "EXPLICIT_INSTALLATION",
272
+ mayInstall: true,
273
+ installer: "uv add",
274
+ tool: extractToolFromArgs(rest.slice(1)),
275
+ };
276
+ }
277
+ return { resolutionMode: "LOCAL_EXECUTABLE", mayInstall: false, installer: null, tool: null };
278
+ }
279
+
280
+ // Check pipx
281
+ if (binary === "pipx") {
282
+ if (rest[0] === "run") {
283
+ return {
284
+ resolutionMode: "INSTALL_CAPABLE_RESOLUTION",
285
+ mayInstall: true,
286
+ installer: "pipx run",
287
+ tool: extractToolFromArgs(rest.slice(1)),
288
+ };
289
+ }
290
+ if (rest[0] === "install") {
291
+ return {
292
+ resolutionMode: "EXPLICIT_INSTALLATION",
293
+ mayInstall: true,
294
+ installer: "pipx install",
295
+ tool: extractToolFromArgs(rest.slice(1)),
296
+ };
297
+ }
298
+ return { resolutionMode: "LOCAL_EXECUTABLE", mayInstall: false, installer: null, tool: null };
299
+ }
300
+
301
+ // Check npm
302
+ if (binary === "npm") {
303
+ if (["install", "i", "add"].includes(rest[0])) {
304
+ return {
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 };
312
+ }
313
+
314
+ // Check python/pip
315
+ if (binary === "pip" || binary === "pip3") {
316
+ if (rest[0] === "install") {
317
+ return {
318
+ resolutionMode: "EXPLICIT_INSTALLATION",
319
+ mayInstall: true,
320
+ installer: binary,
321
+ tool: extractToolFromArgs(rest.slice(1)),
322
+ };
323
+ }
324
+ }
325
+ if (binary === "python" || binary === "python3") {
326
+ if (rest[0] === "-m" && rest[1] === "pip" && rest[2] === "install") {
327
+ return {
328
+ resolutionMode: "EXPLICIT_INSTALLATION",
329
+ mayInstall: true,
330
+ installer: `${binary} -m pip install`,
331
+ tool: extractToolFromArgs(rest.slice(3)),
332
+ };
333
+ }
334
+ }
335
+
336
+ // Check cargo
337
+ if (binary === "cargo") {
338
+ if (rest[0] === "install" || rest[0] === "binstall") {
339
+ return {
340
+ resolutionMode: "EXPLICIT_INSTALLATION",
341
+ mayInstall: true,
342
+ installer: binary,
343
+ tool: extractToolFromArgs(rest.slice(1)),
344
+ };
345
+ }
346
+ }
347
+
348
+ // System package managers
349
+ if (["brew", "apt", "apt-get", "apk", "dnf", "pacman"].includes(binary)) {
350
+ if (["install", "add", "-S"].includes(rest[0])) {
351
+ return {
352
+ resolutionMode: "EXPLICIT_INSTALLATION",
353
+ mayInstall: true,
354
+ installer: binary,
355
+ tool: extractToolFromArgs(rest.slice(1)),
356
+ };
357
+ }
358
+ }
359
+
360
+ return { resolutionMode: "LOCAL_EXECUTABLE", mayInstall: false, installer: null, tool: null };
361
+ }
362
+
363
+ export function classifyCommandResolution(commandString) {
364
+ if (typeof commandString !== "string" || commandString.trim() === "") {
365
+ return { resolutionMode: "UNKNOWN", mayInstall: false, installer: null, tool: null };
366
+ }
367
+
368
+ const subcommands = splitCommandPipeline(commandString);
369
+ for (const subcommand of subcommands) {
370
+ const res = classifySingleCommand(subcommand);
371
+ if (res.mayInstall) {
372
+ return res;
373
+ }
374
+ }
375
+
376
+ return classifySingleCommand(subcommands[0] || commandString);
377
+ }
378
+
379
+ export function getInstallationAuthorityRef(check) {
380
+ return (
381
+ check?.details?.installationAuthorityRef
382
+ ?? check?.details?.authorityRef
383
+ ?? check?.installationAuthorityRef
384
+ ?? check?.authorityRef
385
+ ?? null
386
+ );
387
+ }
388
+
389
+ function normalizeToolName(toolName) {
390
+ if (typeof toolName !== "string") return "";
391
+ if (toolName.startsWith("@")) {
392
+ const parts = toolName.slice(1).split("@");
393
+ return `@${parts[0]}`;
394
+ }
395
+ return toolName.split("@")[0];
396
+ }
397
+
398
+ export function validateAuthorityGrant({ authority, taskId, type = "SOFTWARE_INSTALLATION", tool } = {}) {
399
+ if (!authority || typeof authority !== "object") {
400
+ return {
401
+ valid: false,
402
+ error: {
403
+ code: E_AUTHORITY_INVALID,
404
+ message: "Authority grant artifact is missing or invalid",
405
+ },
406
+ };
407
+ }
408
+
409
+ if (authority.schemaVersion !== 1 || authority.protocolVersion !== 1) {
410
+ return {
411
+ valid: false,
412
+ error: {
413
+ code: E_AUTHORITY_INVALID,
414
+ message: `Authority grant schema version (${authority.schemaVersion}) or protocol version (${authority.protocolVersion}) is invalid`,
415
+ },
416
+ };
417
+ }
418
+
419
+ if (authority.type !== type) {
420
+ return {
421
+ valid: false,
422
+ error: {
423
+ code: E_AUTHORITY_INVALID,
424
+ message: `Authority grant type '${authority.type}' does not match expected '${type}'`,
425
+ },
426
+ };
427
+ }
428
+
429
+ if (authority.status !== "AUTHORIZED") {
430
+ return {
431
+ valid: false,
432
+ error: {
433
+ code: E_AUTHORITY_INVALID,
434
+ message: `Authority grant is not active (status: '${authority.status}')`,
435
+ },
436
+ };
437
+ }
438
+
439
+ if (authority.source === "agent-self") {
440
+ return {
441
+ valid: false,
442
+ error: {
443
+ code: E_AUTHORITY_INVALID,
444
+ message: "Self-asserted authority grants with source 'agent-self' are not permitted",
445
+ },
446
+ };
447
+ }
448
+
449
+ if (!["operator", "host", "project-policy"].includes(authority.source)) {
450
+ return {
451
+ valid: false,
452
+ error: {
453
+ code: E_AUTHORITY_INVALID,
454
+ message: `Authority grant source '${authority.source}' is not recognized`,
455
+ },
456
+ };
457
+ }
458
+
459
+ if (taskId && authority.taskId && authority.taskId !== taskId) {
460
+ return {
461
+ valid: false,
462
+ error: {
463
+ code: E_AUTHORITY_INVALID,
464
+ message: `Authority grant taskId '${authority.taskId}' does not match current task '${taskId}'`,
465
+ },
466
+ };
467
+ }
468
+
469
+ if (tool && authority.scope?.tool && authority.scope.tool !== "*") {
470
+ const requestedNorm = normalizeToolName(tool);
471
+ const scopeNorm = normalizeToolName(authority.scope.tool);
472
+ const exactMatch = authority.scope.tool === tool
473
+ || tool.startsWith(`${authority.scope.tool}@`)
474
+ || authority.scope.tool.startsWith(`${tool}@`)
475
+ || requestedNorm === scopeNorm;
476
+
477
+ if (!exactMatch) {
478
+ return {
479
+ valid: false,
480
+ error: {
481
+ code: E_AUTHORITY_SCOPE_MISMATCH,
482
+ message: `Authority grant scope tool '${authority.scope.tool}' does not match requested verification tool '${tool}'`,
483
+ },
484
+ };
485
+ }
486
+ }
487
+
488
+ return { valid: true, error: null };
489
+ }
490
+
491
+ export function validateVerificationAuthority(check, options = {}) {
492
+ const command = check?.details?.command
493
+ ?? (check?.kind === "command" && typeof check?.source === "string" && !check.source.startsWith("check:")
494
+ ? check.source
495
+ : null);
496
+
497
+ if (!command || typeof command !== "string") {
498
+ return { valid: true, error: null };
499
+ }
500
+
501
+ const classification = classifyCommandResolution(command);
502
+ if (!classification.mayInstall) {
503
+ return { valid: true, error: null };
504
+ }
505
+
506
+ const authorityRef = getInstallationAuthorityRef(check);
507
+ if (!authorityRef) {
508
+ return {
509
+ valid: false,
510
+ error: {
511
+ code: E_INSTALLATION_AUTHORITY_REQUIRED,
512
+ message: `Verification command '${command}' uses installation-capable resolution (${classification.resolutionMode}) without recorded installation authority reference`,
513
+ },
514
+ };
515
+ }
516
+
517
+ const resolved = resolveTrustedAuthority({
518
+ authorityRef,
519
+ target: options.target,
520
+ trustedAuthorityFile: options.trustedAuthorityFile,
521
+ trustedAuthorityDir: options.trustedAuthorityDir,
522
+ authorities: options.authorities,
523
+ authority: options.authority,
524
+ authorityContext: options.authorityContext,
525
+ runtimeContext: options.runtimeContext,
526
+ });
527
+ if (!resolved.trusted) {
528
+ if (resolved.error?.code === E_AUTHORITY_INVALID && resolved.sourceConfigured === false) {
529
+ return {
530
+ valid: false,
531
+ error: {
532
+ code: E_INSTALLATION_AUTHORITY_REQUIRED,
533
+ message: "Installation-capable verification requires a host-attested authority context",
534
+ },
535
+ };
536
+ }
537
+ return { valid: false, error: resolved.error };
538
+ }
539
+
540
+ return validateAuthorityGrant({
541
+ authority: resolved.authority,
542
+ taskId: options.taskId,
543
+ type: "SOFTWARE_INSTALLATION",
544
+ tool: classification.tool,
545
+ });
546
+ }