@bryan-thompson/inspector-assessment-client 1.26.6 → 1.26.7

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 (57) hide show
  1. package/lib/lib/assessment/configTypes.d.ts +2 -0
  2. package/lib/lib/assessment/configTypes.d.ts.map +1 -1
  3. package/lib/lib/securityPatterns.d.ts +4 -2
  4. package/lib/lib/securityPatterns.d.ts.map +1 -1
  5. package/lib/lib/securityPatterns.js +146 -2
  6. package/lib/services/assessment/modules/AUPComplianceAssessor.js +9 -9
  7. package/lib/services/assessment/modules/AuthenticationAssessor.js +4 -4
  8. package/lib/services/assessment/modules/BaseAssessor.d.ts +0 -14
  9. package/lib/services/assessment/modules/BaseAssessor.d.ts.map +1 -1
  10. package/lib/services/assessment/modules/BaseAssessor.js +1 -33
  11. package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.js +1 -1
  12. package/lib/services/assessment/modules/DeveloperExperienceAssessor.js +1 -1
  13. package/lib/services/assessment/modules/DocumentationAssessor.js +2 -2
  14. package/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts.map +1 -1
  15. package/lib/services/assessment/modules/ErrorHandlingAssessor.js +8 -8
  16. package/lib/services/assessment/modules/ExternalAPIScannerAssessor.d.ts.map +1 -1
  17. package/lib/services/assessment/modules/ExternalAPIScannerAssessor.js +3 -3
  18. package/lib/services/assessment/modules/FunctionalityAssessor.js +9 -9
  19. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
  20. package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +12 -12
  21. package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
  22. package/lib/services/assessment/modules/ManifestValidationAssessor.js +9 -5
  23. package/lib/services/assessment/modules/PortabilityAssessor.d.ts.map +1 -1
  24. package/lib/services/assessment/modules/PortabilityAssessor.js +3 -3
  25. package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.js +4 -4
  26. package/lib/services/assessment/modules/PromptAssessor.js +2 -2
  27. package/lib/services/assessment/modules/ProtocolComplianceAssessor.d.ts.map +1 -1
  28. package/lib/services/assessment/modules/ProtocolComplianceAssessor.js +7 -7
  29. package/lib/services/assessment/modules/ProtocolConformanceAssessor.js +1 -1
  30. package/lib/services/assessment/modules/ResourceAssessor.js +1 -1
  31. package/lib/services/assessment/modules/SecurityAssessor.d.ts +25 -2
  32. package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
  33. package/lib/services/assessment/modules/SecurityAssessor.js +149 -17
  34. package/lib/services/assessment/modules/TemporalAssessor.d.ts.map +1 -1
  35. package/lib/services/assessment/modules/TemporalAssessor.js +10 -10
  36. package/lib/services/assessment/modules/ToolAnnotationAssessor.js +9 -9
  37. package/lib/services/assessment/modules/UsabilityAssessor.js +1 -1
  38. package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.d.ts.map +1 -1
  39. package/lib/services/assessment/modules/annotations/DescriptionPoisoningDetector.js +37 -0
  40. package/lib/services/assessment/modules/index.d.ts +3 -0
  41. package/lib/services/assessment/modules/index.d.ts.map +1 -1
  42. package/lib/services/assessment/modules/securityTests/ChainExecutionTester.d.ts +104 -0
  43. package/lib/services/assessment/modules/securityTests/ChainExecutionTester.d.ts.map +1 -0
  44. package/lib/services/assessment/modules/securityTests/ChainExecutionTester.js +257 -0
  45. package/lib/services/assessment/modules/securityTests/CrossToolStateTester.d.ts +91 -0
  46. package/lib/services/assessment/modules/securityTests/CrossToolStateTester.d.ts.map +1 -0
  47. package/lib/services/assessment/modules/securityTests/CrossToolStateTester.js +225 -0
  48. package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +120 -0
  49. package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -1
  50. package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +338 -0
  51. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +59 -0
  52. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
  53. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +168 -0
  54. package/lib/services/assessment/modules/securityTests/index.d.ts +3 -1
  55. package/lib/services/assessment/modules/securityTests/index.d.ts.map +1 -1
  56. package/lib/services/assessment/modules/securityTests/index.js +2 -0
  57. package/package.json +1 -1
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Chain Execution Tester
3
+ * Dynamic testing for multi-tool chain exploitation vulnerabilities
4
+ *
5
+ * Issue #93, Challenge #6: Multi-tool chained exploitation attacks
6
+ * Tests for:
7
+ * 1. Arbitrary tool invocation without allowlist
8
+ * 2. Output injection via {{output}} template substitution
9
+ * 3. Recursive chain execution (DoS potential)
10
+ * 4. State poisoning between chain steps
11
+ * 5. Missing depth/size limits
12
+ *
13
+ * A/B Validation:
14
+ * - vulnerable-mcp (10900): Should detect all vulnerability categories
15
+ * - hardened-mcp (10901): 0 false positives (validation-only behavior)
16
+ */
17
+ import { SecurityResponseAnalyzer, } from "./SecurityResponseAnalyzer.js";
18
+ /**
19
+ * Tests for multi-tool chain exploitation vulnerabilities
20
+ */
21
+ export class ChainExecutionTester {
22
+ verbose;
23
+ analyzer;
24
+ constructor(config = {}) {
25
+ this.verbose = config.verbose ?? false;
26
+ this.analyzer = new SecurityResponseAnalyzer();
27
+ }
28
+ /**
29
+ * Log message if verbose logging is enabled
30
+ */
31
+ log(message) {
32
+ if (this.verbose) {
33
+ // eslint-disable-next-line no-console
34
+ console.log(`[ChainExecutionTester] ${message}`);
35
+ }
36
+ }
37
+ /**
38
+ * Identify tools that might be chain executors
39
+ * Looks for tools with names/descriptions/parameters suggesting chain execution
40
+ */
41
+ identifyChainExecutorTools(tools) {
42
+ const chainNamePatterns = [
43
+ /chain/i,
44
+ /executor/i,
45
+ /pipeline/i,
46
+ /sequence/i,
47
+ /workflow/i,
48
+ /orchestrat/i,
49
+ /multi.*tool/i,
50
+ /batch/i,
51
+ ];
52
+ const chainParamPatterns = [
53
+ /chain/i,
54
+ /steps/i,
55
+ /sequence/i,
56
+ /pipeline/i,
57
+ /tools/i,
58
+ /commands/i,
59
+ ];
60
+ return tools.filter((tool) => {
61
+ // Check tool name
62
+ const nameMatches = chainNamePatterns.some((p) => p.test(tool.name));
63
+ // Check description
64
+ const descMatches = tool.description &&
65
+ chainNamePatterns.some((p) => p.test(tool.description || ""));
66
+ // Check parameter names
67
+ const schema = tool.inputSchema;
68
+ const paramNames = Object.keys(schema?.properties || {});
69
+ const paramMatches = paramNames.some((param) => chainParamPatterns.some((p) => p.test(param)));
70
+ return nameMatches || descMatches || paramMatches;
71
+ });
72
+ }
73
+ /**
74
+ * Get the parameter name for chain input from tool schema
75
+ */
76
+ getChainParamName(tool) {
77
+ const schema = tool.inputSchema;
78
+ const paramNames = Object.keys(schema?.properties || {});
79
+ // Look for chain-like parameter names
80
+ const chainParam = paramNames.find((p) => /chain|steps|sequence|pipeline/i.test(p));
81
+ return chainParam || "chain";
82
+ }
83
+ /**
84
+ * Extract text content from tool response
85
+ */
86
+ extractResponseText(response) {
87
+ if (!response)
88
+ return "";
89
+ // Handle content array format
90
+ if (response.content && Array.isArray(response.content)) {
91
+ return response.content
92
+ .map((item) => {
93
+ if (typeof item === "string")
94
+ return item;
95
+ if (item && typeof item === "object" && "text" in item)
96
+ return String(item.text);
97
+ return JSON.stringify(item);
98
+ })
99
+ .join("\n");
100
+ }
101
+ return "";
102
+ }
103
+ /**
104
+ * Determine the vulnerability reason from analysis result
105
+ */
106
+ determineVulnerabilityReason(analysis) {
107
+ if (analysis.vulnerabilityCategories.includes("OUTPUT_INJECTION")) {
108
+ return "output_injection_detected";
109
+ }
110
+ if (analysis.vulnerabilityCategories.includes("RECURSIVE_CHAIN")) {
111
+ return "recursive_execution_detected";
112
+ }
113
+ if (analysis.vulnerabilityCategories.includes("ARBITRARY_TOOL_INVOCATION")) {
114
+ return "arbitrary_tool_accepted";
115
+ }
116
+ if (analysis.vulnerabilityCategories.includes("STATE_POISONING")) {
117
+ return "state_poisoning_detected";
118
+ }
119
+ return "chain_execution_confirmed";
120
+ }
121
+ /**
122
+ * Test single chain payload against a tool
123
+ */
124
+ async testChainPayload(callTool, tool, chainPayload, paramName) {
125
+ const actualParamName = paramName || this.getChainParamName(tool);
126
+ try {
127
+ this.log(`Testing payload on ${tool.name} with param ${actualParamName}`);
128
+ const response = await callTool(tool.name, {
129
+ [actualParamName]: chainPayload,
130
+ });
131
+ const responseText = this.extractResponseText(response);
132
+ // Analyze response for chain exploitation
133
+ const analysis = this.analyzer.analyzeChainExploitation(response);
134
+ if (analysis.vulnerable) {
135
+ return {
136
+ vulnerable: true,
137
+ reason: this.determineVulnerabilityReason(analysis),
138
+ vulnerabilityCategories: analysis.vulnerabilityCategories,
139
+ evidence: {
140
+ chainPayload,
141
+ response: responseText.substring(0, 500),
142
+ analysisResult: analysis,
143
+ },
144
+ };
145
+ }
146
+ if (analysis.safe) {
147
+ return {
148
+ vulnerable: false,
149
+ reason: analysis.chainType === "SAFE_VALIDATION"
150
+ ? "validation_only"
151
+ : "chain_rejected",
152
+ };
153
+ }
154
+ return {
155
+ vulnerable: false,
156
+ reason: "tool_not_chain_executor",
157
+ };
158
+ }
159
+ catch (error) {
160
+ return {
161
+ vulnerable: false,
162
+ reason: "test_error",
163
+ error: error instanceof Error ? error.message : String(error),
164
+ };
165
+ }
166
+ }
167
+ /**
168
+ * Get test payloads for chain exploitation testing
169
+ */
170
+ getTestPayloads() {
171
+ return [
172
+ {
173
+ name: "single_step_execution",
174
+ payload: '[{"tool": "vulnerable_calculator", "args": {"query": "2+2"}}]',
175
+ description: "Test basic chain execution without allowlist",
176
+ },
177
+ {
178
+ name: "output_injection",
179
+ payload: '[{"tool": "calc", "args": {"q": "test"}}, {"tool": "exec", "args": {"cmd": "{{output}}"}}]',
180
+ description: "Test output injection via {{output}} template",
181
+ },
182
+ {
183
+ name: "recursive_chain",
184
+ payload: '[{"tool": "vulnerable_chain_executor", "args": {"chain": "[]"}}]',
185
+ description: "Test recursive/circular chain execution",
186
+ },
187
+ {
188
+ name: "arbitrary_tool",
189
+ payload: '[{"tool": "UNKNOWN_MALICIOUS_TOOL_XYZ", "args": {}}]',
190
+ description: "Test acceptance of arbitrary tool names",
191
+ },
192
+ {
193
+ name: "state_poisoning",
194
+ payload: '[{"tool": "vulnerable_config_modifier", "args": {"setting": "admin_mode=true"}}, {"tool": "vulnerable_admin_action", "args": {"action": "read secrets"}}]',
195
+ description: "Test state poisoning between chain steps",
196
+ },
197
+ {
198
+ name: "depth_bypass",
199
+ payload: JSON.stringify(Array(15)
200
+ .fill(null)
201
+ .map(() => ({ tool: "test", args: {} }))),
202
+ description: "Test depth limit bypass with 15 steps",
203
+ },
204
+ ];
205
+ }
206
+ /**
207
+ * Run comprehensive chain exploitation tests on a tool
208
+ */
209
+ async runChainExploitationTests(callTool, tool) {
210
+ const results = new Map();
211
+ const testPayloads = this.getTestPayloads();
212
+ const chainParam = this.getChainParamName(tool);
213
+ this.log(`Running chain exploitation tests on ${tool.name}`);
214
+ this.log(`Using parameter: ${chainParam}`);
215
+ for (const test of testPayloads) {
216
+ this.log(` Test: ${test.name} - ${test.description}`);
217
+ const result = await this.testChainPayload(callTool, tool, test.payload, chainParam);
218
+ results.set(test.name, result);
219
+ if (this.verbose) {
220
+ // eslint-disable-next-line no-console
221
+ console.log(` Result: ${result.vulnerable ? "VULNERABLE" : "SAFE"} (${result.reason})`);
222
+ }
223
+ }
224
+ return results;
225
+ }
226
+ /**
227
+ * Summarize chain exploitation test results
228
+ */
229
+ summarizeResults(results) {
230
+ let vulnerable = 0;
231
+ let safe = 0;
232
+ let errors = 0;
233
+ const vulnerableTests = [];
234
+ const categories = new Set();
235
+ for (const [testName, result] of results) {
236
+ if (result.reason === "test_error") {
237
+ errors++;
238
+ }
239
+ else if (result.vulnerable) {
240
+ vulnerable++;
241
+ vulnerableTests.push(testName);
242
+ result.vulnerabilityCategories?.forEach((c) => categories.add(c));
243
+ }
244
+ else {
245
+ safe++;
246
+ }
247
+ }
248
+ return {
249
+ total: results.size,
250
+ vulnerable,
251
+ safe,
252
+ errors,
253
+ vulnerableTests,
254
+ vulnerabilityCategories: Array.from(categories),
255
+ };
256
+ }
257
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Cross-Tool State Tester
3
+ * Tests for privilege escalation by calling tools in sequence
4
+ *
5
+ * Issue #92, Challenge #7: Cross-tool state-based authorization bypass
6
+ * Detects when one tool can modify shared state that affects another tool's authorization.
7
+ *
8
+ * Attack flow:
9
+ * 1. Call admin_action → should get "access denied"
10
+ * 2. Call config_modifier with "admin_mode=true"
11
+ * 3. Call admin_action again → if now succeeds, VULNERABLE
12
+ */
13
+ import { CompatibilityCallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
14
+ import { ProgressCallback } from "../../../../lib/assessment/progressTypes.js";
15
+ /**
16
+ * Function type for calling MCP tools
17
+ */
18
+ export type CallToolFunction = (name: string, params: Record<string, unknown>) => Promise<CompatibilityCallToolResult>;
19
+ /**
20
+ * Result of cross-tool privilege escalation test
21
+ */
22
+ export interface CrossToolTestResult {
23
+ vulnerable: boolean;
24
+ reason: "privilege_escalation_confirmed" | "escalation_blocked" | "baseline_has_access" | "modifier_rejected" | "test_error";
25
+ evidence?: {
26
+ baseline: string;
27
+ afterModifier: string;
28
+ enableResult?: string;
29
+ };
30
+ error?: string;
31
+ }
32
+ /**
33
+ * Identified tool pair for cross-tool testing
34
+ */
35
+ export interface ToolPair {
36
+ admin: Tool;
37
+ modifier: Tool;
38
+ }
39
+ /**
40
+ * Configuration for cross-tool state testing
41
+ */
42
+ export interface CrossToolTestConfig {
43
+ /** Timeout for each tool call in ms (default: 5000) */
44
+ timeout?: number;
45
+ /** Enable verbose logging */
46
+ verbose?: boolean;
47
+ }
48
+ /**
49
+ * Tests for cross-tool privilege escalation via shared mutable state
50
+ */
51
+ export declare class CrossToolStateTester {
52
+ private readonly verbose;
53
+ constructor(config?: CrossToolTestConfig);
54
+ /**
55
+ * Log message if verbose logging is enabled
56
+ */
57
+ private log;
58
+ /**
59
+ * Identify potential cross-tool pairs for testing
60
+ * Looks for admin_action/privileged tools and config_modifier/setting tools
61
+ */
62
+ identifyCrossToolPairs(tools: Tool[]): ToolPair[];
63
+ /**
64
+ * Test cross-tool privilege escalation
65
+ *
66
+ * Attack flow:
67
+ * 1. Call admin_action → expect "access denied"
68
+ * 2. Call config_modifier with "admin_mode=true"
69
+ * 3. Call admin_action again → if now succeeds, VULNERABLE
70
+ */
71
+ testPrivilegeEscalation(callTool: CallToolFunction, adminTool: Tool, modifierTool: Tool, onProgress?: ProgressCallback): Promise<CrossToolTestResult>;
72
+ /**
73
+ * Run sequence tests on all identified tool pairs
74
+ */
75
+ runAllSequenceTests(tools: Tool[], callTool: CallToolFunction, onProgress?: ProgressCallback): Promise<Map<string, CrossToolTestResult>>;
76
+ /**
77
+ * Get summary of sequence test results
78
+ */
79
+ summarizeResults(results: Map<string, CrossToolTestResult>): {
80
+ total: number;
81
+ vulnerable: number;
82
+ safe: number;
83
+ errors: number;
84
+ vulnerablePairs: string[];
85
+ };
86
+ /**
87
+ * Extract text content from MCP response
88
+ */
89
+ private extractResponseText;
90
+ }
91
+ //# sourceMappingURL=CrossToolStateTester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CrossToolStateTester.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/CrossToolStateTester.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,2BAA2B,EAC3B,IAAI,EACL,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,2BAA2B,CAAC,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EACF,gCAAgC,GAChC,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,YAAY,CAAC;IACjB,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,IAAI,CAAC;IACZ,QAAQ,EAAE,IAAI,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEtB,MAAM,GAAE,mBAAwB;IAK5C;;OAEG;IACH,OAAO,CAAC,GAAG;IAOX;;;OAGG;IACH,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE;IA8BjD;;;;;;;OAOG;IACG,uBAAuB,CAC3B,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,IAAI,EACf,YAAY,EAAE,IAAI,EAClB,UAAU,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,mBAAmB,CAAC;IAkI/B;;OAEG;IACG,mBAAmB,CACvB,KAAK,EAAE,IAAI,EAAE,EACb,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAkB5C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG;QAC3D,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,EAAE,MAAM,EAAE,CAAC;KAC3B;IA0BD;;OAEG;IACH,OAAO,CAAC,mBAAmB;CAkB5B"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Cross-Tool State Tester
3
+ * Tests for privilege escalation by calling tools in sequence
4
+ *
5
+ * Issue #92, Challenge #7: Cross-tool state-based authorization bypass
6
+ * Detects when one tool can modify shared state that affects another tool's authorization.
7
+ *
8
+ * Attack flow:
9
+ * 1. Call admin_action → should get "access denied"
10
+ * 2. Call config_modifier with "admin_mode=true"
11
+ * 3. Call admin_action again → if now succeeds, VULNERABLE
12
+ */
13
+ /**
14
+ * Tests for cross-tool privilege escalation via shared mutable state
15
+ */
16
+ export class CrossToolStateTester {
17
+ verbose;
18
+ constructor(config = {}) {
19
+ // Note: config.timeout is accepted but tool call timeout is handled externally
20
+ this.verbose = config.verbose ?? false;
21
+ }
22
+ /**
23
+ * Log message if verbose logging is enabled
24
+ */
25
+ log(message) {
26
+ if (this.verbose) {
27
+ // eslint-disable-next-line no-console
28
+ console.log(`[CrossToolStateTester] ${message}`);
29
+ }
30
+ }
31
+ /**
32
+ * Identify potential cross-tool pairs for testing
33
+ * Looks for admin_action/privileged tools and config_modifier/setting tools
34
+ */
35
+ identifyCrossToolPairs(tools) {
36
+ const pairs = [];
37
+ // Find admin-like tools (tools that check authorization)
38
+ const adminTools = tools.filter((t) => /admin[_-]?action|privileged|elevated|sensitive/i.test(t.name) ||
39
+ /admin[_-]?action|privileged|requires\s+admin/i.test(t.description || ""));
40
+ // Find modifier-like tools (tools that modify state/config)
41
+ const modifierTools = tools.filter((t) => /config[_-]?modifier|setting|mode|enable|activate/i.test(t.name) ||
42
+ /modify.*config|set.*mode|enable.*admin/i.test(t.description || ""));
43
+ for (const admin of adminTools) {
44
+ for (const modifier of modifierTools) {
45
+ if (admin.name !== modifier.name) {
46
+ pairs.push({ admin, modifier });
47
+ }
48
+ }
49
+ }
50
+ return pairs;
51
+ }
52
+ /**
53
+ * Test cross-tool privilege escalation
54
+ *
55
+ * Attack flow:
56
+ * 1. Call admin_action → expect "access denied"
57
+ * 2. Call config_modifier with "admin_mode=true"
58
+ * 3. Call admin_action again → if now succeeds, VULNERABLE
59
+ */
60
+ async testPrivilegeEscalation(callTool, adminTool, modifierTool, onProgress) {
61
+ this.log(`Testing pair: ${modifierTool.name} → ${adminTool.name}`);
62
+ try {
63
+ // Step 1: Baseline - admin action should be denied
64
+ this.log(`Step 1: Calling ${adminTool.name} (baseline)`);
65
+ const baseline = await callTool(adminTool.name, {
66
+ action: "read secrets",
67
+ });
68
+ const baselineText = this.extractResponseText(baseline);
69
+ const baselineDenied = /access denied|unauthorized|admin.*required|permission.*denied/i.test(baselineText);
70
+ if (!baselineDenied) {
71
+ // Already has access - can't test escalation (might indicate other issues)
72
+ // Check if it's because shared_state_checked is false (hardened)
73
+ if (/"shared_state_checked"\s*:\s*false/i.test(baselineText)) {
74
+ return {
75
+ vulnerable: false,
76
+ reason: "baseline_has_access",
77
+ evidence: {
78
+ baseline: baselineText.slice(0, 500),
79
+ afterModifier: "",
80
+ },
81
+ };
82
+ }
83
+ return {
84
+ vulnerable: false,
85
+ reason: "baseline_has_access",
86
+ evidence: {
87
+ baseline: baselineText.slice(0, 500),
88
+ afterModifier: "",
89
+ },
90
+ };
91
+ }
92
+ // Step 2: Attempt to enable admin mode via config modifier
93
+ const enableResult = await callTool(modifierTool.name, {
94
+ setting: "admin_mode=true",
95
+ });
96
+ const enableText = this.extractResponseText(enableResult);
97
+ const modeEnabled = /admin.*activated|elevated.*granted|admin_mode.*enabled|configuration.*updated.*admin/i.test(enableText);
98
+ // Check if modifier is hardened (stores without modifying)
99
+ const modifierHardened = /stored.*without.*modifying|stored.*for.*review|not.*modifying.*runtime/i.test(enableText);
100
+ if (modifierHardened) {
101
+ return {
102
+ vulnerable: false,
103
+ reason: "modifier_rejected",
104
+ evidence: {
105
+ baseline: baselineText.slice(0, 300),
106
+ afterModifier: "",
107
+ enableResult: enableText.slice(0, 300),
108
+ },
109
+ };
110
+ }
111
+ if (!modeEnabled) {
112
+ return {
113
+ vulnerable: false,
114
+ reason: "modifier_rejected",
115
+ evidence: {
116
+ baseline: baselineText.slice(0, 300),
117
+ afterModifier: "",
118
+ enableResult: enableText.slice(0, 300),
119
+ },
120
+ };
121
+ }
122
+ // Step 3: Test if admin action now succeeds
123
+ const escalated = await callTool(adminTool.name, {
124
+ action: "read secrets",
125
+ });
126
+ const escalatedText = this.extractResponseText(escalated);
127
+ // Check for successful escalation
128
+ const escalationSucceeded = !/access denied|unauthorized|admin.*required|permission.*denied/i.test(escalatedText) &&
129
+ /"secrets"|API_KEY|credentials|admin_mode.*true.*evidence.*cross-tool/i.test(escalatedText);
130
+ // Emit progress event if callback provided
131
+ if (onProgress && escalationSucceeded) {
132
+ onProgress({
133
+ type: "vulnerability_found",
134
+ tool: adminTool.name,
135
+ pattern: "Cross-Tool State Bypass",
136
+ confidence: "high",
137
+ evidence: `Cross-tool privilege escalation: ${modifierTool.name} enables access to ${adminTool.name}. ${escalatedText.slice(0, 200)}`,
138
+ riskLevel: "HIGH",
139
+ requiresReview: false,
140
+ payload: "admin_mode=true",
141
+ });
142
+ }
143
+ return {
144
+ vulnerable: escalationSucceeded,
145
+ reason: escalationSucceeded
146
+ ? "privilege_escalation_confirmed"
147
+ : "escalation_blocked",
148
+ evidence: {
149
+ baseline: baselineText.slice(0, 300),
150
+ afterModifier: escalatedText.slice(0, 300),
151
+ enableResult: enableText.slice(0, 300),
152
+ },
153
+ };
154
+ }
155
+ catch (error) {
156
+ return {
157
+ vulnerable: false,
158
+ reason: "test_error",
159
+ error: error instanceof Error ? error.message : String(error),
160
+ };
161
+ }
162
+ }
163
+ /**
164
+ * Run sequence tests on all identified tool pairs
165
+ */
166
+ async runAllSequenceTests(tools, callTool, onProgress) {
167
+ const pairs = this.identifyCrossToolPairs(tools);
168
+ const results = new Map();
169
+ for (const { admin, modifier } of pairs) {
170
+ const key = `${modifier.name} → ${admin.name}`;
171
+ const result = await this.testPrivilegeEscalation(callTool, admin, modifier, onProgress);
172
+ results.set(key, result);
173
+ }
174
+ return results;
175
+ }
176
+ /**
177
+ * Get summary of sequence test results
178
+ */
179
+ summarizeResults(results) {
180
+ let vulnerable = 0;
181
+ let safe = 0;
182
+ let errors = 0;
183
+ const vulnerablePairs = [];
184
+ for (const [key, result] of results) {
185
+ if (result.reason === "test_error") {
186
+ errors++;
187
+ }
188
+ else if (result.vulnerable) {
189
+ vulnerable++;
190
+ vulnerablePairs.push(key);
191
+ }
192
+ else {
193
+ safe++;
194
+ }
195
+ }
196
+ return {
197
+ total: results.size,
198
+ vulnerable,
199
+ safe,
200
+ errors,
201
+ vulnerablePairs,
202
+ };
203
+ }
204
+ /**
205
+ * Extract text content from MCP response
206
+ */
207
+ extractResponseText(response) {
208
+ if (!response)
209
+ return "";
210
+ // Handle content array format
211
+ if (response.content && Array.isArray(response.content)) {
212
+ return response.content
213
+ .map((item) => {
214
+ if (typeof item === "string")
215
+ return item;
216
+ if (item && typeof item === "object" && "text" in item)
217
+ return String(item.text);
218
+ return JSON.stringify(item);
219
+ })
220
+ .join("\n");
221
+ }
222
+ // Fallback to JSON stringify
223
+ return JSON.stringify(response);
224
+ }
225
+ }