@bryan-thompson/inspector-assessment-cli 1.20.2 → 1.20.4
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/build/assess-full.js +41 -5
- package/build/lib/jsonl-events.js +157 -0
- package/package.json +1 -1
package/build/assess-full.js
CHANGED
|
@@ -25,6 +25,7 @@ import { generatePolicyComplianceReport } from "../../client/lib/services/assess
|
|
|
25
25
|
import { compareAssessments } from "../../client/lib/lib/assessmentDiffer.js";
|
|
26
26
|
import { formatDiffAsMarkdown } from "../../client/lib/lib/reportFormatters/DiffReportFormatter.js";
|
|
27
27
|
import { AssessmentStateManager } from "./assessmentState.js";
|
|
28
|
+
import { emitServerConnected, emitToolDiscovered, emitToolsDiscoveryComplete, emitAssessmentComplete, emitTestBatch, emitVulnerabilityFound, emitAnnotationMissing, emitAnnotationMisaligned, emitAnnotationReviewRecommended, } from "./lib/jsonl-events.js";
|
|
28
29
|
/**
|
|
29
30
|
* Load server configuration from Claude Code's MCP settings
|
|
30
31
|
*/
|
|
@@ -40,6 +41,19 @@ function loadServerConfig(serverName, configPath) {
|
|
|
40
41
|
const config = JSON.parse(fs.readFileSync(tryPath, "utf-8"));
|
|
41
42
|
if (config.mcpServers && config.mcpServers[serverName]) {
|
|
42
43
|
const serverConfig = config.mcpServers[serverName];
|
|
44
|
+
// Check if serverConfig specifies http/sse transport
|
|
45
|
+
if (serverConfig.url ||
|
|
46
|
+
serverConfig.transport === "http" ||
|
|
47
|
+
serverConfig.transport === "sse") {
|
|
48
|
+
if (!serverConfig.url) {
|
|
49
|
+
throw new Error(`Invalid server config: transport is '${serverConfig.transport}' but 'url' is missing`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
transport: serverConfig.transport || "http",
|
|
53
|
+
url: serverConfig.url,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Default to stdio transport
|
|
43
57
|
return {
|
|
44
58
|
transport: "stdio",
|
|
45
59
|
command: serverConfig.command,
|
|
@@ -293,18 +307,17 @@ async function runFullAssessment(options) {
|
|
|
293
307
|
console.log("✅ Server config loaded");
|
|
294
308
|
}
|
|
295
309
|
const client = await connectToServer(serverConfig);
|
|
310
|
+
emitServerConnected(options.serverName, serverConfig.transport || "stdio");
|
|
296
311
|
if (!options.jsonOnly) {
|
|
297
312
|
console.log("✅ Connected to MCP server");
|
|
298
313
|
}
|
|
299
314
|
const response = await client.listTools();
|
|
300
315
|
const tools = response.tools || [];
|
|
301
|
-
//
|
|
302
|
-
// Format: TOOL_DISCOVERED:name|description|param1,param2,... (works even with --json flag)
|
|
316
|
+
// Emit JSONL tool discovery events for audit-worker parsing
|
|
303
317
|
for (const tool of tools) {
|
|
304
|
-
|
|
305
|
-
const params = Object.keys(tool.inputSchema?.properties || {}).join(",");
|
|
306
|
-
console.error(`TOOL_DISCOVERED:${tool.name}|${description}|${params}`);
|
|
318
|
+
emitToolDiscovered(tool);
|
|
307
319
|
}
|
|
320
|
+
emitToolsDiscoveryComplete(tools.length);
|
|
308
321
|
if (!options.jsonOnly) {
|
|
309
322
|
console.log(`🔧 Found ${tools.length} tool${tools.length !== 1 ? "s" : ""}`);
|
|
310
323
|
}
|
|
@@ -483,12 +496,32 @@ async function runFullAssessment(options) {
|
|
|
483
496
|
})),
|
|
484
497
|
};
|
|
485
498
|
};
|
|
499
|
+
// Progress callback to emit JSONL events for real-time monitoring
|
|
500
|
+
const onProgress = (event) => {
|
|
501
|
+
if (event.type === "test_batch") {
|
|
502
|
+
emitTestBatch(event.module, event.completed, event.total, event.batchSize, event.elapsed);
|
|
503
|
+
}
|
|
504
|
+
else if (event.type === "vulnerability_found") {
|
|
505
|
+
emitVulnerabilityFound(event.tool, event.pattern, event.confidence, event.evidence, event.riskLevel, event.requiresReview, event.payload);
|
|
506
|
+
}
|
|
507
|
+
else if (event.type === "annotation_missing") {
|
|
508
|
+
emitAnnotationMissing(event.tool, event.title, event.description, event.parameters, event.inferredBehavior);
|
|
509
|
+
}
|
|
510
|
+
else if (event.type === "annotation_misaligned") {
|
|
511
|
+
emitAnnotationMisaligned(event.tool, event.title, event.description, event.parameters, event.field, event.actual, event.expected, event.confidence, event.reason);
|
|
512
|
+
}
|
|
513
|
+
else if (event.type === "annotation_review_recommended") {
|
|
514
|
+
emitAnnotationReviewRecommended(event.tool, event.title, event.description, event.parameters, event.field, event.actual, event.inferred, event.confidence, event.isAmbiguous, event.reason);
|
|
515
|
+
}
|
|
516
|
+
// module_started and module_complete are handled by orchestrator directly
|
|
517
|
+
};
|
|
486
518
|
const context = {
|
|
487
519
|
serverName: options.serverName,
|
|
488
520
|
tools,
|
|
489
521
|
callTool: createCallToolWrapper(client),
|
|
490
522
|
config,
|
|
491
523
|
sourceCodePath: options.sourceCodePath,
|
|
524
|
+
onProgress,
|
|
492
525
|
...sourceFiles,
|
|
493
526
|
// New capability assessment data
|
|
494
527
|
resources,
|
|
@@ -502,6 +535,9 @@ async function runFullAssessment(options) {
|
|
|
502
535
|
console.log("");
|
|
503
536
|
}
|
|
504
537
|
const results = await orchestrator.runFullAssessment(context);
|
|
538
|
+
// Emit assessment complete event
|
|
539
|
+
const defaultOutputPath = `/tmp/inspector-full-assessment-${options.serverName}.json`;
|
|
540
|
+
emitAssessmentComplete(results.overallStatus, results.totalTestsRun, results.executionTime, options.outputPath || defaultOutputPath);
|
|
505
541
|
await client.close();
|
|
506
542
|
return results;
|
|
507
543
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL Event Emission Helpers for CLI
|
|
3
|
+
*
|
|
4
|
+
* These functions emit structured JSONL events to stderr for real-time
|
|
5
|
+
* machine parsing during CLI assessment runs.
|
|
6
|
+
*
|
|
7
|
+
* This is a CLI-local version that imports from the built client lib
|
|
8
|
+
* to avoid rootDir conflicts in TypeScript compilation.
|
|
9
|
+
*/
|
|
10
|
+
import { INSPECTOR_VERSION } from "../../../client/lib/lib/moduleScoring.js";
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Core Functions
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Emit a JSONL event to stderr for real-time machine parsing.
|
|
16
|
+
* Automatically includes version field for compatibility checking.
|
|
17
|
+
*/
|
|
18
|
+
export function emitJSONL(event) {
|
|
19
|
+
console.error(JSON.stringify({ ...event, version: INSPECTOR_VERSION }));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Emit server_connected event after successful connection.
|
|
23
|
+
*/
|
|
24
|
+
export function emitServerConnected(serverName, transport) {
|
|
25
|
+
emitJSONL({ event: "server_connected", serverName, transport });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract parameter metadata from tool input schema.
|
|
29
|
+
*/
|
|
30
|
+
export function extractToolParams(schema) {
|
|
31
|
+
if (!schema || typeof schema !== "object")
|
|
32
|
+
return [];
|
|
33
|
+
const s = schema;
|
|
34
|
+
if (!s.properties || typeof s.properties !== "object")
|
|
35
|
+
return [];
|
|
36
|
+
const required = new Set(Array.isArray(s.required) ? s.required : []);
|
|
37
|
+
const properties = s.properties;
|
|
38
|
+
return Object.entries(properties).map(([name, prop]) => {
|
|
39
|
+
const param = {
|
|
40
|
+
name,
|
|
41
|
+
type: prop.type || "any",
|
|
42
|
+
required: required.has(name),
|
|
43
|
+
};
|
|
44
|
+
if (prop.description) {
|
|
45
|
+
param.description = prop.description;
|
|
46
|
+
}
|
|
47
|
+
return param;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Emit tool_discovered event for each tool found.
|
|
52
|
+
*/
|
|
53
|
+
export function emitToolDiscovered(tool) {
|
|
54
|
+
const params = extractToolParams(tool.inputSchema);
|
|
55
|
+
emitJSONL({
|
|
56
|
+
event: "tool_discovered",
|
|
57
|
+
name: tool.name,
|
|
58
|
+
description: tool.description || null,
|
|
59
|
+
params,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Emit tools_discovery_complete event after all tools discovered.
|
|
64
|
+
*/
|
|
65
|
+
export function emitToolsDiscoveryComplete(count) {
|
|
66
|
+
emitJSONL({ event: "tools_discovery_complete", count });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Emit assessment_complete event at the end of assessment.
|
|
70
|
+
*/
|
|
71
|
+
export function emitAssessmentComplete(overallStatus, totalTests, executionTime, outputPath) {
|
|
72
|
+
emitJSONL({
|
|
73
|
+
event: "assessment_complete",
|
|
74
|
+
overallStatus,
|
|
75
|
+
totalTests,
|
|
76
|
+
executionTime,
|
|
77
|
+
outputPath,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Emit test_batch event during assessment for real-time progress.
|
|
82
|
+
*/
|
|
83
|
+
export function emitTestBatch(module, completed, total, batchSize, elapsed) {
|
|
84
|
+
emitJSONL({
|
|
85
|
+
event: "test_batch",
|
|
86
|
+
module,
|
|
87
|
+
completed,
|
|
88
|
+
total,
|
|
89
|
+
batchSize,
|
|
90
|
+
elapsed,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Emit vulnerability_found event when security testing detects issues.
|
|
95
|
+
*/
|
|
96
|
+
export function emitVulnerabilityFound(tool, pattern, confidence, evidence, riskLevel, requiresReview, payload) {
|
|
97
|
+
emitJSONL({
|
|
98
|
+
event: "vulnerability_found",
|
|
99
|
+
tool,
|
|
100
|
+
pattern,
|
|
101
|
+
confidence,
|
|
102
|
+
evidence,
|
|
103
|
+
riskLevel,
|
|
104
|
+
requiresReview,
|
|
105
|
+
...(payload && { payload }),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Emit annotation_missing event when a tool lacks required annotations.
|
|
110
|
+
*/
|
|
111
|
+
export function emitAnnotationMissing(tool, title, description, parameters, inferredBehavior) {
|
|
112
|
+
emitJSONL({
|
|
113
|
+
event: "annotation_missing",
|
|
114
|
+
tool,
|
|
115
|
+
...(title && { title }),
|
|
116
|
+
...(description && { description }),
|
|
117
|
+
parameters,
|
|
118
|
+
inferredBehavior,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Emit annotation_misaligned event when actual annotations contradict inferred behavior.
|
|
123
|
+
*/
|
|
124
|
+
export function emitAnnotationMisaligned(tool, title, description, parameters, field, actual, expected, confidence, reason) {
|
|
125
|
+
emitJSONL({
|
|
126
|
+
event: "annotation_misaligned",
|
|
127
|
+
tool,
|
|
128
|
+
...(title && { title }),
|
|
129
|
+
...(description && { description }),
|
|
130
|
+
parameters,
|
|
131
|
+
field,
|
|
132
|
+
actual,
|
|
133
|
+
expected,
|
|
134
|
+
confidence,
|
|
135
|
+
reason,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Emit annotation_review_recommended event for ambiguous patterns.
|
|
140
|
+
* This indicates human review is suggested but no automated penalty applied.
|
|
141
|
+
* Used for patterns like store_*, queue_*, cache_* where behavior varies.
|
|
142
|
+
*/
|
|
143
|
+
export function emitAnnotationReviewRecommended(tool, title, description, parameters, field, actual, inferred, confidence, isAmbiguous, reason) {
|
|
144
|
+
emitJSONL({
|
|
145
|
+
event: "annotation_review_recommended",
|
|
146
|
+
tool,
|
|
147
|
+
...(title && { title }),
|
|
148
|
+
...(description && { description }),
|
|
149
|
+
parameters,
|
|
150
|
+
field,
|
|
151
|
+
actual,
|
|
152
|
+
inferred,
|
|
153
|
+
confidence,
|
|
154
|
+
isAmbiguous,
|
|
155
|
+
reason,
|
|
156
|
+
});
|
|
157
|
+
}
|
package/package.json
CHANGED