@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.
- package/LOOP_ENGINEERING.md +33 -0
- package/PROTOCOL_INTEGRATION.md +71 -0
- package/README.md +16 -9
- package/THREAT_MODEL.md +4 -0
- package/package.json +1 -1
- package/schemas/authority.schema.json +34 -0
- package/src/commands/inspect.js +2 -0
- package/src/commands/prepare-completion.js +2 -2
- package/src/core/audit.js +2 -2
- package/src/core/checks.js +12 -5
- package/src/core/completion-artifacts.js +122 -23
- package/src/core/completion-recovery.js +4 -0
- package/src/core/completion-relationships.js +39 -4
- package/src/core/completion.js +29 -7
- package/src/core/coverage.js +15 -2
- package/src/core/evidence-readiness.js +32 -6
- package/src/core/inspect.js +3 -1
- package/src/core/next-action.js +87 -9
- package/src/core/phase.js +29 -6
- package/src/core/protocol.js +5 -0
- package/src/core/receipt.js +6 -6
- package/src/core/runtime-context.js +80 -0
- package/src/core/trusted-authority.js +296 -0
- package/src/core/verification-capability.js +499 -0
|
@@ -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
|
+
}
|