@bryan-thompson/inspector-assessment-cli 1.32.2 → 1.32.3
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/__tests__/assessment-runner/server-config.test.js +8 -5
- package/build/__tests__/jsonl-events.test.js +195 -1
- package/build/__tests__/lib/server-configSchemas.test.js +314 -0
- package/build/__tests__/lib/zodErrorFormatter.test.js +721 -0
- package/build/__tests__/security/security-pattern-count.test.js +245 -0
- package/build/assess-security.js +11 -77
- package/build/lib/assessment-runner/__tests__/server-config.test.js +116 -0
- package/build/lib/assessment-runner/assessment-executor.js +18 -1
- package/build/lib/assessment-runner/config-builder.js +10 -0
- package/build/lib/assessment-runner/server-config.js +43 -35
- package/build/lib/assessment-runner/server-configSchemas.js +4 -1
- package/build/lib/jsonl-events.js +59 -0
- package/build/lib/zodErrorFormatter.js +31 -0
- package/package.json +1 -1
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* Server Configuration Loading
|
|
3
3
|
*
|
|
4
4
|
* Handles loading MCP server configuration from Claude Code settings.
|
|
5
|
+
* Uses Zod schemas for runtime validation (Issue #84).
|
|
5
6
|
*
|
|
6
7
|
* @module cli/lib/assessment-runner/server-config
|
|
7
8
|
*/
|
|
8
9
|
import * as fs from "fs";
|
|
9
10
|
import * as path from "path";
|
|
10
11
|
import * as os from "os";
|
|
12
|
+
import { ServerEntrySchema, isHttpSseConfig, isStdioConfig, } from "./server-configSchemas.js";
|
|
13
|
+
import { formatZodError } from "../zodErrorFormatter.js";
|
|
11
14
|
/**
|
|
12
15
|
* Load server configuration from Claude Code's MCP settings
|
|
13
16
|
*
|
|
@@ -24,55 +27,60 @@ export function loadServerConfig(serverName, configPath) {
|
|
|
24
27
|
for (const tryPath of possiblePaths) {
|
|
25
28
|
if (!fs.existsSync(tryPath))
|
|
26
29
|
continue;
|
|
27
|
-
let
|
|
30
|
+
let rawConfig;
|
|
28
31
|
try {
|
|
29
|
-
|
|
32
|
+
rawConfig = JSON.parse(fs.readFileSync(tryPath, "utf-8"));
|
|
30
33
|
}
|
|
31
34
|
catch (e) {
|
|
32
35
|
throw new Error(`Invalid JSON in config file: ${tryPath}\n${e instanceof Error ? e.message : String(e)}`);
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
37
|
+
// Determine which config entry to validate
|
|
38
|
+
let serverEntry;
|
|
39
|
+
let configSource;
|
|
40
|
+
// Check for Claude Desktop format (mcpServers object)
|
|
41
|
+
if (typeof rawConfig === "object" &&
|
|
42
|
+
rawConfig !== null &&
|
|
43
|
+
"mcpServers" in rawConfig &&
|
|
44
|
+
typeof rawConfig.mcpServers === "object") {
|
|
45
|
+
const mcpServers = rawConfig
|
|
46
|
+
.mcpServers;
|
|
47
|
+
if (mcpServers && serverName in mcpServers) {
|
|
48
|
+
serverEntry = mcpServers[serverName];
|
|
49
|
+
configSource = `${tryPath} (mcpServers.${serverName})`;
|
|
47
50
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
transport: "stdio",
|
|
51
|
-
command: serverConfig.command,
|
|
52
|
-
args: serverConfig.args || [],
|
|
53
|
-
env: serverConfig.env || {},
|
|
54
|
-
cwd: serverConfig.cwd,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
if (config.url ||
|
|
58
|
-
config.transport === "http" ||
|
|
59
|
-
config.transport === "sse") {
|
|
60
|
-
if (!config.url) {
|
|
61
|
-
throw new Error(`Invalid server config: transport is '${config.transport}' but 'url' is missing`);
|
|
51
|
+
else {
|
|
52
|
+
continue; // Server not in this file, try next path
|
|
62
53
|
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Standalone config format
|
|
57
|
+
serverEntry = rawConfig;
|
|
58
|
+
configSource = tryPath;
|
|
59
|
+
}
|
|
60
|
+
// Validate server entry with Zod schema
|
|
61
|
+
const validationResult = ServerEntrySchema.safeParse(serverEntry);
|
|
62
|
+
if (!validationResult.success) {
|
|
63
|
+
throw new Error(`Invalid server config in ${configSource}:\n${formatZodError(validationResult.error)}`);
|
|
64
|
+
}
|
|
65
|
+
const validatedEntry = validationResult.data;
|
|
66
|
+
// Convert to ServerConfig using type guards
|
|
67
|
+
if (isHttpSseConfig(validatedEntry)) {
|
|
63
68
|
return {
|
|
64
|
-
transport:
|
|
65
|
-
url:
|
|
69
|
+
transport: validatedEntry.transport || "http",
|
|
70
|
+
url: validatedEntry.url,
|
|
66
71
|
};
|
|
67
72
|
}
|
|
68
|
-
if (
|
|
73
|
+
if (isStdioConfig(validatedEntry)) {
|
|
69
74
|
return {
|
|
70
75
|
transport: "stdio",
|
|
71
|
-
command:
|
|
72
|
-
args:
|
|
73
|
-
env:
|
|
76
|
+
command: validatedEntry.command,
|
|
77
|
+
args: validatedEntry.args || [],
|
|
78
|
+
env: validatedEntry.env || {},
|
|
79
|
+
cwd: validatedEntry.cwd,
|
|
74
80
|
};
|
|
75
81
|
}
|
|
82
|
+
// This should never happen due to schema validation, but TypeScript needs it
|
|
83
|
+
throw new Error(`Unable to determine transport type for config: ${configSource}`);
|
|
76
84
|
}
|
|
77
85
|
throw new Error(`Server config not found for: ${serverName}\nTried: ${possiblePaths.join(", ")}`);
|
|
78
86
|
}
|
|
@@ -34,7 +34,10 @@ export { TransportTypeSchema };
|
|
|
34
34
|
*/
|
|
35
35
|
export const HttpSseServerConfigSchema = z.object({
|
|
36
36
|
transport: z.enum(["http", "sse"]).optional(),
|
|
37
|
-
url: z
|
|
37
|
+
url: z
|
|
38
|
+
.string()
|
|
39
|
+
.min(1, "'url' is required for HTTP/SSE transport")
|
|
40
|
+
.url("url must be a valid URL"),
|
|
38
41
|
});
|
|
39
42
|
/**
|
|
40
43
|
* Schema for stdio transport server configuration.
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This is a CLI-local version that imports from the built client lib
|
|
8
8
|
* to avoid rootDir conflicts in TypeScript compilation.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: Phase 7 events (tool_test_complete, validation_summary, phase_started,
|
|
11
|
+
* phase_complete) are re-exported from scripts/lib/jsonl-events.ts to maintain
|
|
12
|
+
* a single source of truth. See Issue #88.
|
|
9
13
|
*/
|
|
10
14
|
import { INSPECTOR_VERSION, SCHEMA_VERSION, } from "../../../client/lib/lib/moduleScoring.js";
|
|
11
15
|
// Re-export for consumers of this module
|
|
@@ -196,3 +200,58 @@ export function emitModulesConfigured(enabled, skipped, reason) {
|
|
|
196
200
|
reason,
|
|
197
201
|
});
|
|
198
202
|
}
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Phase 7 Events - Per-Tool Testing & Phase Lifecycle
|
|
205
|
+
// ============================================================================
|
|
206
|
+
/**
|
|
207
|
+
* Emit tool_test_complete event after all tests for a single tool finish.
|
|
208
|
+
* Provides per-tool summary for real-time progress in auditor UI.
|
|
209
|
+
*/
|
|
210
|
+
export function emitToolTestComplete(tool, module, scenariosPassed, scenariosExecuted, confidence, status, executionTime) {
|
|
211
|
+
emitJSONL({
|
|
212
|
+
event: "tool_test_complete",
|
|
213
|
+
tool,
|
|
214
|
+
module,
|
|
215
|
+
scenariosPassed,
|
|
216
|
+
scenariosExecuted,
|
|
217
|
+
confidence,
|
|
218
|
+
status,
|
|
219
|
+
executionTime,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Emit validation_summary event with per-tool input validation metrics.
|
|
224
|
+
* Tracks how tools handle invalid inputs (wrong types, missing required, etc.)
|
|
225
|
+
*/
|
|
226
|
+
export function emitValidationSummary(tool, wrongType, missingRequired, extraParams, nullValues, invalidValues) {
|
|
227
|
+
emitJSONL({
|
|
228
|
+
event: "validation_summary",
|
|
229
|
+
tool,
|
|
230
|
+
wrongType,
|
|
231
|
+
missingRequired,
|
|
232
|
+
extraParams,
|
|
233
|
+
nullValues,
|
|
234
|
+
invalidValues,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Emit phase_started event when an assessment phase begins.
|
|
239
|
+
* Used for high-level progress tracking (discovery, assessment, analysis).
|
|
240
|
+
*/
|
|
241
|
+
export function emitPhaseStarted(phase) {
|
|
242
|
+
emitJSONL({
|
|
243
|
+
event: "phase_started",
|
|
244
|
+
phase,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Emit phase_complete event when an assessment phase finishes.
|
|
249
|
+
* Includes duration for performance tracking.
|
|
250
|
+
*/
|
|
251
|
+
export function emitPhaseComplete(phase, duration) {
|
|
252
|
+
emitJSONL({
|
|
253
|
+
event: "phase_complete",
|
|
254
|
+
phase,
|
|
255
|
+
duration,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
@@ -17,11 +17,42 @@ export function formatZodIssue(issue) {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Format a ZodError into a readable string.
|
|
20
|
+
* Detects union validation failures and extracts the most specific error messages.
|
|
20
21
|
*
|
|
21
22
|
* @param error - Zod validation error
|
|
22
23
|
* @returns Formatted error messages joined by newlines
|
|
23
24
|
*/
|
|
24
25
|
export function formatZodError(error) {
|
|
26
|
+
// Check if this is a union validation error (code: invalid_union)
|
|
27
|
+
const unionErrors = error.errors.filter((e) => e.code === "invalid_union");
|
|
28
|
+
if (unionErrors.length > 0) {
|
|
29
|
+
// Extract detailed errors from union attempts
|
|
30
|
+
const detailedErrors = [];
|
|
31
|
+
for (const unionError of unionErrors) {
|
|
32
|
+
// Union errors have unionErrors property with detailed failures
|
|
33
|
+
if ("unionErrors" in unionError &&
|
|
34
|
+
Array.isArray(unionError.unionErrors)) {
|
|
35
|
+
for (const subError of unionError.unionErrors) {
|
|
36
|
+
// Find the most specific error message (not "Required")
|
|
37
|
+
const specificErrors = subError.errors.filter((e) => e.message !== "Required" && e.code !== "invalid_type");
|
|
38
|
+
if (specificErrors.length > 0) {
|
|
39
|
+
detailedErrors.push(...specificErrors.map(formatZodIssue));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Fallback to any error from this branch
|
|
43
|
+
detailedErrors.push(...subError.errors.map(formatZodIssue));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// If we found detailed errors, use them; otherwise fall back to generic message
|
|
49
|
+
if (detailedErrors.length > 0) {
|
|
50
|
+
// Deduplicate errors and return all unique errors
|
|
51
|
+
const uniqueErrors = Array.from(new Set(detailedErrors));
|
|
52
|
+
return uniqueErrors.join("\n");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Standard formatting for non-union errors
|
|
25
56
|
return error.errors.map(formatZodIssue).join("\n");
|
|
26
57
|
}
|
|
27
58
|
/**
|
package/package.json
CHANGED