@weave_protocol/mund 0.1.10 → 0.1.12

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.
@@ -5,9 +5,11 @@
5
5
 
6
6
  export { registerMonitoringTools } from './monitoring.js';
7
7
  export { registerConfigurationTools } from './configuration.js';
8
+ export { registerMcpScanningTools } from './mcp-scanning.js';
8
9
 
9
10
  import { registerMonitoringTools } from './monitoring.js';
10
11
  import { registerConfigurationTools } from './configuration.js';
12
+ import { registerMcpScanningTools } from './mcp-scanning.js';
11
13
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
14
  import type { IStorage, DetectionRule } from '../types.js';
13
15
  import { AnalyzerEngine } from '../analyzers/index.js';
@@ -24,6 +26,12 @@ export function registerAllTools(
24
26
  notificationHub: NotificationHub,
25
27
  blockMode: boolean
26
28
  ): void {
29
+ // Core monitoring tools
27
30
  registerMonitoringTools(server, storage, rules, analyzer, notificationHub, blockMode);
31
+
32
+ // Configuration management tools
28
33
  registerConfigurationTools(server, storage, rules);
34
+
35
+ // MCP server scanning tools (new)
36
+ registerMcpScanningTools(server);
29
37
  }
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Mund - The Guardian Protocol
3
+ * MCP Scanning Tools - Tools for scanning MCP servers before installation
4
+ *
5
+ * Tools:
6
+ * - mund_scan_mcp_server: Full security scan of a server manifest
7
+ * - mund_check_typosquatting: Check if a name is typosquatting a known server
8
+ * - mund_audit_mcp_permissions: Audit the permission scope of server tools
9
+ */
10
+
11
+ import { z } from 'zod';
12
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
+ import {
14
+ McpServerAnalyzer,
15
+ type McpServerManifest,
16
+ type McpCapabilities,
17
+ type McpScanResult
18
+ } from '../analyzers/mcp-server-analyzer.js';
19
+ import type { SecurityIssue } from '../types.js';
20
+
21
+ // ============================================================================
22
+ // Tool Schemas
23
+ // ============================================================================
24
+
25
+ const ScanMcpServerSchema = z.object({
26
+ manifest: z.string().describe('JSON content of server.json manifest to scan'),
27
+ source: z.string().optional().describe('Source URL, registry name, or file path for context')
28
+ });
29
+
30
+ const CheckTyposquattingSchema = z.object({
31
+ name: z.string().describe('MCP server name to check for typosquatting')
32
+ });
33
+
34
+ const AuditPermissionsSchema = z.object({
35
+ manifest: z.string().describe('JSON content of server.json manifest to audit')
36
+ });
37
+
38
+ // ============================================================================
39
+ // Tool Registration
40
+ // ============================================================================
41
+
42
+ /**
43
+ * Register all MCP scanning tools
44
+ */
45
+ export function registerMcpScanningTools(server: McpServer): void {
46
+ const analyzer = new McpServerAnalyzer();
47
+
48
+ // -------------------------------------------------------------------------
49
+ // Tool: mund_scan_mcp_server
50
+ // -------------------------------------------------------------------------
51
+ server.tool(
52
+ 'mund_scan_mcp_server',
53
+ 'Scan an MCP server manifest (server.json) for security issues before installation. Detects prompt injection in tool descriptions, typosquatting, embedded secrets, and dangerous permissions.',
54
+ ScanMcpServerSchema.shape,
55
+ async ({ manifest, source }): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
56
+ try {
57
+ // Parse manifest
58
+ const parsed = McpServerAnalyzer.parseManifest(manifest);
59
+ if (!parsed) {
60
+ return {
61
+ content: [{
62
+ type: 'text',
63
+ text: JSON.stringify({
64
+ error: 'Invalid manifest',
65
+ message: 'Could not parse the provided content as a valid MCP server manifest. Ensure it is valid JSON with name, tools, or resources fields.',
66
+ recommendation: 'CANNOT_ANALYZE'
67
+ }, null, 2)
68
+ }]
69
+ };
70
+ }
71
+
72
+ // Run full analysis
73
+ const issues = await analyzer.analyze(manifest, []);
74
+
75
+ // Analyze capabilities
76
+ const capabilities = McpServerAnalyzer.analyzeCapabilities(parsed);
77
+ const riskLevel = McpServerAnalyzer.getRiskLevel(capabilities);
78
+
79
+ // Count by severity
80
+ const bySeverity = {
81
+ critical: issues.filter(i => i.severity === 'critical').length,
82
+ high: issues.filter(i => i.severity === 'high').length,
83
+ medium: issues.filter(i => i.severity === 'medium').length,
84
+ low: issues.filter(i => i.severity === 'low').length,
85
+ info: issues.filter(i => i.severity === 'info').length
86
+ };
87
+
88
+ // Determine recommendation
89
+ let recommendation: McpScanResult['recommendation'];
90
+ if (bySeverity.critical > 0) {
91
+ recommendation = 'DO_NOT_INSTALL';
92
+ } else if (bySeverity.high > 0) {
93
+ recommendation = 'REVIEW_CAREFULLY';
94
+ } else if (bySeverity.medium > 0 || riskLevel === 'HIGH') {
95
+ recommendation = 'CAUTION';
96
+ } else {
97
+ recommendation = 'APPEARS_SAFE';
98
+ }
99
+
100
+ const result: McpScanResult = {
101
+ server_name: parsed.name || 'unknown',
102
+ version: parsed.version || 'unknown',
103
+ scanned_at: new Date().toISOString(),
104
+ source,
105
+ issue_count: issues.length,
106
+ by_severity: bySeverity,
107
+ recommendation,
108
+ capabilities,
109
+ issues: issues.map(formatIssue)
110
+ };
111
+
112
+ return {
113
+ content: [{
114
+ type: 'text',
115
+ text: JSON.stringify(result, null, 2)
116
+ }]
117
+ };
118
+ } catch (error) {
119
+ return {
120
+ content: [{
121
+ type: 'text',
122
+ text: JSON.stringify({
123
+ error: 'Scan failed',
124
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
125
+ recommendation: 'CANNOT_ANALYZE'
126
+ }, null, 2)
127
+ }]
128
+ };
129
+ }
130
+ }
131
+ );
132
+
133
+ // -------------------------------------------------------------------------
134
+ // Tool: mund_check_typosquatting
135
+ // -------------------------------------------------------------------------
136
+ server.tool(
137
+ 'mund_check_typosquatting',
138
+ 'Check if an MCP server name is potentially typosquatting a known legitimate server. Compares against a list of official and common MCP servers.',
139
+ CheckTyposquattingSchema.shape,
140
+ async ({ name }): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
141
+ try {
142
+ // Create a minimal manifest for the check
143
+ const fakeManifest: McpServerManifest = { name, version: '0.0.0' };
144
+ const issues = analyzer.checkTyposquatting(fakeManifest);
145
+
146
+ const similarTo = issues.map(i => {
147
+ // Extract the legitimate name from the match string
148
+ const match = i.match?.match(/"([^"]+)"$/);
149
+ return match ? match[1] : i.match;
150
+ });
151
+
152
+ const result = {
153
+ name,
154
+ checked_at: new Date().toISOString(),
155
+ is_suspicious: issues.length > 0,
156
+ risk_level: issues.length > 0 ? 'HIGH' : 'NONE',
157
+ similar_to: similarTo,
158
+ details: issues.map(i => ({
159
+ pattern: i.rule_name,
160
+ match: i.match,
161
+ suggestion: i.suggestion
162
+ })),
163
+ recommendation: issues.length > 0
164
+ ? 'This name is suspiciously similar to a known legitimate server. Verify you have the correct server from a trusted source before installing.'
165
+ : 'No typosquatting patterns detected. This does not guarantee the server is safe - always verify the source.'
166
+ };
167
+
168
+ return {
169
+ content: [{
170
+ type: 'text',
171
+ text: JSON.stringify(result, null, 2)
172
+ }]
173
+ };
174
+ } catch (error) {
175
+ return {
176
+ content: [{
177
+ type: 'text',
178
+ text: JSON.stringify({
179
+ error: 'Check failed',
180
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
181
+ }, null, 2)
182
+ }]
183
+ };
184
+ }
185
+ }
186
+ );
187
+
188
+ // -------------------------------------------------------------------------
189
+ // Tool: mund_audit_mcp_permissions
190
+ // -------------------------------------------------------------------------
191
+ server.tool(
192
+ 'mund_audit_mcp_permissions',
193
+ 'Analyze the permission scope and capabilities of an MCP server\'s tools. Identifies network access, filesystem operations, command execution, and other potentially dangerous capabilities.',
194
+ AuditPermissionsSchema.shape,
195
+ async ({ manifest }): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
196
+ try {
197
+ // Parse manifest
198
+ const parsed = McpServerAnalyzer.parseManifest(manifest);
199
+ if (!parsed) {
200
+ return {
201
+ content: [{
202
+ type: 'text',
203
+ text: JSON.stringify({
204
+ error: 'Invalid manifest',
205
+ message: 'Could not parse as MCP server manifest'
206
+ }, null, 2)
207
+ }]
208
+ };
209
+ }
210
+
211
+ // Get capabilities
212
+ const capabilities = McpServerAnalyzer.analyzeCapabilities(parsed);
213
+ const riskLevel = McpServerAnalyzer.getRiskLevel(capabilities);
214
+
215
+ // Get permission-related issues
216
+ const allIssues = await analyzer.analyze(manifest, []);
217
+ const permissionIssues = allIssues.filter(i =>
218
+ i.rule_id.startsWith('mcp_capability_') ||
219
+ i.rule_id === 'mcp_execution_warning' ||
220
+ i.rule_id === 'mcp_excessive_tools'
221
+ );
222
+
223
+ // Analyze each tool
224
+ const toolAnalysis = (parsed.tools || []).map(tool => {
225
+ const combined = `${tool.name} ${tool.description || ''}`.toLowerCase();
226
+ const permissions: string[] = [];
227
+
228
+ if (/\b(fetch|request|http|https|curl|wget|axios|socket|websocket)\b/i.test(combined)) {
229
+ permissions.push('network');
230
+ }
231
+ if (/\b(file|read|write|path|directory|fs|fopen)\b/i.test(combined)) {
232
+ permissions.push('filesystem');
233
+ }
234
+ if (/\b(exec|execute|shell|command|bash|spawn|eval)\b/i.test(combined)) {
235
+ permissions.push('execution');
236
+ }
237
+ if (/\b(env|environment|process\.env|secret|credential)\b/i.test(combined)) {
238
+ permissions.push('environment');
239
+ }
240
+ if (/\b(sql|query|database|db|postgres|mysql|mongo|redis)\b/i.test(combined)) {
241
+ permissions.push('database');
242
+ }
243
+ if (/\b(crypto|encrypt|decrypt|hash|sign)\b/i.test(combined)) {
244
+ permissions.push('crypto');
245
+ }
246
+
247
+ return {
248
+ name: tool.name,
249
+ description: truncate(tool.description || '', 100),
250
+ detected_permissions: permissions,
251
+ risk: permissions.includes('execution') ? 'HIGH' :
252
+ permissions.length > 1 ? 'MEDIUM' :
253
+ permissions.length > 0 ? 'LOW' : 'NONE'
254
+ };
255
+ });
256
+
257
+ const result = {
258
+ server_name: parsed.name || 'unknown',
259
+ version: parsed.version || 'unknown',
260
+ audited_at: new Date().toISOString(),
261
+ tool_count: parsed.tools?.length || 0,
262
+ resource_count: parsed.resources?.length || 0,
263
+ prompt_count: parsed.prompts?.length || 0,
264
+ overall_risk_level: riskLevel,
265
+ capabilities,
266
+ capability_summary: formatCapabilitySummary(capabilities),
267
+ tools: toolAnalysis,
268
+ permission_issues: permissionIssues.map(formatIssue),
269
+ recommendations: generatePermissionRecommendations(capabilities, parsed.tools?.length || 0)
270
+ };
271
+
272
+ return {
273
+ content: [{
274
+ type: 'text',
275
+ text: JSON.stringify(result, null, 2)
276
+ }]
277
+ };
278
+ } catch (error) {
279
+ return {
280
+ content: [{
281
+ type: 'text',
282
+ text: JSON.stringify({
283
+ error: 'Audit failed',
284
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
285
+ }, null, 2)
286
+ }]
287
+ };
288
+ }
289
+ }
290
+ );
291
+ }
292
+
293
+ // ============================================================================
294
+ // Helper Functions
295
+ // ============================================================================
296
+
297
+ /**
298
+ * Format a security issue for JSON output
299
+ */
300
+ function formatIssue(issue: SecurityIssue): SecurityIssue {
301
+ return {
302
+ rule_id: issue.rule_id,
303
+ rule_name: issue.rule_name,
304
+ type: issue.type,
305
+ severity: issue.severity,
306
+ action: issue.action,
307
+ match: issue.match,
308
+ suggestion: issue.suggestion,
309
+ location: issue.location
310
+ };
311
+ }
312
+
313
+ /**
314
+ * Truncate a string
315
+ */
316
+ function truncate(str: string, maxLength: number): string {
317
+ if (str.length <= maxLength) return str;
318
+ return str.substring(0, maxLength) + '...';
319
+ }
320
+
321
+ /**
322
+ * Format capabilities into a human-readable summary
323
+ */
324
+ function formatCapabilitySummary(capabilities: McpCapabilities): string[] {
325
+ const summary: string[] = [];
326
+
327
+ if (capabilities.execution) {
328
+ summary.push('⚠️ Can execute commands/code on your system');
329
+ }
330
+ if (capabilities.filesystem) {
331
+ summary.push('📁 Can read/write files');
332
+ }
333
+ if (capabilities.network) {
334
+ summary.push('🌐 Can make network requests');
335
+ }
336
+ if (capabilities.environment) {
337
+ summary.push('🔑 Can access environment variables/secrets');
338
+ }
339
+ if (capabilities.database) {
340
+ summary.push('🗄️ Can access databases');
341
+ }
342
+ if (capabilities.crypto) {
343
+ summary.push('🔐 Uses cryptographic operations');
344
+ }
345
+
346
+ if (summary.length === 0) {
347
+ summary.push('✅ No dangerous capabilities detected');
348
+ }
349
+
350
+ return summary;
351
+ }
352
+
353
+ /**
354
+ * Generate permission-based recommendations
355
+ */
356
+ function generatePermissionRecommendations(capabilities: McpCapabilities, toolCount: number): string[] {
357
+ const recommendations: string[] = [];
358
+
359
+ if (capabilities.execution) {
360
+ recommendations.push('CRITICAL: This server can execute arbitrary commands. Only install if you fully trust the source and understand what commands it will run.');
361
+ }
362
+
363
+ if (capabilities.filesystem && capabilities.network) {
364
+ recommendations.push('HIGH RISK: This server can both access files and make network requests. This combination could be used to exfiltrate data.');
365
+ }
366
+
367
+ if (capabilities.environment) {
368
+ recommendations.push('This server can access environment variables, which may include API keys and secrets. Ensure you trust the source.');
369
+ }
370
+
371
+ if (toolCount > 20) {
372
+ recommendations.push(`This server has ${toolCount} tools, which is a large attack surface. Consider whether you need all of them.`);
373
+ }
374
+
375
+ if (recommendations.length === 0) {
376
+ recommendations.push('This server has a relatively low-risk permission profile, but always verify the source before installing.');
377
+ }
378
+
379
+ return recommendations;
380
+ }
381
+
382
+ export default registerMcpScanningTools;