@bryan-thompson/inspector-assessment 1.35.1 → 1.35.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/cli/build/__tests__/assess-full-e2e.test.js +23 -7
- package/cli/build/__tests__/assess-full.test.js +3 -0
- package/cli/build/__tests__/assessment-runner/assessment-executor.test.js +14 -1
- package/cli/build/__tests__/assessment-runner/config-builder.test.js +6 -0
- package/cli/build/__tests__/assessment-runner/index.test.js +3 -0
- package/cli/build/__tests__/assessment-runner/server-config.test.js +3 -0
- package/cli/build/__tests__/assessment-runner/server-connection.test.js +9 -1
- package/cli/build/__tests__/assessment-runner/source-loader.test.js +29 -16
- package/cli/build/__tests__/assessment-runner/tool-wrapper.test.js +3 -0
- package/cli/build/__tests__/assessment-runner-facade.test.js +3 -0
- package/cli/build/__tests__/cli-build-fixes.test.js +3 -0
- package/cli/build/__tests__/flag-parsing.test.js +3 -2
- package/cli/build/__tests__/http-transport-integration.test.js +10 -3
- package/cli/build/__tests__/lib/server-configSchemas.test.js +4 -1
- package/cli/build/__tests__/lib/zodErrorFormatter.test.js +4 -1
- package/cli/build/__tests__/profiles.test.js +19 -8
- package/cli/build/__tests__/security/security-pattern-count.test.js +3 -0
- package/cli/build/__tests__/stage3-fix-validation.test.js +3 -0
- package/cli/build/__tests__/testbed-integration.test.js +10 -3
- package/cli/build/__tests__/transport.test.js +3 -0
- package/cli/build/lib/__tests__/cli-parserSchemas.test.js +3 -0
- package/cli/build/lib/assessment-runner/__tests__/server-configSchemas.test.js +3 -0
- package/cli/package.json +1 -1
- package/client/dist/assets/{OAuthCallback-CUf6mvrP.js → OAuthCallback-BglPlsRX.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-DkfPOggR.js → OAuthDebugCallback-CWrUH2KB.js} +1 -1
- package/client/dist/assets/{index-uinVACY4.js → index-iCeeS8vg.js} +4 -4
- package/client/dist/index.html +1 -1
- package/client/lib/lib/assessment/coreTypes.d.ts +23 -0
- package/client/lib/lib/assessment/coreTypes.d.ts.map +1 -1
- package/client/lib/lib/assessment/extendedTypes.d.ts +64 -7
- package/client/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
- package/client/lib/lib/assessment/jsonlEventSchemas.d.ts +4 -4
- package/client/lib/lib/assessment/resultTypes.d.ts +12 -1
- package/client/lib/lib/assessment/resultTypes.d.ts.map +1 -1
- package/client/lib/lib/aupPatterns.d.ts +50 -0
- package/client/lib/lib/aupPatterns.d.ts.map +1 -1
- package/client/lib/lib/aupPatterns.js +140 -0
- package/client/lib/lib/securityPatterns.d.ts.map +1 -1
- package/client/lib/lib/securityPatterns.js +92 -0
- package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts +26 -1
- package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.js +160 -1
- package/client/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +40 -0
- package/client/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/ManifestValidationAssessor.js +269 -28
- package/client/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/ConfidenceScorer.js +28 -0
- package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +95 -0
- package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +174 -0
- package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +15 -0
- package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +40 -0
- package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +143 -131
- package/client/package.json +1 -1
- package/package.json +1 -1
- package/server/build/__tests__/helpers.test.js +3 -0
- package/server/build/__tests__/security.test.js +3 -0
- package/server/package.json +1 -1
|
@@ -76,6 +76,10 @@ async function spawnCLI(args, timeout = 60000) {
|
|
|
76
76
|
// Set timeout
|
|
77
77
|
const timer = setTimeout(() => {
|
|
78
78
|
if (proc && !proc.killed) {
|
|
79
|
+
// Destroy streams before killing to prevent memory leaks
|
|
80
|
+
proc.stdout?.destroy();
|
|
81
|
+
proc.stderr?.destroy();
|
|
82
|
+
proc.stdin?.destroy();
|
|
79
83
|
proc.kill("SIGTERM");
|
|
80
84
|
exitCode = -1; // Indicate timeout
|
|
81
85
|
}
|
|
@@ -83,6 +87,10 @@ async function spawnCLI(args, timeout = 60000) {
|
|
|
83
87
|
// Handle process exit
|
|
84
88
|
proc.on("close", (code) => {
|
|
85
89
|
clearTimeout(timer);
|
|
90
|
+
// Destroy streams to prevent memory leaks
|
|
91
|
+
proc?.stdout?.destroy();
|
|
92
|
+
proc?.stderr?.destroy();
|
|
93
|
+
proc?.stdin?.destroy();
|
|
86
94
|
// Don't overwrite timeout exit code (-1)
|
|
87
95
|
if (exitCode !== -1) {
|
|
88
96
|
exitCode = code;
|
|
@@ -100,6 +108,10 @@ async function spawnCLI(args, timeout = 60000) {
|
|
|
100
108
|
// Handle errors
|
|
101
109
|
proc.on("error", (err) => {
|
|
102
110
|
clearTimeout(timer);
|
|
111
|
+
// Destroy streams to prevent memory leaks
|
|
112
|
+
proc?.stdout?.destroy();
|
|
113
|
+
proc?.stderr?.destroy();
|
|
114
|
+
proc?.stdin?.destroy();
|
|
103
115
|
stderr += `\nProcess error: ${err.message}`;
|
|
104
116
|
resolve({
|
|
105
117
|
stdout,
|
|
@@ -151,10 +163,10 @@ function parseJSONLEvents(stderr) {
|
|
|
151
163
|
* @returns True if server responds, false otherwise
|
|
152
164
|
*/
|
|
153
165
|
async function checkServerAvailable(url) {
|
|
166
|
+
const controller = new AbortController();
|
|
167
|
+
// Give enough time to receive initial response but not wait forever
|
|
168
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
154
169
|
try {
|
|
155
|
-
const controller = new AbortController();
|
|
156
|
-
// Give enough time to receive initial response but not wait forever
|
|
157
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
158
170
|
const response = await fetch(url, {
|
|
159
171
|
method: "POST",
|
|
160
172
|
headers: DEFAULT_HEADERS,
|
|
@@ -172,26 +184,22 @@ async function checkServerAvailable(url) {
|
|
|
172
184
|
});
|
|
173
185
|
// Server responded with a status code - check if it's OK
|
|
174
186
|
if (response.status >= 500) {
|
|
175
|
-
clearTimeout(timeoutId);
|
|
176
187
|
return false;
|
|
177
188
|
}
|
|
178
189
|
// For SSE responses, check if we can read any data
|
|
179
190
|
// This confirms the server is actually responding
|
|
180
191
|
const reader = response.body?.getReader();
|
|
181
192
|
if (!reader) {
|
|
182
|
-
clearTimeout(timeoutId);
|
|
183
193
|
return response.status < 500;
|
|
184
194
|
}
|
|
185
195
|
try {
|
|
186
196
|
// Try to read the first chunk
|
|
187
197
|
const { done, value } = await reader.read();
|
|
188
|
-
clearTimeout(timeoutId);
|
|
189
198
|
reader.cancel(); // Cancel the stream - we don't need more data
|
|
190
199
|
// If we got any data, the server is available
|
|
191
200
|
return !done && value && value.length > 0;
|
|
192
201
|
}
|
|
193
202
|
catch {
|
|
194
|
-
clearTimeout(timeoutId);
|
|
195
203
|
// If read fails after successful fetch, server still responded
|
|
196
204
|
return true;
|
|
197
205
|
}
|
|
@@ -199,6 +207,11 @@ async function checkServerAvailable(url) {
|
|
|
199
207
|
catch {
|
|
200
208
|
return false;
|
|
201
209
|
}
|
|
210
|
+
finally {
|
|
211
|
+
// Always clean up timeout and abort controller
|
|
212
|
+
clearTimeout(timeoutId);
|
|
213
|
+
controller.abort();
|
|
214
|
+
}
|
|
202
215
|
}
|
|
203
216
|
/**
|
|
204
217
|
* Create a temporary config file for testing
|
|
@@ -238,6 +251,9 @@ function createInvalidConfig(content, filename) {
|
|
|
238
251
|
// Test Setup
|
|
239
252
|
// ============================================================================
|
|
240
253
|
describe("CLI E2E Integration Tests", () => {
|
|
254
|
+
afterEach(() => {
|
|
255
|
+
jest.clearAllMocks();
|
|
256
|
+
});
|
|
241
257
|
let vulnerableAvailable = false;
|
|
242
258
|
let hardenedAvailable = false;
|
|
243
259
|
beforeAll(async () => {
|
|
@@ -13,6 +13,9 @@ import * as path from "path";
|
|
|
13
13
|
* without needing to import the actual module (which has side effects)
|
|
14
14
|
*/
|
|
15
15
|
describe("CLI Argument Parsing Concepts", () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
16
19
|
describe("Profile Flag Parsing", () => {
|
|
17
20
|
const VALID_PROFILES = ["quick", "security", "compliance", "full"];
|
|
18
21
|
function parseProfile(args) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tests for runFullAssessment() orchestration logic.
|
|
5
5
|
*/
|
|
6
|
-
import { jest, describe, it, expect, beforeEach, afterEach, } from "@jest/globals";
|
|
6
|
+
import { jest, describe, it, expect, beforeEach, afterEach, afterAll, } from "@jest/globals";
|
|
7
7
|
// Mock all dependencies with explicit any types for flexibility
|
|
8
8
|
const mockLoadServerConfig = jest.fn();
|
|
9
9
|
const mockConnectToServer = jest.fn();
|
|
@@ -137,6 +137,19 @@ describe("runFullAssessment", () => {
|
|
|
137
137
|
afterEach(() => {
|
|
138
138
|
jest.restoreAllMocks();
|
|
139
139
|
});
|
|
140
|
+
afterAll(() => {
|
|
141
|
+
// Clean up module mocks to prevent memory leaks
|
|
142
|
+
jest.unmock("../../lib/assessment-runner/server-config.js");
|
|
143
|
+
jest.unmock("../../lib/assessment-runner/server-connection.js");
|
|
144
|
+
jest.unmock("../../lib/assessment-runner/source-loader.js");
|
|
145
|
+
jest.unmock("../../lib/assessment-runner/tool-wrapper.js");
|
|
146
|
+
jest.unmock("../../lib/assessment-runner/config-builder.js");
|
|
147
|
+
jest.unmock("../../../../client/lib/services/assessment/AssessmentOrchestrator.js");
|
|
148
|
+
jest.unmock("../../assessmentState.js");
|
|
149
|
+
jest.unmock("../../lib/jsonl-events.js");
|
|
150
|
+
jest.unmock("fs");
|
|
151
|
+
jest.unmock("../../../../client/lib/lib/assessmentTypes.js");
|
|
152
|
+
});
|
|
140
153
|
describe("orchestration flow", () => {
|
|
141
154
|
it("should load server config", async () => {
|
|
142
155
|
await runFullAssessment(defaultOptions);
|
|
@@ -62,6 +62,12 @@ describe("buildConfig", () => {
|
|
|
62
62
|
afterEach(() => {
|
|
63
63
|
process.env = originalEnv;
|
|
64
64
|
});
|
|
65
|
+
afterAll(() => {
|
|
66
|
+
jest.unmock("../../profiles.js");
|
|
67
|
+
jest.unmock("../../../../client/lib/lib/assessmentTypes.js");
|
|
68
|
+
jest.unmock("../../../../client/lib/services/assessment/lib/claudeCodeBridge.js");
|
|
69
|
+
jest.unmock("../../../../client/lib/services/assessment/config/performanceConfig.js");
|
|
70
|
+
});
|
|
65
71
|
describe("default configuration", () => {
|
|
66
72
|
it("should spread DEFAULT_ASSESSMENT_CONFIG", () => {
|
|
67
73
|
const result = buildConfig({ serverName: "test" });
|
|
@@ -7,6 +7,9 @@ import { describe, it, expect } from "@jest/globals";
|
|
|
7
7
|
// Import the barrel/facade module
|
|
8
8
|
import * as assessmentRunner from "../../lib/assessment-runner/index.js";
|
|
9
9
|
describe("assessment-runner index exports", () => {
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
10
13
|
describe("function exports", () => {
|
|
11
14
|
it("should export all 6 public functions", () => {
|
|
12
15
|
expect(typeof assessmentRunner.loadServerConfig).toBe("function");
|
|
@@ -22,6 +22,9 @@ describe("loadServerConfig", () => {
|
|
|
22
22
|
jest.clearAllMocks();
|
|
23
23
|
mockExistsSync.mockReturnValue(false);
|
|
24
24
|
});
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
jest.unmock("fs");
|
|
27
|
+
});
|
|
25
28
|
describe("config path resolution", () => {
|
|
26
29
|
it("should search explicit configPath first when provided", () => {
|
|
27
30
|
const configPath = "/custom/path/config.json";
|
|
@@ -39,10 +39,18 @@ const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sd
|
|
|
39
39
|
const { connectToServer } = await import("../../lib/assessment-runner/server-connection.js");
|
|
40
40
|
describe("connectToServer", () => {
|
|
41
41
|
beforeEach(() => {
|
|
42
|
-
jest.clearAllMocks();
|
|
43
42
|
mockConnect.mockResolvedValue(undefined);
|
|
44
43
|
mockStdioTransport.stderr.on.mockClear();
|
|
45
44
|
});
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
jest.clearAllMocks();
|
|
47
|
+
});
|
|
48
|
+
afterAll(() => {
|
|
49
|
+
jest.unmock("@modelcontextprotocol/sdk/client/index.js");
|
|
50
|
+
jest.unmock("@modelcontextprotocol/sdk/client/stdio.js");
|
|
51
|
+
jest.unmock("@modelcontextprotocol/sdk/client/sse.js");
|
|
52
|
+
jest.unmock("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
53
|
+
});
|
|
46
54
|
describe("HTTP transport", () => {
|
|
47
55
|
it("should create StreamableHTTPClientTransport for transport:http", async () => {
|
|
48
56
|
const config = {
|
|
@@ -34,10 +34,15 @@ describe("loadSourceFiles", () => {
|
|
|
34
34
|
const mockReadFileSync = fs.readFileSync;
|
|
35
35
|
const mockReaddirSync = fs.readdirSync;
|
|
36
36
|
beforeEach(() => {
|
|
37
|
-
jest.clearAllMocks();
|
|
38
37
|
mockExistsSync.mockReturnValue(false);
|
|
39
38
|
mockReaddirSync.mockReturnValue([]);
|
|
40
39
|
});
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
afterAll(() => {
|
|
44
|
+
jest.unmock("fs");
|
|
45
|
+
});
|
|
41
46
|
describe("README discovery", () => {
|
|
42
47
|
it("should find README.md in source directory", () => {
|
|
43
48
|
const sourcePath = "/project";
|
|
@@ -118,13 +123,17 @@ describe("loadSourceFiles", () => {
|
|
|
118
123
|
const consoleSpy = jest
|
|
119
124
|
.spyOn(console, "warn")
|
|
120
125
|
.mockImplementation(() => { });
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
try {
|
|
127
|
+
mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "manifest.json"));
|
|
128
|
+
mockReadFileSync.mockReturnValue("{ invalid json }");
|
|
129
|
+
const result = loadSourceFiles(sourcePath);
|
|
130
|
+
expect(result.manifestRaw).toBe("{ invalid json }");
|
|
131
|
+
expect(result.manifestJson).toBeUndefined();
|
|
132
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse manifest.json"));
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
consoleSpy.mockRestore();
|
|
136
|
+
}
|
|
128
137
|
});
|
|
129
138
|
});
|
|
130
139
|
describe("source file collection", () => {
|
|
@@ -276,14 +285,18 @@ describe("loadSourceFiles", () => {
|
|
|
276
285
|
const consoleSpy = jest
|
|
277
286
|
.spyOn(console, "warn")
|
|
278
287
|
.mockImplementation(() => { });
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
try {
|
|
289
|
+
mockExistsSync.mockReturnValue(false);
|
|
290
|
+
mockReaddirSync.mockImplementation(() => {
|
|
291
|
+
throw new Error("Permission denied");
|
|
292
|
+
});
|
|
293
|
+
const result = loadSourceFiles(sourcePath);
|
|
294
|
+
expect(result.sourceCodeFiles?.size).toBe(0);
|
|
295
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Could not load source files"), expect.any(Error));
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
consoleSpy.mockRestore();
|
|
299
|
+
}
|
|
287
300
|
});
|
|
288
301
|
it("should skip unreadable files silently", () => {
|
|
289
302
|
const sourcePath = "/project";
|
|
@@ -15,6 +15,9 @@ describe("createCallToolWrapper", () => {
|
|
|
15
15
|
callTool: mockCallTool,
|
|
16
16
|
};
|
|
17
17
|
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
18
21
|
describe("successful tool calls", () => {
|
|
19
22
|
it("should wrap successful tool response with content array", async () => {
|
|
20
23
|
mockCallTool.mockResolvedValue({
|
|
@@ -12,6 +12,9 @@ import { loadServerConfig, loadSourceFiles, connectToServer, createCallToolWrapp
|
|
|
12
12
|
// Test namespace import
|
|
13
13
|
import * as AssessmentRunner from "../lib/assessment-runner.js";
|
|
14
14
|
describe("Assessment Runner Facade", () => {
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
});
|
|
15
18
|
describe("Function Exports", () => {
|
|
16
19
|
it("should export loadServerConfig function", () => {
|
|
17
20
|
expect(typeof loadServerConfig).toBe("function");
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
import { describe, it, expect } from "@jest/globals";
|
|
12
12
|
import { ScopedListenerConfig } from "../lib/event-config.js";
|
|
13
13
|
describe("CLI Build Fixes Regression Tests", () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
14
17
|
describe("event-config.ts - CLI_DEFAULT_MAX_LISTENERS constant", () => {
|
|
15
18
|
it("should use local constant instead of cross-workspace import", () => {
|
|
16
19
|
// Fix: Replaced DEFAULT_PERFORMANCE_CONFIG.eventEmitterMaxListeners
|
|
@@ -669,8 +669,9 @@ describe("parseArgs Zod Schema Integration", () => {
|
|
|
669
669
|
// Run any pending timers and restore
|
|
670
670
|
jest.runAllTimers();
|
|
671
671
|
jest.useRealTimers();
|
|
672
|
-
|
|
673
|
-
|
|
672
|
+
// Use optional chaining in case spies weren't created (prevents memory leaks)
|
|
673
|
+
processExitSpy?.mockRestore();
|
|
674
|
+
consoleErrorSpy?.mockRestore();
|
|
674
675
|
});
|
|
675
676
|
describe("LogLevelSchema integration", () => {
|
|
676
677
|
it("parseArgs validates log level with LogLevelSchema", () => {
|
|
@@ -24,9 +24,9 @@ const DEFAULT_HEADERS = {
|
|
|
24
24
|
* Check if a server is available by sending a basic HTTP request
|
|
25
25
|
*/
|
|
26
26
|
async function checkServerAvailable(url) {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
27
29
|
try {
|
|
28
|
-
const controller = new AbortController();
|
|
29
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
30
30
|
const response = await fetch(url, {
|
|
31
31
|
method: "POST",
|
|
32
32
|
headers: DEFAULT_HEADERS,
|
|
@@ -42,13 +42,17 @@ async function checkServerAvailable(url) {
|
|
|
42
42
|
}),
|
|
43
43
|
signal: controller.signal,
|
|
44
44
|
});
|
|
45
|
-
clearTimeout(timeoutId);
|
|
46
45
|
// Accept any response (200 or error) as indication server is up
|
|
47
46
|
return response.status < 500;
|
|
48
47
|
}
|
|
49
48
|
catch {
|
|
50
49
|
return false;
|
|
51
50
|
}
|
|
51
|
+
finally {
|
|
52
|
+
// Always clean up timeout and abort controller to prevent memory leaks
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
controller.abort();
|
|
55
|
+
}
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
54
58
|
* Parse SSE response to extract JSON data
|
|
@@ -101,6 +105,9 @@ async function sendMcpRequest(url, method, params = {}, headers = {}) {
|
|
|
101
105
|
return { response, data };
|
|
102
106
|
}
|
|
103
107
|
describe("HTTP Transport Integration", () => {
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
});
|
|
104
111
|
let vulnerableServerAvailable = false;
|
|
105
112
|
let hardenedServerAvailable = false;
|
|
106
113
|
beforeAll(async () => {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Addresses QA requirement: Test that type guards are mutually exclusive
|
|
8
8
|
* and correctly discriminate between transport types.
|
|
9
9
|
*/
|
|
10
|
-
import { describe, it, expect } from "@jest/globals";
|
|
10
|
+
import { jest, describe, it, expect } from "@jest/globals";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
// Define schemas inline to avoid import issues with client/lib in CLI tests
|
|
13
13
|
const HttpSseServerConfigSchema = z.object({
|
|
@@ -36,6 +36,9 @@ function isStdioConfig(entry) {
|
|
|
36
36
|
return "command" in entry && !("url" in entry);
|
|
37
37
|
}
|
|
38
38
|
describe("server-configSchemas type guards", () => {
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.clearAllMocks();
|
|
41
|
+
});
|
|
39
42
|
describe("isHttpSseConfig", () => {
|
|
40
43
|
it("should return true for HTTP transport config", () => {
|
|
41
44
|
const config = {
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* Tests for formatZodError utility to ensure helpful error messages.
|
|
5
5
|
* Addresses QA requirement: verify Zod error messages are helpful (not just generic "Invalid").
|
|
6
6
|
*/
|
|
7
|
-
import { describe, it, expect } from "@jest/globals";
|
|
7
|
+
import { jest, describe, it, expect } from "@jest/globals";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { formatZodError, formatZodIssue, formatZodErrorIndented, zodErrorToArray, formatUserFriendlyError, formatZodErrorForJson, } from "../../lib/zodErrorFormatter.js";
|
|
10
10
|
describe("zodErrorFormatter", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
11
14
|
describe("formatZodIssue", () => {
|
|
12
15
|
it("should format issue with path", () => {
|
|
13
16
|
const issue = {
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import { jest, describe, it, expect } from "@jest/globals";
|
|
8
8
|
import { ASSESSMENT_PROFILES, PROFILE_METADATA, MODULE_ALIASES, DEPRECATED_MODULES, TIER_1_CORE_SECURITY, TIER_2_COMPLIANCE, TIER_3_CAPABILITY, TIER_4_EXTENDED, ALL_MODULES, resolveModuleNames, getProfileModules, isValidProfileName, getProfileHelpText, mapLegacyConfigToModules, modulesToLegacyConfig, } from "../profiles.js";
|
|
9
9
|
describe("Profile Definitions", () => {
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.restoreAllMocks();
|
|
12
|
+
});
|
|
10
13
|
describe("Profile Constants", () => {
|
|
11
14
|
it("should have four profiles defined", () => {
|
|
12
15
|
const profiles = Object.keys(ASSESSMENT_PROFILES);
|
|
@@ -124,17 +127,25 @@ describe("resolveModuleNames", () => {
|
|
|
124
127
|
});
|
|
125
128
|
it("should emit warnings for deprecated modules when warn=true", () => {
|
|
126
129
|
const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
try {
|
|
131
|
+
const modules = ["documentation"];
|
|
132
|
+
resolveModuleNames(modules, true);
|
|
133
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("deprecated"));
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
consoleSpy.mockRestore();
|
|
137
|
+
}
|
|
131
138
|
});
|
|
132
139
|
it("should not emit warnings when warn=false", () => {
|
|
133
140
|
const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
try {
|
|
142
|
+
const modules = ["documentation"];
|
|
143
|
+
resolveModuleNames(modules, false);
|
|
144
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
consoleSpy.mockRestore();
|
|
148
|
+
}
|
|
138
149
|
});
|
|
139
150
|
});
|
|
140
151
|
describe("getProfileModules", () => {
|
|
@@ -22,6 +22,9 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
22
22
|
const __dirname = path.dirname(__filename);
|
|
23
23
|
const projectRoot = path.resolve(__dirname, "../../../.."); // From cli/src/__tests__/security to root
|
|
24
24
|
describe("Security Pattern Count Consistency", () => {
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
});
|
|
25
28
|
describe("CLI assess-security references", () => {
|
|
26
29
|
it("should use consistent pattern count in assess-security.ts", () => {
|
|
27
30
|
const filePath = path.join(projectRoot, "cli/src/assess-security.ts");
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { describe, it, expect } from "@jest/globals";
|
|
10
10
|
import { AssessmentOptionsSchema, safeParseAssessmentOptions, validateAssessmentOptions, } from "../lib/cli-parserSchemas.js";
|
|
11
11
|
describe("Stage 3 Fix Validation Tests", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
12
15
|
describe("[TEST-001] cli-parserSchemas.ts - stageBVerbose field (FIX-001)", () => {
|
|
13
16
|
describe("stageBVerbose field validation", () => {
|
|
14
17
|
it("should accept stageBVerbose with true value (happy path)", () => {
|
|
@@ -26,9 +26,9 @@ const DEFAULT_HEADERS = {
|
|
|
26
26
|
* Check if a server is available by sending an initialize request
|
|
27
27
|
*/
|
|
28
28
|
async function checkServerAvailable(url) {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
29
31
|
try {
|
|
30
|
-
const controller = new AbortController();
|
|
31
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
32
32
|
const response = await fetch(url, {
|
|
33
33
|
method: "POST",
|
|
34
34
|
headers: DEFAULT_HEADERS,
|
|
@@ -44,12 +44,16 @@ async function checkServerAvailable(url) {
|
|
|
44
44
|
}),
|
|
45
45
|
signal: controller.signal,
|
|
46
46
|
});
|
|
47
|
-
clearTimeout(timeoutId);
|
|
48
47
|
return response.status < 500;
|
|
49
48
|
}
|
|
50
49
|
catch {
|
|
51
50
|
return false;
|
|
52
51
|
}
|
|
52
|
+
finally {
|
|
53
|
+
// Always clean up timeout and abort controller to prevent memory leaks
|
|
54
|
+
clearTimeout(timeoutId);
|
|
55
|
+
controller.abort();
|
|
56
|
+
}
|
|
53
57
|
}
|
|
54
58
|
/**
|
|
55
59
|
* Parse SSE response to extract JSON data
|
|
@@ -120,6 +124,9 @@ async function callTool(url, toolName, args) {
|
|
|
120
124
|
return data;
|
|
121
125
|
}
|
|
122
126
|
describe("Testbed A/B Comparison", () => {
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
jest.clearAllMocks();
|
|
129
|
+
});
|
|
123
130
|
let bothServersAvailable = false;
|
|
124
131
|
let vulnerableAvailable = false;
|
|
125
132
|
let hardenedAvailable = false;
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
import { describe, it, expect } from "@jest/globals";
|
|
9
9
|
import { createTransport } from "../transport.js";
|
|
10
10
|
describe("Transport Creation", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
11
14
|
describe("Input Validation", () => {
|
|
12
15
|
it("should throw error when URL is missing for HTTP transport", () => {
|
|
13
16
|
const options = {
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { ZodError } from "zod";
|
|
10
10
|
import { AssessmentProfileNameSchema, AssessmentModuleNameSchema, ServerConfigSchema, AssessmentOptionsSchema, ValidationResultSchema, validateAssessmentOptions, validateServerConfig, parseAssessmentOptions, safeParseAssessmentOptions, parseModuleNames, safeParseModuleNames, LogLevelSchema, ReportFormatSchema, TransportTypeSchema, ZOD_SCHEMA_VERSION, } from "../cli-parserSchemas.js";
|
|
11
11
|
describe("cli-parserSchemas", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
12
15
|
describe("Re-exported schemas", () => {
|
|
13
16
|
test("exports ZOD_SCHEMA_VERSION", () => {
|
|
14
17
|
expect(ZOD_SCHEMA_VERSION).toBe(1);
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { ZodError } from "zod";
|
|
10
10
|
import { HttpSseServerConfigSchema, StdioServerConfigSchema, ServerEntrySchema, ClaudeDesktopConfigSchema, StandaloneConfigSchema, ConfigFileSchema, parseConfigFile, safeParseConfigFile, validateServerEntry, isHttpSseConfig, isStdioConfig, TransportTypeSchema, } from "../server-configSchemas.js";
|
|
11
11
|
describe("server-configSchemas", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
12
15
|
describe("Re-exported schemas", () => {
|
|
13
16
|
test("exports TransportTypeSchema", () => {
|
|
14
17
|
expect(TransportTypeSchema.safeParse("stdio").success).toBe(true);
|
package/cli/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-
|
|
1
|
+
import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-iCeeS8vg.js";
|
|
2
2
|
const OAuthCallback = ({ onConnect }) => {
|
|
3
3
|
const { toast } = useToast();
|
|
4
4
|
const hasProcessedRef = reactExports.useRef(false);
|
package/client/dist/assets/{OAuthDebugCallback-DkfPOggR.js → OAuthDebugCallback-CWrUH2KB.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-
|
|
1
|
+
import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-iCeeS8vg.js";
|
|
2
2
|
const OAuthDebugCallback = ({ onConnect }) => {
|
|
3
3
|
reactExports.useEffect(() => {
|
|
4
4
|
let isProcessed = false;
|
|
@@ -16373,7 +16373,7 @@ object({
|
|
|
16373
16373
|
token_type_hint: string().optional()
|
|
16374
16374
|
}).strip();
|
|
16375
16375
|
const name = "@bryan-thompson/inspector-assessment-client";
|
|
16376
|
-
const version$1 = "1.35.
|
|
16376
|
+
const version$1 = "1.35.3";
|
|
16377
16377
|
const packageJson = {
|
|
16378
16378
|
name,
|
|
16379
16379
|
version: version$1
|
|
@@ -48920,7 +48920,7 @@ const useTheme = () => {
|
|
|
48920
48920
|
[theme, setThemeWithSideEffect]
|
|
48921
48921
|
);
|
|
48922
48922
|
};
|
|
48923
|
-
const version = "1.35.
|
|
48923
|
+
const version = "1.35.3";
|
|
48924
48924
|
var [createTooltipContext] = createContextScope("Tooltip", [
|
|
48925
48925
|
createPopperScope
|
|
48926
48926
|
]);
|
|
@@ -52515,13 +52515,13 @@ const App = () => {
|
|
|
52515
52515
|
) });
|
|
52516
52516
|
if (window.location.pathname === "/oauth/callback") {
|
|
52517
52517
|
const OAuthCallback = React.lazy(
|
|
52518
|
-
() => __vitePreload(() => import("./OAuthCallback-
|
|
52518
|
+
() => __vitePreload(() => import("./OAuthCallback-BglPlsRX.js"), true ? [] : void 0)
|
|
52519
52519
|
);
|
|
52520
52520
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
|
|
52521
52521
|
}
|
|
52522
52522
|
if (window.location.pathname === "/oauth/callback/debug") {
|
|
52523
52523
|
const OAuthDebugCallback = React.lazy(
|
|
52524
|
-
() => __vitePreload(() => import("./OAuthDebugCallback-
|
|
52524
|
+
() => __vitePreload(() => import("./OAuthDebugCallback-CWrUH2KB.js"), true ? [] : void 0)
|
|
52525
52525
|
);
|
|
52526
52526
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
|
|
52527
52527
|
}
|
package/client/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/mcp.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>MCP Inspector</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-iCeeS8vg.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-BoUA5OL1.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -280,4 +280,27 @@ export interface PackageJson {
|
|
|
280
280
|
export type ToolInputSchema = JSONSchema7 & {
|
|
281
281
|
type: "object";
|
|
282
282
|
};
|
|
283
|
+
/**
|
|
284
|
+
* Result of namespace/prefix detection for tool naming.
|
|
285
|
+
* Used to identify when tools share a common namespace (e.g., calc_add, calc_subtract).
|
|
286
|
+
* This helps downstream analyzers (like mcp-auditor) understand intentional naming patterns
|
|
287
|
+
* and reduces false positives for "naming conflicts".
|
|
288
|
+
* @public
|
|
289
|
+
*/
|
|
290
|
+
export interface NamespaceDetectionResult {
|
|
291
|
+
/** Whether a namespace was detected */
|
|
292
|
+
detected: boolean;
|
|
293
|
+
/** The detected namespace/prefix (e.g., "calc" from calc_add, calc_subtract) */
|
|
294
|
+
namespace?: string;
|
|
295
|
+
/** Confidence level of the detection */
|
|
296
|
+
confidence: "high" | "medium" | "low";
|
|
297
|
+
/** Number of tools that match this namespace */
|
|
298
|
+
toolsCovered: number;
|
|
299
|
+
/** Total number of tools analyzed */
|
|
300
|
+
totalTools: number;
|
|
301
|
+
/** How the namespace was detected */
|
|
302
|
+
matchPattern?: "prefix" | "serverName" | "none";
|
|
303
|
+
/** Sample tool names showing the pattern */
|
|
304
|
+
evidence?: string[];
|
|
305
|
+
}
|
|
283
306
|
//# sourceMappingURL=coreTypes.d.ts.map
|