@bryan-thompson/inspector-assessment 1.35.2 → 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.
Files changed (60) hide show
  1. package/cli/build/__tests__/assess-full-e2e.test.js +23 -7
  2. package/cli/build/__tests__/assess-full.test.js +3 -0
  3. package/cli/build/__tests__/assessment-runner/assessment-executor.test.js +14 -1
  4. package/cli/build/__tests__/assessment-runner/config-builder.test.js +6 -0
  5. package/cli/build/__tests__/assessment-runner/index.test.js +3 -0
  6. package/cli/build/__tests__/assessment-runner/server-config.test.js +3 -0
  7. package/cli/build/__tests__/assessment-runner/server-connection.test.js +9 -1
  8. package/cli/build/__tests__/assessment-runner/source-loader.test.js +29 -16
  9. package/cli/build/__tests__/assessment-runner/tool-wrapper.test.js +3 -0
  10. package/cli/build/__tests__/assessment-runner-facade.test.js +3 -0
  11. package/cli/build/__tests__/cli-build-fixes.test.js +3 -0
  12. package/cli/build/__tests__/flag-parsing.test.js +3 -2
  13. package/cli/build/__tests__/http-transport-integration.test.js +10 -3
  14. package/cli/build/__tests__/lib/server-configSchemas.test.js +4 -1
  15. package/cli/build/__tests__/lib/zodErrorFormatter.test.js +4 -1
  16. package/cli/build/__tests__/profiles.test.js +19 -8
  17. package/cli/build/__tests__/security/security-pattern-count.test.js +3 -0
  18. package/cli/build/__tests__/stage3-fix-validation.test.js +3 -0
  19. package/cli/build/__tests__/testbed-integration.test.js +10 -3
  20. package/cli/build/__tests__/transport.test.js +3 -0
  21. package/cli/build/lib/__tests__/cli-parserSchemas.test.js +3 -0
  22. package/cli/build/lib/assessment-runner/__tests__/server-configSchemas.test.js +3 -0
  23. package/cli/package.json +1 -1
  24. package/client/dist/assets/{OAuthCallback-jfmizOMH.js → OAuthCallback-BglPlsRX.js} +1 -1
  25. package/client/dist/assets/{OAuthDebugCallback-bU5kKvnt.js → OAuthDebugCallback-CWrUH2KB.js} +1 -1
  26. package/client/dist/assets/{index-Ce63ds7G.js → index-iCeeS8vg.js} +4 -4
  27. package/client/dist/index.html +1 -1
  28. package/client/lib/lib/assessment/coreTypes.d.ts +23 -0
  29. package/client/lib/lib/assessment/coreTypes.d.ts.map +1 -1
  30. package/client/lib/lib/assessment/extendedTypes.d.ts +45 -2
  31. package/client/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
  32. package/client/lib/lib/assessment/jsonlEventSchemas.d.ts +4 -4
  33. package/client/lib/lib/assessment/resultTypes.d.ts +12 -1
  34. package/client/lib/lib/assessment/resultTypes.d.ts.map +1 -1
  35. package/client/lib/lib/aupPatterns.d.ts +50 -0
  36. package/client/lib/lib/aupPatterns.d.ts.map +1 -1
  37. package/client/lib/lib/aupPatterns.js +140 -0
  38. package/client/lib/lib/securityPatterns.d.ts.map +1 -1
  39. package/client/lib/lib/securityPatterns.js +92 -0
  40. package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts +26 -1
  41. package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts.map +1 -1
  42. package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.js +160 -1
  43. package/client/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +32 -0
  44. package/client/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
  45. package/client/lib/services/assessment/modules/ManifestValidationAssessor.js +218 -20
  46. package/client/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts.map +1 -1
  47. package/client/lib/services/assessment/modules/securityTests/ConfidenceScorer.js +28 -0
  48. package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +95 -0
  49. package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -1
  50. package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +174 -0
  51. package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -1
  52. package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +15 -0
  53. package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +40 -0
  54. package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
  55. package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +143 -131
  56. package/client/package.json +1 -1
  57. package/package.json +1 -1
  58. package/server/build/__tests__/helpers.test.js +3 -0
  59. package/server/build/__tests__/security.test.js +3 -0
  60. 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
- mockExistsSync.mockImplementation((p) => p === path.join(sourcePath, "manifest.json"));
122
- mockReadFileSync.mockReturnValue("{ invalid json }");
123
- const result = loadSourceFiles(sourcePath);
124
- expect(result.manifestRaw).toBe("{ invalid json }");
125
- expect(result.manifestJson).toBeUndefined();
126
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Failed to parse manifest.json"));
127
- consoleSpy.mockRestore();
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
- mockExistsSync.mockReturnValue(false);
280
- mockReaddirSync.mockImplementation(() => {
281
- throw new Error("Permission denied");
282
- });
283
- const result = loadSourceFiles(sourcePath);
284
- expect(result.sourceCodeFiles?.size).toBe(0);
285
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Could not load source files"), expect.any(Error));
286
- consoleSpy.mockRestore();
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
- processExitSpy.mockRestore();
673
- consoleErrorSpy.mockRestore();
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
- const modules = ["documentation"];
128
- resolveModuleNames(modules, true);
129
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("deprecated"));
130
- consoleSpy.mockRestore();
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
- const modules = ["documentation"];
135
- resolveModuleNames(modules, false);
136
- expect(consoleSpy).not.toHaveBeenCalled();
137
- consoleSpy.mockRestore();
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment-cli",
3
- "version": "1.35.2",
3
+ "version": "1.35.3",
4
4
  "description": "CLI for the Enhanced MCP Inspector with assessment capabilities",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",
@@ -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-Ce63ds7G.js";
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);
@@ -1,4 +1,4 @@
1
- import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-Ce63ds7G.js";
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.2";
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.2";
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-jfmizOMH.js"), true ? [] : void 0)
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-bU5kKvnt.js"), true ? [] : void 0)
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
  }
@@ -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-Ce63ds7G.js"></script>
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