@zcode-apps/mcp-sentinel 0.1.1 β†’ 0.2.1

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/README.md CHANGED
@@ -13,24 +13,55 @@ npm install -g @zcode-apps/mcp-sentinel
13
13
  mcp-sentinel scan https://your-mcp-server.com
14
14
  ```
15
15
 
16
+ ## Usage
17
+
18
+ ```bash
19
+ # Basic scan (text output)
20
+ npx @zcode-apps/mcp-sentinel scan https://api.example.com/mcp
21
+
22
+ # JSON output
23
+ npx @zcode-apps/mcp-sentinel scan https://api.example.com/mcp --json
24
+
25
+ # Verbose mode (show evidence)
26
+ npx @zcode-apps/mcp-sentinel scan https://api.example.com/mcp --verbose
27
+ ```
28
+
16
29
  ## Features
17
30
 
18
- - **RCE Detection** - Remote Code Execution vulnerability scanning
19
- - **Auth Audit** - Authentication gap detection
20
- - **Path Traversal** - File access vulnerability scanning
21
- - **OWASP MCP Top 10** - Full compliance check
31
+ - **MCP Protocol Detection** - Verifies valid MCP endpoints
32
+ - **Authentication Bypass** - Checks for missing auth
33
+ - **Dangerous Tool Detection** - Finds tools with RCE, file access, SQL risks
34
+ - **Path Traversal** - Detects unsafe resource URIs
35
+ - **Prompt Injection Risks** - Identifies dynamic prompt vulnerabilities
22
36
 
23
- ## Usage
37
+ ## Output Example
24
38
 
25
- ```bash
26
- # Basic scan
27
- npx @zcode-apps/mcp-sentinel scan https://api.example.com
39
+ ```
40
+ πŸ” MCP Sentinel - Security Scanner
41
+
42
+ Target: https://api.example.com/mcp
43
+ ──────────────────────────────────────────────────
28
44
 
29
- # With output file
30
- npx @zcode-apps/mcp-sentinel scan https://api.example.com --output report.json
45
+ πŸ”΅ INFO:
31
46
 
32
- # Verbose mode
33
- npx @zcode-apps/mcp-sentinel scan https://api.example.com --verbose
47
+ [INFO]
48
+ MCP Server detected: my-mcp-server v1.0.0
49
+
50
+ [INFO]
51
+ Found 5 exposed tools: ["get_weather", "run_command", "read_file"]
52
+
53
+ 🟠 HIGH SEVERITY:
54
+
55
+ [AUTH_BYPASS]
56
+ MCP server accepts unauthenticated connections
57
+ πŸ’‘ Recommendation: Implement authentication on MCP endpoints
58
+
59
+ ──────────────────────────────────────────────────
60
+ πŸ“Š SUMMARY:
61
+ Critical: 0
62
+ High: 1
63
+ Medium: 0
64
+ Info: 2
34
65
  ```
35
66
 
36
67
  ## Why MCP Sentinel?
@@ -43,53 +74,10 @@ npx @zcode-apps/mcp-sentinel scan https://api.example.com --verbose
43
74
 
44
75
  **Don't be part of the 43%.** Scan your MCP servers today.
45
76
 
46
- ## Known CVEs Detected
47
-
48
- | CVE | Type | CVSS |
49
- |-----|------|------|
50
- | CVE-2026-01234 | Prompt Injection RCE | 9.8 |
51
- | CVE-2026-2178 | xcode-mcp-server RCE | 9.1 |
52
- | CVE-2026-27825 | MCPwnfluence Attack Chain | 9.1 |
53
- | CVE-2026-27826 | MCPwnfluence RCE | 8.2 |
54
- | CVE-2026-02345 | MCP DoS | 6.5 |
55
-
56
- ## Output Format
57
-
58
- ```json
59
- {
60
- "url": "https://api.example.com",
61
- "timestamp": "2026-03-13T09:00:00Z",
62
- "vulnerabilities": [
63
- {
64
- "type": "RCE",
65
- "severity": "CRITICAL",
66
- "description": "Command injection in tool execution",
67
- "recommendation": "Sanitize all user inputs before execution"
68
- }
69
- ],
70
- "summary": {
71
- "critical": 1,
72
- "high": 0,
73
- "medium": 0,
74
- "low": 0
75
- }
76
- }
77
- ```
78
-
79
- ## Programmatic Usage
80
-
81
- ```typescript
82
- import { MCPSentinel } from '@zcode-apps/mcp-sentinel';
83
-
84
- const scanner = new MCPSentinel();
85
- const results = await scanner.scan('https://api.example.com');
86
-
87
- console.log(results.vulnerabilities);
88
- ```
89
-
90
77
  ## Repository
91
78
 
92
- **GitLab:** https://git.z-code.ai/openclaw-dev/arc-mcp-sentinel
79
+ **GitLab:** https://git.z-code.ai/openclaw-dev/arc-mcp-sentinel
80
+ **npm:** https://www.npmjs.com/package/@zcode-apps/mcp-sentinel
93
81
 
94
82
  ## License
95
83
 
@@ -97,4 +85,4 @@ MIT License
97
85
 
98
86
  ---
99
87
 
100
- **Built by ARC** | **Published by zcode-apps**
88
+ **Built by ARC**
package/dist/cli.js CHANGED
@@ -4,13 +4,68 @@ import { MCPSentinel } from './scanner.js';
4
4
  const program = new Command();
5
5
  program
6
6
  .name('mcp-sentinel')
7
- .description('MCP Security Scanner - Scans for vulnerabilities');
7
+ .description('MCP Security Scanner - Detects vulnerabilities in MCP servers')
8
+ .version('0.2.1');
8
9
  program
9
10
  .command('scan <url>')
10
- .description('Scan a URL for vulnerabilities')
11
- .action(async (url) => {
11
+ .description('Scan an MCP server endpoint for security vulnerabilities')
12
+ .option('-j, --json', 'Output as JSON', false)
13
+ .option('-v, --verbose', 'Show detailed evidence', false)
14
+ .action(async (url, options) => {
12
15
  const scanner = new MCPSentinel();
16
+ if (!options.json) {
17
+ console.log(`\nπŸ” MCP Sentinel - Security Scanner\n`);
18
+ console.log(`Target: ${url}\n`);
19
+ console.log('─'.repeat(50));
20
+ }
13
21
  const results = await scanner.scan(url);
14
- console.log(JSON.stringify(results, null, 2));
22
+ if (options.json) {
23
+ console.log(JSON.stringify(results, null, 2));
24
+ return;
25
+ }
26
+ // Text output
27
+ if (results.length === 0) {
28
+ console.log('βœ… No vulnerabilities found.\n');
29
+ return;
30
+ }
31
+ // Group by severity
32
+ const critical = results.filter(r => r.severity === 'critical');
33
+ const high = results.filter(r => r.severity === 'high');
34
+ const medium = results.filter(r => r.severity === 'medium');
35
+ const low = results.filter(r => r.severity === 'low');
36
+ if (critical.length > 0) {
37
+ console.log('\nπŸ”΄ CRITICAL VULNERABILITIES:');
38
+ critical.forEach(v => printVulnerability(v, options.verbose || false));
39
+ }
40
+ if (high.length > 0) {
41
+ console.log('\n🟠 HIGH SEVERITY:');
42
+ high.forEach(v => printVulnerability(v, options.verbose || false));
43
+ }
44
+ if (medium.length > 0) {
45
+ console.log('\n🟑 MEDIUM SEVERITY:');
46
+ medium.forEach(v => printVulnerability(v, options.verbose || false));
47
+ }
48
+ if (low.length > 0) {
49
+ console.log('\nπŸ”΅ INFO:');
50
+ low.forEach(v => printVulnerability(v, options.verbose || false));
51
+ }
52
+ // Summary
53
+ console.log('\n' + '─'.repeat(50));
54
+ console.log('πŸ“Š SUMMARY:');
55
+ console.log(` Critical: ${critical.length}`);
56
+ console.log(` High: ${high.length}`);
57
+ console.log(` Medium: ${medium.length}`);
58
+ console.log(` Info: ${low.length}`);
59
+ console.log('');
15
60
  });
61
+ function printVulnerability(v, verbose) {
62
+ console.log(`\n [${v.type}]`);
63
+ console.log(` ${v.description}`);
64
+ if (verbose && v.evidence) {
65
+ console.log(` Evidence: ${JSON.stringify(v.evidence, null, 2).split('\n').join('\n ')}`);
66
+ }
67
+ if (v.recommendation) {
68
+ console.log(` πŸ’‘ Recommendation: ${v.recommendation}`);
69
+ }
70
+ }
16
71
  program.parse();
package/dist/scanner.d.ts CHANGED
@@ -1,4 +1,15 @@
1
+ export interface Vulnerability {
2
+ type: string;
3
+ severity: 'critical' | 'high' | 'medium' | 'low';
4
+ description: string;
5
+ evidence?: any;
6
+ recommendation?: string;
7
+ }
1
8
  export declare class MCPSentinel {
2
- scan(url: string): Promise<any[]>;
3
- private checkRCE;
9
+ scan(url: string): Promise<Vulnerability[]>;
10
+ private sendMCPRequest;
11
+ private checkAuthBypass;
12
+ private analyzeTool;
13
+ private checkPathTraversal;
14
+ private analyzePrompt;
4
15
  }
package/dist/scanner.js CHANGED
@@ -2,44 +2,214 @@ import fetch from 'node-fetch';
2
2
  export class MCPSentinel {
3
3
  async scan(url) {
4
4
  const results = [];
5
- // RCE Detection Check
6
- const rceResult = await this.checkRCE(url);
7
- if (rceResult) {
8
- results.push(rceResult);
5
+ console.error(`[MCP Sentinel] Scanning ${url}...`);
6
+ // 1. Check if MCP endpoint is accessible
7
+ const baseUrl = url.replace(/\/$/, '');
8
+ // 2. Try MCP Initialize handshake
9
+ let serverInfo = null;
10
+ try {
11
+ const initResponse = await this.sendMCPRequest(baseUrl, {
12
+ jsonrpc: '2.0',
13
+ id: 1,
14
+ method: 'initialize',
15
+ params: {
16
+ protocolVersion: '2024-11-05',
17
+ clientInfo: {
18
+ name: 'mcp-sentinel',
19
+ version: '0.1.0'
20
+ },
21
+ capabilities: {}
22
+ }
23
+ });
24
+ if (initResponse.result) {
25
+ serverInfo = initResponse.result;
26
+ results.push({
27
+ type: 'INFO',
28
+ severity: 'low',
29
+ description: `MCP Server detected: ${serverInfo.serverInfo?.name || 'Unknown'} v${serverInfo.serverInfo?.version || 'Unknown'}`,
30
+ evidence: serverInfo
31
+ });
32
+ }
33
+ }
34
+ catch (error) {
35
+ results.push({
36
+ type: 'MCP_PROTOCOL_ERROR',
37
+ severity: 'medium',
38
+ description: `MCP endpoint not responding properly: ${error.message}`,
39
+ recommendation: 'Verify the URL is a valid MCP server endpoint'
40
+ });
41
+ return results;
42
+ }
43
+ // 3. Check for authentication bypass
44
+ const authResult = await this.checkAuthBypass(baseUrl);
45
+ if (authResult)
46
+ results.push(authResult);
47
+ // 4. List and analyze tools
48
+ try {
49
+ const toolsResponse = await this.sendMCPRequest(baseUrl, {
50
+ jsonrpc: '2.0',
51
+ id: 2,
52
+ method: 'tools/list'
53
+ });
54
+ if (toolsResponse.result?.tools) {
55
+ const tools = toolsResponse.result.tools;
56
+ results.push({
57
+ type: 'INFO',
58
+ severity: 'low',
59
+ description: `Found ${tools.length} exposed tools`,
60
+ evidence: tools.map((t) => t.name)
61
+ });
62
+ // Check for dangerous tools
63
+ for (const tool of tools) {
64
+ const toolCheck = this.analyzeTool(tool);
65
+ if (toolCheck)
66
+ results.push(toolCheck);
67
+ }
68
+ }
69
+ }
70
+ catch (error) {
71
+ results.push({
72
+ type: 'TOOLS_ACCESS_ERROR',
73
+ severity: 'medium',
74
+ description: `Could not enumerate tools: ${error.message}`
75
+ });
76
+ }
77
+ // 5. Check for resource exposure
78
+ try {
79
+ const resourcesResponse = await this.sendMCPRequest(baseUrl, {
80
+ jsonrpc: '2.0',
81
+ id: 3,
82
+ method: 'resources/list'
83
+ });
84
+ if (resourcesResponse.result?.resources) {
85
+ const resources = resourcesResponse.result.resources;
86
+ // Check for path traversal
87
+ for (const resource of resources) {
88
+ const pathCheck = this.checkPathTraversal(resource);
89
+ if (pathCheck)
90
+ results.push(pathCheck);
91
+ }
92
+ }
93
+ }
94
+ catch (error) {
95
+ // Resources might not be implemented
96
+ }
97
+ // 6. Check for prompt injection vulnerabilities
98
+ try {
99
+ const promptsResponse = await this.sendMCPRequest(baseUrl, {
100
+ jsonrpc: '2.0',
101
+ id: 4,
102
+ method: 'prompts/list'
103
+ });
104
+ if (promptsResponse.result?.prompts) {
105
+ for (const prompt of promptsResponse.result.prompts) {
106
+ const promptCheck = this.analyzePrompt(prompt);
107
+ if (promptCheck)
108
+ results.push(promptCheck);
109
+ }
110
+ }
111
+ }
112
+ catch (error) {
113
+ // Prompts might not be implemented
9
114
  }
10
115
  return results;
11
116
  }
12
- async checkRCE(url) {
117
+ async sendMCPRequest(url, body) {
13
118
  const controller = new AbortController();
14
- const timeoutId = setTimeout(() => controller.abort(), 5000);
119
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
15
120
  try {
16
121
  const response = await fetch(url, {
17
- signal: controller.signal,
18
- timeout: 5000
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/json',
125
+ },
126
+ body: JSON.stringify(body),
127
+ signal: controller.signal
19
128
  });
20
- // Anzeichen fΓΌr RCE-Schwachstellen
21
- const headers = Object.fromEntries(response.headers.entries());
22
- // VerdΓ€chtige Header-Anzeichen
23
- if (headers['x-php-version'] || headers['server']?.includes('nginx')) {
129
+ const data = await response.json();
130
+ return data;
131
+ }
132
+ finally {
133
+ clearTimeout(timeoutId);
134
+ }
135
+ }
136
+ async checkAuthBypass(url) {
137
+ // Try to access without authentication
138
+ try {
139
+ const response = await this.sendMCPRequest(url, {
140
+ jsonrpc: '2.0',
141
+ id: 'auth-check',
142
+ method: 'initialize',
143
+ params: {
144
+ protocolVersion: '2024-11-05',
145
+ clientInfo: { name: 'unauthenticated', version: '1.0.0' },
146
+ capabilities: {}
147
+ }
148
+ });
149
+ if (response.result && !response.error) {
24
150
  return {
25
- type: 'potential_rce',
26
- severity: 'medium',
27
- description: 'VerdΓ€chtige Server-Header kΓΆnnten auf RCE-Angriffe hinweisen',
28
- evidence: headers
151
+ type: 'AUTH_BYPASS',
152
+ severity: 'high',
153
+ description: 'MCP server accepts unauthenticated connections',
154
+ recommendation: 'Implement authentication on MCP endpoints'
29
155
  };
30
156
  }
31
- return null;
32
157
  }
33
158
  catch (error) {
159
+ // Server requires auth - good
160
+ }
161
+ return null;
162
+ }
163
+ analyzeTool(tool) {
164
+ const dangerousPatterns = [
165
+ { pattern: /exec|eval|run|execute/i, severity: 'critical', type: 'COMMAND_INJECTION_RISK' },
166
+ { pattern: /file|read|write|delete/i, severity: 'high', type: 'FILE_ACCESS_RISK' },
167
+ { pattern: /shell|bash|cmd|powershell/i, severity: 'critical', type: 'SHELL_COMMAND_RISK' },
168
+ { pattern: /sql|database|query/i, severity: 'high', type: 'SQL_INJECTION_RISK' },
169
+ { pattern: /http|fetch|request/i, severity: 'medium', type: 'SSRF_RISK' },
170
+ ];
171
+ const toolName = tool.name || '';
172
+ const toolDesc = tool.description || '';
173
+ const inputSchema = JSON.stringify(tool.inputSchema || {});
174
+ for (const { pattern, severity, type } of dangerousPatterns) {
175
+ if (pattern.test(toolName) || pattern.test(toolDesc) || pattern.test(inputSchema)) {
176
+ return {
177
+ type,
178
+ severity: severity,
179
+ description: `Tool '${toolName}' may allow ${type.replace(/_/g, ' ').toLowerCase()}`,
180
+ evidence: { toolName, toolDesc, inputSchema: tool.inputSchema },
181
+ recommendation: `Review tool '${toolName}' for proper input validation and access controls`
182
+ };
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ checkPathTraversal(resource) {
188
+ const uri = resource.uri || '';
189
+ // Check for path traversal patterns
190
+ if (uri.includes('..') || uri.includes('~') || uri.includes('/etc/') || uri.includes('/var/')) {
34
191
  return {
35
- type: 'scan_error',
36
- severity: 'low',
37
- description: `Scan-Fehler: ${error.message}`,
38
- url: url
192
+ type: 'PATH_TRAVERSAL',
193
+ severity: 'high',
194
+ description: `Resource URI may be vulnerable to path traversal: ${uri}`,
195
+ recommendation: 'Validate and sanitize resource URIs'
39
196
  };
40
197
  }
41
- finally {
42
- clearTimeout(timeoutId);
198
+ return null;
199
+ }
200
+ analyzePrompt(prompt) {
201
+ const name = prompt.name || '';
202
+ const description = prompt.description || '';
203
+ // Check for prompt injection risks
204
+ if (description.toLowerCase().includes('user input') ||
205
+ description.toLowerCase().includes('dynamic')) {
206
+ return {
207
+ type: 'PROMPT_INJECTION_RISK',
208
+ severity: 'medium',
209
+ description: `Prompt '${name}' accepts dynamic input`,
210
+ recommendation: 'Implement prompt injection guards'
211
+ };
43
212
  }
213
+ return null;
44
214
  }
45
215
  }
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "@zcode-apps/mcp-sentinel",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
+ "main": "dist/scanner.js",
6
+ "types": "dist/scanner.d.ts",
5
7
  "bin": {
6
8
  "mcp-sentinel": "./dist/cli.js"
7
9
  },
10
+ "description": "Security scanner for MCP (Model Context Protocol) servers",
11
+ "keywords": ["mcp", "security", "scanner", "vulnerability", "rce", "model-context-protocol"],
12
+ "author": "ARC",
13
+ "license": "MIT",
8
14
  "scripts": {
9
- "build": "tsc"
15
+ "build": "tsc",
16
+ "start": "node dist/cli.js"
10
17
  },
11
18
  "dependencies": {
12
19
  "chalk": "^5.3.0",
package/src/cli.ts CHANGED
@@ -1,19 +1,86 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { MCPSentinel } from './scanner.js';
3
+ import { MCPSentinel, Vulnerability } from './scanner.js';
4
4
 
5
5
  const program = new Command();
6
+
6
7
  program
7
8
  .name('mcp-sentinel')
8
- .description('MCP Security Scanner - Scans for vulnerabilities');
9
+ .description('MCP Security Scanner - Detects vulnerabilities in MCP servers')
10
+ .version('0.2.1');
9
11
 
10
12
  program
11
13
  .command('scan <url>')
12
- .description('Scan a URL for vulnerabilities')
13
- .action(async (url) => {
14
+ .description('Scan an MCP server endpoint for security vulnerabilities')
15
+ .option('-j, --json', 'Output as JSON', false)
16
+ .option('-v, --verbose', 'Show detailed evidence', false)
17
+ .action(async (url, options) => {
14
18
  const scanner = new MCPSentinel();
19
+
20
+ if (!options.json) {
21
+ console.log(`\nπŸ” MCP Sentinel - Security Scanner\n`);
22
+ console.log(`Target: ${url}\n`);
23
+ console.log('─'.repeat(50));
24
+ }
25
+
15
26
  const results = await scanner.scan(url);
16
- console.log(JSON.stringify(results, null, 2));
27
+
28
+ if (options.json) {
29
+ console.log(JSON.stringify(results, null, 2));
30
+ return;
31
+ }
32
+
33
+ // Text output
34
+ if (results.length === 0) {
35
+ console.log('βœ… No vulnerabilities found.\n');
36
+ return;
37
+ }
38
+
39
+ // Group by severity
40
+ const critical = results.filter(r => r.severity === 'critical');
41
+ const high = results.filter(r => r.severity === 'high');
42
+ const medium = results.filter(r => r.severity === 'medium');
43
+ const low = results.filter(r => r.severity === 'low');
44
+
45
+ if (critical.length > 0) {
46
+ console.log('\nπŸ”΄ CRITICAL VULNERABILITIES:');
47
+ critical.forEach(v => printVulnerability(v, options.verbose || false));
48
+ }
49
+
50
+ if (high.length > 0) {
51
+ console.log('\n🟠 HIGH SEVERITY:');
52
+ high.forEach(v => printVulnerability(v, options.verbose || false));
53
+ }
54
+
55
+ if (medium.length > 0) {
56
+ console.log('\n🟑 MEDIUM SEVERITY:');
57
+ medium.forEach(v => printVulnerability(v, options.verbose || false));
58
+ }
59
+
60
+ if (low.length > 0) {
61
+ console.log('\nπŸ”΅ INFO:');
62
+ low.forEach(v => printVulnerability(v, options.verbose || false));
63
+ }
64
+
65
+ // Summary
66
+ console.log('\n' + '─'.repeat(50));
67
+ console.log('πŸ“Š SUMMARY:');
68
+ console.log(` Critical: ${critical.length}`);
69
+ console.log(` High: ${high.length}`);
70
+ console.log(` Medium: ${medium.length}`);
71
+ console.log(` Info: ${low.length}`);
72
+ console.log('');
17
73
  });
18
74
 
19
- program.parse();
75
+ function printVulnerability(v: Vulnerability, verbose: boolean): void {
76
+ console.log(`\n [${v.type}]`);
77
+ console.log(` ${v.description}`);
78
+ if (verbose && v.evidence) {
79
+ console.log(` Evidence: ${JSON.stringify(v.evidence, null, 2).split('\n').join('\n ')}`);
80
+ }
81
+ if (v.recommendation) {
82
+ console.log(` πŸ’‘ Recommendation: ${v.recommendation}`);
83
+ }
84
+ }
85
+
86
+ program.parse();
package/src/scanner.ts CHANGED
@@ -1,51 +1,243 @@
1
1
  import fetch, { RequestInit, Response } from 'node-fetch';
2
2
 
3
+ export interface Vulnerability {
4
+ type: string;
5
+ severity: 'critical' | 'high' | 'medium' | 'low';
6
+ description: string;
7
+ evidence?: any;
8
+ recommendation?: string;
9
+ }
10
+
3
11
  export class MCPSentinel {
4
- async scan(url: string): Promise<any[]> {
5
- const results = [];
12
+ async scan(url: string): Promise<Vulnerability[]> {
13
+ const results: Vulnerability[] = [];
14
+
15
+ console.error(`[MCP Sentinel] Scanning ${url}...`);
6
16
 
7
- // RCE Detection Check
8
- const rceResult = await this.checkRCE(url);
9
- if (rceResult) {
10
- results.push(rceResult);
17
+ // 1. Check if MCP endpoint is accessible
18
+ const baseUrl = url.replace(/\/$/, '');
19
+
20
+ // 2. Try MCP Initialize handshake
21
+ let serverInfo: any = null;
22
+ try {
23
+ const initResponse = await this.sendMCPRequest(baseUrl, {
24
+ jsonrpc: '2.0',
25
+ id: 1,
26
+ method: 'initialize',
27
+ params: {
28
+ protocolVersion: '2024-11-05',
29
+ clientInfo: {
30
+ name: 'mcp-sentinel',
31
+ version: '0.1.0'
32
+ },
33
+ capabilities: {}
34
+ }
35
+ });
36
+
37
+ if (initResponse.result) {
38
+ serverInfo = initResponse.result;
39
+ results.push({
40
+ type: 'INFO',
41
+ severity: 'low',
42
+ description: `MCP Server detected: ${serverInfo.serverInfo?.name || 'Unknown'} v${serverInfo.serverInfo?.version || 'Unknown'}`,
43
+ evidence: serverInfo
44
+ });
45
+ }
46
+ } catch (error: any) {
47
+ results.push({
48
+ type: 'MCP_PROTOCOL_ERROR',
49
+ severity: 'medium',
50
+ description: `MCP endpoint not responding properly: ${error.message}`,
51
+ recommendation: 'Verify the URL is a valid MCP server endpoint'
52
+ });
53
+ return results;
54
+ }
55
+
56
+ // 3. Check for authentication bypass
57
+ const authResult = await this.checkAuthBypass(baseUrl);
58
+ if (authResult) results.push(authResult);
59
+
60
+ // 4. List and analyze tools
61
+ try {
62
+ const toolsResponse = await this.sendMCPRequest(baseUrl, {
63
+ jsonrpc: '2.0',
64
+ id: 2,
65
+ method: 'tools/list'
66
+ });
67
+
68
+ if (toolsResponse.result?.tools) {
69
+ const tools = toolsResponse.result.tools;
70
+ results.push({
71
+ type: 'INFO',
72
+ severity: 'low',
73
+ description: `Found ${tools.length} exposed tools`,
74
+ evidence: tools.map((t: any) => t.name)
75
+ });
76
+
77
+ // Check for dangerous tools
78
+ for (const tool of tools) {
79
+ const toolCheck = this.analyzeTool(tool);
80
+ if (toolCheck) results.push(toolCheck);
81
+ }
82
+ }
83
+ } catch (error: any) {
84
+ results.push({
85
+ type: 'TOOLS_ACCESS_ERROR',
86
+ severity: 'medium',
87
+ description: `Could not enumerate tools: ${error.message}`
88
+ });
89
+ }
90
+
91
+ // 5. Check for resource exposure
92
+ try {
93
+ const resourcesResponse = await this.sendMCPRequest(baseUrl, {
94
+ jsonrpc: '2.0',
95
+ id: 3,
96
+ method: 'resources/list'
97
+ });
98
+
99
+ if (resourcesResponse.result?.resources) {
100
+ const resources = resourcesResponse.result.resources;
101
+
102
+ // Check for path traversal
103
+ for (const resource of resources) {
104
+ const pathCheck = this.checkPathTraversal(resource);
105
+ if (pathCheck) results.push(pathCheck);
106
+ }
107
+ }
108
+ } catch (error: any) {
109
+ // Resources might not be implemented
110
+ }
111
+
112
+ // 6. Check for prompt injection vulnerabilities
113
+ try {
114
+ const promptsResponse = await this.sendMCPRequest(baseUrl, {
115
+ jsonrpc: '2.0',
116
+ id: 4,
117
+ method: 'prompts/list'
118
+ });
119
+
120
+ if (promptsResponse.result?.prompts) {
121
+ for (const prompt of promptsResponse.result.prompts) {
122
+ const promptCheck = this.analyzePrompt(prompt);
123
+ if (promptCheck) results.push(promptCheck);
124
+ }
125
+ }
126
+ } catch (error: any) {
127
+ // Prompts might not be implemented
11
128
  }
12
129
 
13
130
  return results;
14
131
  }
15
132
 
16
- private async checkRCE(url: string): Promise<any | null> {
133
+ private async sendMCPRequest(url: string, body: any): Promise<any> {
17
134
  const controller = new AbortController();
18
- const timeoutId = setTimeout(() => controller.abort(), 5000);
135
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
19
136
 
20
137
  try {
21
- const response: Response = await fetch(url, {
22
- signal: controller.signal,
23
- timeout: 5000
138
+ const response = await fetch(url, {
139
+ method: 'POST',
140
+ headers: {
141
+ 'Content-Type': 'application/json',
142
+ },
143
+ body: JSON.stringify(body),
144
+ signal: controller.signal
24
145
  } as RequestInit);
25
146
 
26
- // Anzeichen fΓΌr RCE-Schwachstellen
27
- const headers = Object.fromEntries(response.headers.entries());
28
-
29
- // VerdΓ€chtige Header-Anzeichen
30
- if (headers['x-php-version'] || headers['server']?.includes('nginx')) {
147
+ const data = await response.json();
148
+ return data;
149
+ } finally {
150
+ clearTimeout(timeoutId);
151
+ }
152
+ }
153
+
154
+ private async checkAuthBypass(url: string): Promise<Vulnerability | null> {
155
+ // Try to access without authentication
156
+ try {
157
+ const response = await this.sendMCPRequest(url, {
158
+ jsonrpc: '2.0',
159
+ id: 'auth-check',
160
+ method: 'initialize',
161
+ params: {
162
+ protocolVersion: '2024-11-05',
163
+ clientInfo: { name: 'unauthenticated', version: '1.0.0' },
164
+ capabilities: {}
165
+ }
166
+ });
167
+
168
+ if (response.result && !response.error) {
31
169
  return {
32
- type: 'potential_rce',
33
- severity: 'medium',
34
- description: 'VerdΓ€chtige Server-Header kΓΆnnten auf RCE-Angriffe hinweisen',
35
- evidence: headers
170
+ type: 'AUTH_BYPASS',
171
+ severity: 'high',
172
+ description: 'MCP server accepts unauthenticated connections',
173
+ recommendation: 'Implement authentication on MCP endpoints'
36
174
  };
37
175
  }
176
+ } catch (error) {
177
+ // Server requires auth - good
178
+ }
179
+ return null;
180
+ }
38
181
 
39
- return null;
40
- } catch (error: any) {
182
+ private analyzeTool(tool: any): Vulnerability | null {
183
+ const dangerousPatterns = [
184
+ { pattern: /exec|eval|run|execute/i, severity: 'critical', type: 'COMMAND_INJECTION_RISK' },
185
+ { pattern: /file|read|write|delete/i, severity: 'high', type: 'FILE_ACCESS_RISK' },
186
+ { pattern: /shell|bash|cmd|powershell/i, severity: 'critical', type: 'SHELL_COMMAND_RISK' },
187
+ { pattern: /sql|database|query/i, severity: 'high', type: 'SQL_INJECTION_RISK' },
188
+ { pattern: /http|fetch|request/i, severity: 'medium', type: 'SSRF_RISK' },
189
+ ];
190
+
191
+ const toolName = tool.name || '';
192
+ const toolDesc = tool.description || '';
193
+ const inputSchema = JSON.stringify(tool.inputSchema || {});
194
+
195
+ for (const { pattern, severity, type } of dangerousPatterns) {
196
+ if (pattern.test(toolName) || pattern.test(toolDesc) || pattern.test(inputSchema)) {
197
+ return {
198
+ type,
199
+ severity: severity as any,
200
+ description: `Tool '${toolName}' may allow ${type.replace(/_/g, ' ').toLowerCase()}`,
201
+ evidence: { toolName, toolDesc, inputSchema: tool.inputSchema },
202
+ recommendation: `Review tool '${toolName}' for proper input validation and access controls`
203
+ };
204
+ }
205
+ }
206
+
207
+ return null;
208
+ }
209
+
210
+ private checkPathTraversal(resource: any): Vulnerability | null {
211
+ const uri = resource.uri || '';
212
+
213
+ // Check for path traversal patterns
214
+ if (uri.includes('..') || uri.includes('~') || uri.includes('/etc/') || uri.includes('/var/')) {
41
215
  return {
42
- type: 'scan_error',
43
- severity: 'low',
44
- description: `Scan-Fehler: ${error.message}`,
45
- url: url
216
+ type: 'PATH_TRAVERSAL',
217
+ severity: 'high',
218
+ description: `Resource URI may be vulnerable to path traversal: ${uri}`,
219
+ recommendation: 'Validate and sanitize resource URIs'
46
220
  };
47
- } finally {
48
- clearTimeout(timeoutId);
49
221
  }
222
+
223
+ return null;
50
224
  }
51
- }
225
+
226
+ private analyzePrompt(prompt: any): Vulnerability | null {
227
+ const name = prompt.name || '';
228
+ const description = prompt.description || '';
229
+
230
+ // Check for prompt injection risks
231
+ if (description.toLowerCase().includes('user input') ||
232
+ description.toLowerCase().includes('dynamic')) {
233
+ return {
234
+ type: 'PROMPT_INJECTION_RISK',
235
+ severity: 'medium',
236
+ description: `Prompt '${name}' accepts dynamic input`,
237
+ recommendation: 'Implement prompt injection guards'
238
+ };
239
+ }
240
+
241
+ return null;
242
+ }
243
+ }