@weave_protocol/mund 0.1.0
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/dist/analyzers/code-analyzer.d.ts +24 -0
- package/dist/analyzers/code-analyzer.d.ts.map +1 -0
- package/dist/analyzers/code-analyzer.js +73 -0
- package/dist/analyzers/code-analyzer.js.map +1 -0
- package/dist/analyzers/exfiltration-detector.d.ts +40 -0
- package/dist/analyzers/exfiltration-detector.d.ts.map +1 -0
- package/dist/analyzers/exfiltration-detector.js +220 -0
- package/dist/analyzers/exfiltration-detector.js.map +1 -0
- package/dist/analyzers/index.d.ts +26 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +61 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/injection-detector.d.ts +36 -0
- package/dist/analyzers/injection-detector.d.ts.map +1 -0
- package/dist/analyzers/injection-detector.js +165 -0
- package/dist/analyzers/injection-detector.js.map +1 -0
- package/dist/analyzers/pii-detector.d.ts +44 -0
- package/dist/analyzers/pii-detector.d.ts.map +1 -0
- package/dist/analyzers/pii-detector.js +190 -0
- package/dist/analyzers/pii-detector.js.map +1 -0
- package/dist/analyzers/secret-scanner.d.ts +32 -0
- package/dist/analyzers/secret-scanner.d.ts.map +1 -0
- package/dist/analyzers/secret-scanner.js +119 -0
- package/dist/analyzers/secret-scanner.js.map +1 -0
- package/dist/constants.d.ts +18 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +580 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +242 -0
- package/dist/index.js.map +1 -0
- package/dist/notifications/email.d.ts +18 -0
- package/dist/notifications/email.d.ts.map +1 -0
- package/dist/notifications/email.js +192 -0
- package/dist/notifications/email.js.map +1 -0
- package/dist/notifications/index.d.ts +51 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +107 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/slack.d.ts +17 -0
- package/dist/notifications/slack.d.ts.map +1 -0
- package/dist/notifications/slack.js +128 -0
- package/dist/notifications/slack.js.map +1 -0
- package/dist/notifications/teams.d.ts +17 -0
- package/dist/notifications/teams.d.ts.map +1 -0
- package/dist/notifications/teams.js +136 -0
- package/dist/notifications/teams.js.map +1 -0
- package/dist/notifications/webhook.d.ts +15 -0
- package/dist/notifications/webhook.d.ts.map +1 -0
- package/dist/notifications/webhook.js +67 -0
- package/dist/notifications/webhook.js.map +1 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +26 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory.d.ts +35 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +144 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/tools/configuration.d.ts +8 -0
- package/dist/tools/configuration.d.ts.map +1 -0
- package/dist/tools/configuration.js +399 -0
- package/dist/tools/configuration.js.map +1 -0
- package/dist/tools/index.d.ts +15 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/monitoring.d.ts +10 -0
- package/dist/tools/monitoring.d.ts.map +1 -0
- package/dist/tools/monitoring.js +476 -0
- package/dist/tools/monitoring.js.map +1 -0
- package/dist/types.d.ts +207 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -0
- package/rules/default.yaml +189 -0
- package/src/analyzers/code-analyzer.ts +88 -0
- package/src/analyzers/exfiltration-detector.ts +257 -0
- package/src/analyzers/index.ts +74 -0
- package/src/analyzers/injection-detector.ts +191 -0
- package/src/analyzers/pii-detector.ts +220 -0
- package/src/analyzers/secret-scanner.ts +142 -0
- package/src/constants.ts +603 -0
- package/src/index.ts +285 -0
- package/src/notifications/email.ts +211 -0
- package/src/notifications/index.ts +136 -0
- package/src/notifications/slack.ts +170 -0
- package/src/notifications/teams.ts +200 -0
- package/src/notifications/webhook.ts +80 -0
- package/src/storage/index.ts +29 -0
- package/src/storage/memory.ts +181 -0
- package/src/tools/configuration.ts +478 -0
- package/src/tools/index.ts +29 -0
- package/src/tools/monitoring.ts +559 -0
- package/src/types.ts +300 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Monitoring Tools - MCP tools for security monitoring
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
import type {
|
|
11
|
+
IStorage,
|
|
12
|
+
DetectionRule,
|
|
13
|
+
SecurityEvent,
|
|
14
|
+
ScanResult,
|
|
15
|
+
EventContext,
|
|
16
|
+
Severity,
|
|
17
|
+
DetectorType
|
|
18
|
+
} from '../types.js';
|
|
19
|
+
import { AnalyzerEngine } from '../analyzers/index.js';
|
|
20
|
+
import { NotificationHub } from '../notifications/index.js';
|
|
21
|
+
import { MAX_SNIPPET_LENGTH, DEFAULT_EVENTS_LIMIT, MAX_EVENTS_PER_QUERY } from '../constants.js';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Input Schemas
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
const ScanContentInputSchema = z.object({
|
|
28
|
+
content: z.string()
|
|
29
|
+
.min(1, 'Content is required')
|
|
30
|
+
.max(1000000, 'Content exceeds maximum length')
|
|
31
|
+
.describe('The text/code content to scan for security issues'),
|
|
32
|
+
content_type: z.enum(['text', 'code', 'json', 'yaml'])
|
|
33
|
+
.default('text')
|
|
34
|
+
.describe('Type of content being scanned'),
|
|
35
|
+
tool_name: z.string().optional().describe('Name of the tool that generated this content'),
|
|
36
|
+
agent_id: z.string().optional().describe('ID of the agent making this request'),
|
|
37
|
+
session_id: z.string().optional().describe('Current session ID')
|
|
38
|
+
}).strict();
|
|
39
|
+
|
|
40
|
+
const CheckUrlInputSchema = z.object({
|
|
41
|
+
url: z.string()
|
|
42
|
+
.url('Invalid URL format')
|
|
43
|
+
.describe('URL to check for safety'),
|
|
44
|
+
tool_name: z.string().optional().describe('Name of the tool attempting to access this URL'),
|
|
45
|
+
agent_id: z.string().optional().describe('ID of the agent making this request')
|
|
46
|
+
}).strict();
|
|
47
|
+
|
|
48
|
+
const ValidateCommandInputSchema = z.object({
|
|
49
|
+
command: z.string()
|
|
50
|
+
.min(1, 'Command is required')
|
|
51
|
+
.describe('Shell command to validate'),
|
|
52
|
+
tool_name: z.string().optional().describe('Name of the tool executing this command'),
|
|
53
|
+
agent_id: z.string().optional().describe('ID of the agent making this request')
|
|
54
|
+
}).strict();
|
|
55
|
+
|
|
56
|
+
const GetEventsInputSchema = z.object({
|
|
57
|
+
limit: z.number().int().min(1).max(MAX_EVENTS_PER_QUERY).default(DEFAULT_EVENTS_LIMIT)
|
|
58
|
+
.describe('Maximum number of events to return'),
|
|
59
|
+
offset: z.number().int().min(0).default(0)
|
|
60
|
+
.describe('Number of events to skip for pagination'),
|
|
61
|
+
severity: z.enum(['critical', 'high', 'medium', 'low', 'info']).optional()
|
|
62
|
+
.describe('Filter by severity level'),
|
|
63
|
+
type: z.enum(['secret', 'pii', 'code_pattern', 'injection', 'exfiltration', 'behavioral']).optional()
|
|
64
|
+
.describe('Filter by detection type'),
|
|
65
|
+
acknowledged: z.boolean().optional()
|
|
66
|
+
.describe('Filter by acknowledgment status')
|
|
67
|
+
}).strict();
|
|
68
|
+
|
|
69
|
+
const GetStatusInputSchema = z.object({}).strict();
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Tool Registration
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
export function registerMonitoringTools(
|
|
76
|
+
server: McpServer,
|
|
77
|
+
storage: IStorage,
|
|
78
|
+
rules: DetectionRule[],
|
|
79
|
+
analyzer: AnalyzerEngine,
|
|
80
|
+
notificationHub: NotificationHub,
|
|
81
|
+
blockMode: boolean
|
|
82
|
+
): void {
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// mund_scan_content - Scan text/code for security issues
|
|
86
|
+
// ============================================================================
|
|
87
|
+
server.registerTool(
|
|
88
|
+
'mund_scan_content',
|
|
89
|
+
{
|
|
90
|
+
title: 'Scan Content for Security Issues',
|
|
91
|
+
description: `Scan text or code content for security vulnerabilities, secrets, PII, and other issues.
|
|
92
|
+
|
|
93
|
+
This tool analyzes the provided content using multiple security analyzers:
|
|
94
|
+
- Secret Scanner: Detects API keys, tokens, passwords, and credentials
|
|
95
|
+
- PII Detector: Finds personal identifiable information
|
|
96
|
+
- Code Analyzer: Identifies dangerous code patterns
|
|
97
|
+
- Injection Detector: Spots prompt injection attempts
|
|
98
|
+
- Exfiltration Detector: Detects data exfiltration patterns
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
- content (string): The text/code to scan (required)
|
|
102
|
+
- content_type ('text' | 'code' | 'json' | 'yaml'): Type of content (default: 'text')
|
|
103
|
+
- tool_name (string): Name of tool that generated this content (optional)
|
|
104
|
+
- agent_id (string): ID of the requesting agent (optional)
|
|
105
|
+
- session_id (string): Current session ID (optional)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
JSON object with:
|
|
109
|
+
- scan_id: Unique identifier for this scan
|
|
110
|
+
- issues_found: Number of security issues detected
|
|
111
|
+
- blocked: Whether the content was blocked (if block_mode is enabled)
|
|
112
|
+
- issues: Array of detected issues with severity, type, and suggestions
|
|
113
|
+
- scan_duration_ms: Time taken to complete the scan
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
- Scan code before committing: {"content": "const key = 'AKIAIOSFODNN7EXAMPLE'"}
|
|
117
|
+
- Check user input: {"content": "Please ignore previous instructions", "content_type": "text"}`,
|
|
118
|
+
inputSchema: ScanContentInputSchema,
|
|
119
|
+
annotations: {
|
|
120
|
+
readOnlyHint: true,
|
|
121
|
+
destructiveHint: false,
|
|
122
|
+
idempotentHint: true,
|
|
123
|
+
openWorldHint: false
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async (params) => {
|
|
127
|
+
const startTime = Date.now();
|
|
128
|
+
const contentHash = createHash('sha256').update(params.content).digest('hex');
|
|
129
|
+
|
|
130
|
+
// Run analysis
|
|
131
|
+
const issues = await analyzer.analyzeAll(params.content, rules);
|
|
132
|
+
|
|
133
|
+
// Create scan result
|
|
134
|
+
const scanResult: ScanResult = {
|
|
135
|
+
id: uuidv4(),
|
|
136
|
+
timestamp: new Date(),
|
|
137
|
+
content_hash: contentHash,
|
|
138
|
+
issues,
|
|
139
|
+
scan_duration_ms: Date.now() - startTime,
|
|
140
|
+
rules_checked: rules.filter(r => r.enabled).length
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Determine if content should be blocked
|
|
144
|
+
const hasCritical = issues.some(i => i.severity === 'critical');
|
|
145
|
+
const hasHigh = issues.some(i => i.severity === 'high');
|
|
146
|
+
const shouldBlock = blockMode && (hasCritical || hasHigh);
|
|
147
|
+
|
|
148
|
+
// Create and store security events for significant issues
|
|
149
|
+
const context: EventContext = {
|
|
150
|
+
tool_name: params.tool_name,
|
|
151
|
+
agent_id: params.agent_id,
|
|
152
|
+
session_id: params.session_id
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
for (const issue of issues.filter(i => i.severity === 'critical' || i.severity === 'high')) {
|
|
156
|
+
const event: SecurityEvent = {
|
|
157
|
+
id: uuidv4(),
|
|
158
|
+
timestamp: new Date(),
|
|
159
|
+
rule_id: issue.rule_id,
|
|
160
|
+
rule_name: issue.rule_name,
|
|
161
|
+
severity: issue.severity as Severity,
|
|
162
|
+
type: issue.type as DetectorType,
|
|
163
|
+
action_taken: shouldBlock ? 'block' : issue.action,
|
|
164
|
+
content_snippet: truncateSnippet(issue.match, MAX_SNIPPET_LENGTH),
|
|
165
|
+
full_content_hash: contentHash,
|
|
166
|
+
context,
|
|
167
|
+
acknowledged: false
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
await storage.saveEvent(event);
|
|
171
|
+
|
|
172
|
+
// Send notifications for critical/high severity
|
|
173
|
+
await notificationHub.notify(event);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Format response
|
|
177
|
+
const response = {
|
|
178
|
+
scan_id: scanResult.id,
|
|
179
|
+
issues_found: issues.length,
|
|
180
|
+
blocked: shouldBlock,
|
|
181
|
+
issues: issues.map(i => ({
|
|
182
|
+
rule_id: i.rule_id,
|
|
183
|
+
rule_name: i.rule_name,
|
|
184
|
+
severity: i.severity,
|
|
185
|
+
type: i.type,
|
|
186
|
+
match: i.match,
|
|
187
|
+
location: i.location,
|
|
188
|
+
suggestion: i.suggestion
|
|
189
|
+
})),
|
|
190
|
+
scan_duration_ms: scanResult.scan_duration_ms,
|
|
191
|
+
rules_checked: scanResult.rules_checked
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
196
|
+
structuredContent: response
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// mund_check_url - Validate URL safety
|
|
203
|
+
// ============================================================================
|
|
204
|
+
server.registerTool(
|
|
205
|
+
'mund_check_url',
|
|
206
|
+
{
|
|
207
|
+
title: 'Check URL Safety',
|
|
208
|
+
description: `Check if a URL is safe to access.
|
|
209
|
+
|
|
210
|
+
Validates URLs against known dangerous patterns including:
|
|
211
|
+
- Data exfiltration services (webhook.site, requestbin, etc.)
|
|
212
|
+
- IP-based URLs (often used to bypass domain filtering)
|
|
213
|
+
- Suspicious TLDs (.tk, .ml, etc.)
|
|
214
|
+
- Data URLs (can contain arbitrary content)
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
- url (string): The URL to check (required)
|
|
218
|
+
- tool_name (string): Name of tool attempting access (optional)
|
|
219
|
+
- agent_id (string): ID of requesting agent (optional)
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
JSON object with:
|
|
223
|
+
- url: The checked URL
|
|
224
|
+
- safe: Boolean indicating if URL is safe
|
|
225
|
+
- risk_level: 'safe' | 'suspicious' | 'dangerous'
|
|
226
|
+
- warnings: Array of warning messages if any
|
|
227
|
+
- blocked: Whether access was blocked`,
|
|
228
|
+
inputSchema: CheckUrlInputSchema,
|
|
229
|
+
annotations: {
|
|
230
|
+
readOnlyHint: true,
|
|
231
|
+
destructiveHint: false,
|
|
232
|
+
idempotentHint: true,
|
|
233
|
+
openWorldHint: false
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
async (params) => {
|
|
237
|
+
const warnings: string[] = [];
|
|
238
|
+
let riskLevel: 'safe' | 'suspicious' | 'dangerous' = 'safe';
|
|
239
|
+
|
|
240
|
+
// Check against dangerous URL patterns
|
|
241
|
+
const { DANGEROUS_URL_PATTERNS } = await import('../constants.js');
|
|
242
|
+
|
|
243
|
+
for (const pattern of DANGEROUS_URL_PATTERNS) {
|
|
244
|
+
if (pattern.test(params.url)) {
|
|
245
|
+
riskLevel = 'dangerous';
|
|
246
|
+
warnings.push('URL matches known dangerous pattern');
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Check for IP-based URLs
|
|
252
|
+
if (/^https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.test(params.url)) {
|
|
253
|
+
if (riskLevel === 'safe') riskLevel = 'suspicious';
|
|
254
|
+
warnings.push('IP-based URL detected');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check for data URLs
|
|
258
|
+
if (params.url.startsWith('data:')) {
|
|
259
|
+
riskLevel = 'dangerous';
|
|
260
|
+
warnings.push('Data URL can contain arbitrary content');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check for suspicious ports
|
|
264
|
+
try {
|
|
265
|
+
const urlObj = new URL(params.url);
|
|
266
|
+
const port = urlObj.port;
|
|
267
|
+
if (port && !['80', '443', '8080', '8443'].includes(port)) {
|
|
268
|
+
if (riskLevel === 'safe') riskLevel = 'suspicious';
|
|
269
|
+
warnings.push(`Unusual port: ${port}`);
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
riskLevel = 'dangerous';
|
|
273
|
+
warnings.push('Invalid URL format');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const isSafe = riskLevel === 'safe';
|
|
277
|
+
const shouldBlock = blockMode && riskLevel === 'dangerous';
|
|
278
|
+
|
|
279
|
+
// Log event if dangerous
|
|
280
|
+
if (riskLevel === 'dangerous') {
|
|
281
|
+
const event: SecurityEvent = {
|
|
282
|
+
id: uuidv4(),
|
|
283
|
+
timestamp: new Date(),
|
|
284
|
+
rule_id: 'url_check',
|
|
285
|
+
rule_name: 'Dangerous URL Detected',
|
|
286
|
+
severity: 'high',
|
|
287
|
+
type: 'exfiltration',
|
|
288
|
+
action_taken: shouldBlock ? 'block' : 'alert',
|
|
289
|
+
content_snippet: truncateSnippet(params.url, MAX_SNIPPET_LENGTH),
|
|
290
|
+
full_content_hash: createHash('sha256').update(params.url).digest('hex'),
|
|
291
|
+
context: { tool_name: params.tool_name, agent_id: params.agent_id },
|
|
292
|
+
acknowledged: false
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
await storage.saveEvent(event);
|
|
296
|
+
await notificationHub.notify(event);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const response = {
|
|
300
|
+
url: params.url,
|
|
301
|
+
safe: isSafe,
|
|
302
|
+
risk_level: riskLevel,
|
|
303
|
+
warnings,
|
|
304
|
+
blocked: shouldBlock
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
309
|
+
structuredContent: response
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// ============================================================================
|
|
315
|
+
// mund_validate_command - Check shell command safety
|
|
316
|
+
// ============================================================================
|
|
317
|
+
server.registerTool(
|
|
318
|
+
'mund_validate_command',
|
|
319
|
+
{
|
|
320
|
+
title: 'Validate Shell Command',
|
|
321
|
+
description: `Validate a shell command for safety before execution.
|
|
322
|
+
|
|
323
|
+
Checks for dangerous patterns including:
|
|
324
|
+
- Destructive commands (rm -rf, format, etc.)
|
|
325
|
+
- Privilege escalation (sudo, chmod 777, etc.)
|
|
326
|
+
- Network exfiltration (curl to suspicious URLs, etc.)
|
|
327
|
+
- Code injection (eval, exec with variables, etc.)
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
- command (string): Shell command to validate (required)
|
|
331
|
+
- tool_name (string): Name of tool executing command (optional)
|
|
332
|
+
- agent_id (string): ID of requesting agent (optional)
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
JSON object with:
|
|
336
|
+
- command: The validated command
|
|
337
|
+
- safe: Boolean indicating if command is safe
|
|
338
|
+
- risk_level: 'safe' | 'suspicious' | 'dangerous'
|
|
339
|
+
- warnings: Array of warning messages
|
|
340
|
+
- blocked: Whether execution was blocked`,
|
|
341
|
+
inputSchema: ValidateCommandInputSchema,
|
|
342
|
+
annotations: {
|
|
343
|
+
readOnlyHint: true,
|
|
344
|
+
destructiveHint: false,
|
|
345
|
+
idempotentHint: true,
|
|
346
|
+
openWorldHint: false
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
async (params) => {
|
|
350
|
+
const warnings: string[] = [];
|
|
351
|
+
let riskLevel: 'safe' | 'suspicious' | 'dangerous' = 'safe';
|
|
352
|
+
|
|
353
|
+
// Dangerous command patterns
|
|
354
|
+
const dangerousPatterns = [
|
|
355
|
+
{ pattern: /rm\s+-[rf]{1,2}\s+[\/~]/, message: 'Recursive deletion of root/home directory' },
|
|
356
|
+
{ pattern: /mkfs\./, message: 'Filesystem format command' },
|
|
357
|
+
{ pattern: /dd\s+if=.*of=\/dev\//, message: 'Direct disk write' },
|
|
358
|
+
{ pattern: />\s*\/dev\/sd[a-z]/, message: 'Direct device write' },
|
|
359
|
+
{ pattern: /chmod\s+777/, message: 'Overly permissive chmod' },
|
|
360
|
+
{ pattern: /curl.*\|\s*(ba)?sh/, message: 'Piping curl to shell' },
|
|
361
|
+
{ pattern: /wget.*\|\s*(ba)?sh/, message: 'Piping wget to shell' },
|
|
362
|
+
{ pattern: /eval\s+"?\$/, message: 'Eval with variable' },
|
|
363
|
+
{ pattern: /:(){ :|:& };:/, message: 'Fork bomb detected' },
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
for (const { pattern, message } of dangerousPatterns) {
|
|
367
|
+
if (pattern.test(params.command)) {
|
|
368
|
+
riskLevel = 'dangerous';
|
|
369
|
+
warnings.push(message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Suspicious patterns
|
|
374
|
+
const suspiciousPatterns = [
|
|
375
|
+
{ pattern: /sudo\s/, message: 'Sudo usage' },
|
|
376
|
+
{ pattern: />\s*\/etc\//, message: 'Writing to /etc' },
|
|
377
|
+
{ pattern: /curl|wget/, message: 'Network download command' },
|
|
378
|
+
{ pattern: /nc\s+-/, message: 'Netcat usage' },
|
|
379
|
+
{ pattern: /base64\s+-d/, message: 'Base64 decoding' },
|
|
380
|
+
];
|
|
381
|
+
|
|
382
|
+
for (const { pattern, message } of suspiciousPatterns) {
|
|
383
|
+
if (pattern.test(params.command) && riskLevel === 'safe') {
|
|
384
|
+
riskLevel = 'suspicious';
|
|
385
|
+
warnings.push(message);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const isSafe = riskLevel === 'safe';
|
|
390
|
+
const shouldBlock = blockMode && riskLevel === 'dangerous';
|
|
391
|
+
|
|
392
|
+
// Log event if dangerous
|
|
393
|
+
if (riskLevel === 'dangerous') {
|
|
394
|
+
const event: SecurityEvent = {
|
|
395
|
+
id: uuidv4(),
|
|
396
|
+
timestamp: new Date(),
|
|
397
|
+
rule_id: 'command_check',
|
|
398
|
+
rule_name: 'Dangerous Command Detected',
|
|
399
|
+
severity: 'high',
|
|
400
|
+
type: 'code_pattern',
|
|
401
|
+
action_taken: shouldBlock ? 'block' : 'alert',
|
|
402
|
+
content_snippet: truncateSnippet(params.command, MAX_SNIPPET_LENGTH),
|
|
403
|
+
full_content_hash: createHash('sha256').update(params.command).digest('hex'),
|
|
404
|
+
context: { tool_name: params.tool_name, agent_id: params.agent_id },
|
|
405
|
+
acknowledged: false
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
await storage.saveEvent(event);
|
|
409
|
+
await notificationHub.notify(event);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const response = {
|
|
413
|
+
command: params.command,
|
|
414
|
+
safe: isSafe,
|
|
415
|
+
risk_level: riskLevel,
|
|
416
|
+
warnings,
|
|
417
|
+
blocked: shouldBlock
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
422
|
+
structuredContent: response
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
// ============================================================================
|
|
428
|
+
// mund_get_events - Retrieve recent security events
|
|
429
|
+
// ============================================================================
|
|
430
|
+
server.registerTool(
|
|
431
|
+
'mund_get_events',
|
|
432
|
+
{
|
|
433
|
+
title: 'Get Security Events',
|
|
434
|
+
description: `Retrieve recent security events from the Mund monitoring system.
|
|
435
|
+
|
|
436
|
+
Supports filtering and pagination for efficient event retrieval.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
- limit (number): Maximum events to return, 1-1000 (default: 50)
|
|
440
|
+
- offset (number): Skip this many events for pagination (default: 0)
|
|
441
|
+
- severity ('critical' | 'high' | 'medium' | 'low' | 'info'): Filter by severity
|
|
442
|
+
- type: Filter by detection type
|
|
443
|
+
- acknowledged (boolean): Filter by acknowledgment status
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
JSON object with:
|
|
447
|
+
- total: Total matching events
|
|
448
|
+
- count: Events in this response
|
|
449
|
+
- offset: Current offset
|
|
450
|
+
- has_more: Whether more events exist
|
|
451
|
+
- events: Array of security events`,
|
|
452
|
+
inputSchema: GetEventsInputSchema,
|
|
453
|
+
annotations: {
|
|
454
|
+
readOnlyHint: true,
|
|
455
|
+
destructiveHint: false,
|
|
456
|
+
idempotentHint: true,
|
|
457
|
+
openWorldHint: false
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
async (params) => {
|
|
461
|
+
const query = {
|
|
462
|
+
limit: params.limit,
|
|
463
|
+
offset: params.offset,
|
|
464
|
+
severity: params.severity as Severity | undefined,
|
|
465
|
+
type: params.type as DetectorType | undefined,
|
|
466
|
+
acknowledged: params.acknowledged
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const [events, total] = await Promise.all([
|
|
470
|
+
storage.getEvents(query),
|
|
471
|
+
storage.countEvents(query)
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
const response = {
|
|
475
|
+
total,
|
|
476
|
+
count: events.length,
|
|
477
|
+
offset: params.offset,
|
|
478
|
+
has_more: total > params.offset + events.length,
|
|
479
|
+
events: events.map(e => ({
|
|
480
|
+
id: e.id,
|
|
481
|
+
timestamp: e.timestamp.toISOString(),
|
|
482
|
+
rule_id: e.rule_id,
|
|
483
|
+
rule_name: e.rule_name,
|
|
484
|
+
severity: e.severity,
|
|
485
|
+
type: e.type,
|
|
486
|
+
action_taken: e.action_taken,
|
|
487
|
+
content_snippet: e.content_snippet,
|
|
488
|
+
context: e.context,
|
|
489
|
+
acknowledged: e.acknowledged,
|
|
490
|
+
acknowledged_by: e.acknowledged_by,
|
|
491
|
+
acknowledged_at: e.acknowledged_at?.toISOString()
|
|
492
|
+
}))
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
497
|
+
structuredContent: response
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// ============================================================================
|
|
503
|
+
// mund_get_status - Get current monitoring status
|
|
504
|
+
// ============================================================================
|
|
505
|
+
server.registerTool(
|
|
506
|
+
'mund_get_status',
|
|
507
|
+
{
|
|
508
|
+
title: 'Get Monitoring Status',
|
|
509
|
+
description: `Get the current status of the Mund monitoring system.
|
|
510
|
+
|
|
511
|
+
Returns information about:
|
|
512
|
+
- Active analyzers and their status
|
|
513
|
+
- Number of rules loaded
|
|
514
|
+
- Configured notification channels
|
|
515
|
+
- Block mode status
|
|
516
|
+
- Recent event statistics`,
|
|
517
|
+
inputSchema: GetStatusInputSchema,
|
|
518
|
+
annotations: {
|
|
519
|
+
readOnlyHint: true,
|
|
520
|
+
destructiveHint: false,
|
|
521
|
+
idempotentHint: true,
|
|
522
|
+
openWorldHint: false
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
async () => {
|
|
526
|
+
const eventCounts = {
|
|
527
|
+
total: await storage.countEvents({}),
|
|
528
|
+
unacknowledged: await storage.countEvents({ acknowledged: false }),
|
|
529
|
+
critical: await storage.countEvents({ severity: 'critical' as Severity }),
|
|
530
|
+
high: await storage.countEvents({ severity: 'high' as Severity })
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const response = {
|
|
534
|
+
status: 'active',
|
|
535
|
+
version: '0.1.0',
|
|
536
|
+
block_mode: blockMode,
|
|
537
|
+
analyzers: analyzer.getAnalyzerNames(),
|
|
538
|
+
rules_loaded: rules.filter(r => r.enabled).length,
|
|
539
|
+
notifiers: notificationHub.getNotifierNames(),
|
|
540
|
+
event_counts: eventCounts,
|
|
541
|
+
timestamp: new Date().toISOString()
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
546
|
+
structuredContent: response
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// Helper Functions
|
|
554
|
+
// ============================================================================
|
|
555
|
+
|
|
556
|
+
function truncateSnippet(text: string, maxLength: number): string {
|
|
557
|
+
if (text.length <= maxLength) return text;
|
|
558
|
+
return text.substring(0, maxLength - 3) + '...';
|
|
559
|
+
}
|