@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Mund - The Guardian Protocol
|
|
4
|
+
* Main Entry Point
|
|
5
|
+
*
|
|
6
|
+
* An MCP-based security monitoring protocol for agentic AI systems.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
12
|
+
import express from 'express';
|
|
13
|
+
|
|
14
|
+
import type { MundConfig, DetectionRule } from './types.js';
|
|
15
|
+
import { DEFAULT_CONFIG, DEFAULT_RULES } from './constants.js';
|
|
16
|
+
import { createStorage } from './storage/index.js';
|
|
17
|
+
import { AnalyzerEngine } from './analyzers/index.js';
|
|
18
|
+
import { NotificationHub } from './notifications/index.js';
|
|
19
|
+
import { registerAllTools } from './tools/index.js';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Configuration Loading
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
function loadConfig(): MundConfig {
|
|
26
|
+
const config: MundConfig = {
|
|
27
|
+
...DEFAULT_CONFIG,
|
|
28
|
+
|
|
29
|
+
// Server settings from environment
|
|
30
|
+
port: parseInt(process.env.MUND_PORT || '3000'),
|
|
31
|
+
host: process.env.MUND_HOST || '127.0.0.1',
|
|
32
|
+
transport: (process.env.MUND_TRANSPORT as 'stdio' | 'http') || 'stdio',
|
|
33
|
+
|
|
34
|
+
// Logging
|
|
35
|
+
log_level: (process.env.MUND_LOG_LEVEL as MundConfig['log_level']) || 'info',
|
|
36
|
+
|
|
37
|
+
// Storage
|
|
38
|
+
storage_type: (process.env.MUND_STORAGE as MundConfig['storage_type']) || 'memory',
|
|
39
|
+
database_url: process.env.MUND_DATABASE_URL,
|
|
40
|
+
|
|
41
|
+
// Security
|
|
42
|
+
block_mode: process.env.MUND_BLOCK_MODE === 'block',
|
|
43
|
+
api_key: process.env.MUND_API_KEY,
|
|
44
|
+
|
|
45
|
+
// Notifications
|
|
46
|
+
notifications: {
|
|
47
|
+
slack: process.env.MUND_SLACK_WEBHOOK ? {
|
|
48
|
+
webhook_url: process.env.MUND_SLACK_WEBHOOK,
|
|
49
|
+
channel: process.env.MUND_SLACK_CHANNEL,
|
|
50
|
+
username: process.env.MUND_SLACK_USERNAME || 'Mund Guardian',
|
|
51
|
+
icon_emoji: process.env.MUND_SLACK_EMOJI || ':eye:',
|
|
52
|
+
min_severity: (process.env.MUND_SLACK_MIN_SEVERITY as any) || 'high'
|
|
53
|
+
} : undefined,
|
|
54
|
+
|
|
55
|
+
teams: process.env.MUND_TEAMS_WEBHOOK ? {
|
|
56
|
+
webhook_url: process.env.MUND_TEAMS_WEBHOOK,
|
|
57
|
+
min_severity: (process.env.MUND_TEAMS_MIN_SEVERITY as any) || 'high'
|
|
58
|
+
} : undefined,
|
|
59
|
+
|
|
60
|
+
email: process.env.MUND_EMAIL_SMTP_HOST ? {
|
|
61
|
+
smtp_host: process.env.MUND_EMAIL_SMTP_HOST,
|
|
62
|
+
smtp_port: parseInt(process.env.MUND_EMAIL_SMTP_PORT || '587'),
|
|
63
|
+
smtp_secure: process.env.MUND_EMAIL_SMTP_SECURE === 'true',
|
|
64
|
+
smtp_user: process.env.MUND_EMAIL_SMTP_USER,
|
|
65
|
+
smtp_pass: process.env.MUND_EMAIL_SMTP_PASS,
|
|
66
|
+
from_address: process.env.MUND_EMAIL_FROM || 'mund@localhost',
|
|
67
|
+
to_addresses: (process.env.MUND_EMAIL_TO || '').split(',').filter(Boolean),
|
|
68
|
+
min_severity: (process.env.MUND_EMAIL_MIN_SEVERITY as any) || 'high'
|
|
69
|
+
} : undefined,
|
|
70
|
+
|
|
71
|
+
webhooks: process.env.MUND_WEBHOOK_URL ? [{
|
|
72
|
+
url: process.env.MUND_WEBHOOK_URL,
|
|
73
|
+
method: 'POST' as const,
|
|
74
|
+
headers: process.env.MUND_WEBHOOK_HEADERS ? JSON.parse(process.env.MUND_WEBHOOK_HEADERS) : undefined,
|
|
75
|
+
min_severity: (process.env.MUND_WEBHOOK_MIN_SEVERITY as 'critical' | 'high' | 'medium' | 'low' | 'info') || 'high'
|
|
76
|
+
}] : undefined
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return config;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Server Initialization
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
87
|
+
async function createMundServer(config: MundConfig): Promise<McpServer> {
|
|
88
|
+
// Create MCP server
|
|
89
|
+
const server = new McpServer({
|
|
90
|
+
name: 'mund-mcp-server',
|
|
91
|
+
version: '0.1.0'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Initialize storage
|
|
95
|
+
const storage = createStorage(config);
|
|
96
|
+
|
|
97
|
+
// Initialize analyzer engine
|
|
98
|
+
const analyzer = new AnalyzerEngine();
|
|
99
|
+
|
|
100
|
+
// Initialize notification hub
|
|
101
|
+
const notificationHub = new NotificationHub(config);
|
|
102
|
+
|
|
103
|
+
// Load rules (start with defaults, could be extended from file)
|
|
104
|
+
const rules: DetectionRule[] = [...DEFAULT_RULES];
|
|
105
|
+
|
|
106
|
+
// Load any saved custom rules from storage
|
|
107
|
+
const savedRules = await storage.getRules();
|
|
108
|
+
for (const rule of savedRules) {
|
|
109
|
+
if (!rules.find(r => r.id === rule.id)) {
|
|
110
|
+
rules.push(rule);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Register all tools
|
|
115
|
+
registerAllTools(server, storage, rules, analyzer, notificationHub, config.block_mode);
|
|
116
|
+
|
|
117
|
+
// Log startup info
|
|
118
|
+
console.error(`Mund Guardian Protocol v0.1.0`);
|
|
119
|
+
console.error(`- Transport: ${config.transport}`);
|
|
120
|
+
console.error(`- Storage: ${config.storage_type}`);
|
|
121
|
+
console.error(`- Block Mode: ${config.block_mode ? 'enabled' : 'disabled'}`);
|
|
122
|
+
console.error(`- Rules Loaded: ${rules.filter(r => r.enabled).length}`);
|
|
123
|
+
console.error(`- Notifiers: ${notificationHub.getNotifierNames().join(', ') || 'none'}`);
|
|
124
|
+
|
|
125
|
+
return server;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Transport Handlers
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
async function runStdio(config: MundConfig): Promise<void> {
|
|
133
|
+
const server = await createMundServer(config);
|
|
134
|
+
const transport = new StdioServerTransport();
|
|
135
|
+
|
|
136
|
+
await server.connect(transport);
|
|
137
|
+
console.error('Mund MCP server running on stdio');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function runHTTP(config: MundConfig): Promise<void> {
|
|
141
|
+
const server = await createMundServer(config);
|
|
142
|
+
const app = express();
|
|
143
|
+
|
|
144
|
+
app.use(express.json({ limit: '10mb' }));
|
|
145
|
+
|
|
146
|
+
// Health check endpoint
|
|
147
|
+
app.get('/health', (_req, res) => {
|
|
148
|
+
res.json({ status: 'ok', version: '0.1.0' });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// MCP endpoint
|
|
152
|
+
app.post('/mcp', async (req, res) => {
|
|
153
|
+
const transport = new StreamableHTTPServerTransport({
|
|
154
|
+
sessionIdGenerator: undefined,
|
|
155
|
+
enableJsonResponse: true
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
res.on('close', () => transport.close());
|
|
159
|
+
|
|
160
|
+
await server.connect(transport);
|
|
161
|
+
await transport.handleRequest(req, res, req.body);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Start server
|
|
165
|
+
app.listen(config.port, config.host, () => {
|
|
166
|
+
console.error(`Mund MCP server running on http://${config.host}:${config.port}/mcp`);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// CLI and Main
|
|
172
|
+
// ============================================================================
|
|
173
|
+
|
|
174
|
+
function printHelp(): void {
|
|
175
|
+
console.log(`
|
|
176
|
+
Mund - The Guardian Protocol
|
|
177
|
+
MCP-based security monitoring for agentic AI systems
|
|
178
|
+
|
|
179
|
+
Usage: mund-mcp [options]
|
|
180
|
+
|
|
181
|
+
Options:
|
|
182
|
+
--help, -h Show this help message
|
|
183
|
+
--version, -v Show version number
|
|
184
|
+
--transport <type> Transport type: stdio (default) or http
|
|
185
|
+
--port <port> HTTP port (default: 3000)
|
|
186
|
+
--host <host> HTTP host (default: 127.0.0.1)
|
|
187
|
+
--block Enable block mode (block dangerous content)
|
|
188
|
+
|
|
189
|
+
Environment Variables:
|
|
190
|
+
MUND_PORT HTTP server port
|
|
191
|
+
MUND_HOST HTTP server host
|
|
192
|
+
MUND_TRANSPORT Transport type (stdio or http)
|
|
193
|
+
MUND_LOG_LEVEL Log level (debug, info, warn, error)
|
|
194
|
+
MUND_STORAGE Storage type (memory, sqlite, postgres)
|
|
195
|
+
MUND_DATABASE_URL Database connection URL
|
|
196
|
+
MUND_BLOCK_MODE Set to 'block' to enable blocking
|
|
197
|
+
MUND_API_KEY API key for authentication
|
|
198
|
+
|
|
199
|
+
MUND_SLACK_WEBHOOK Slack webhook URL
|
|
200
|
+
MUND_SLACK_CHANNEL Slack channel override
|
|
201
|
+
MUND_SLACK_MIN_SEVERITY Minimum severity for Slack alerts
|
|
202
|
+
|
|
203
|
+
MUND_TEAMS_WEBHOOK Microsoft Teams webhook URL
|
|
204
|
+
MUND_TEAMS_MIN_SEVERITY Minimum severity for Teams alerts
|
|
205
|
+
|
|
206
|
+
MUND_EMAIL_SMTP_HOST SMTP server host
|
|
207
|
+
MUND_EMAIL_SMTP_PORT SMTP server port
|
|
208
|
+
MUND_EMAIL_FROM From address for emails
|
|
209
|
+
MUND_EMAIL_TO Comma-separated recipient addresses
|
|
210
|
+
|
|
211
|
+
MUND_WEBHOOK_URL Generic webhook URL
|
|
212
|
+
MUND_WEBHOOK_HEADERS JSON string of webhook headers
|
|
213
|
+
|
|
214
|
+
Examples:
|
|
215
|
+
# Run as stdio server (for MCP clients)
|
|
216
|
+
mund-mcp
|
|
217
|
+
|
|
218
|
+
# Run as HTTP server
|
|
219
|
+
mund-mcp --transport http --port 3000
|
|
220
|
+
|
|
221
|
+
# Run with Slack notifications
|
|
222
|
+
MUND_SLACK_WEBHOOK=https://hooks.slack.com/... mund-mcp
|
|
223
|
+
|
|
224
|
+
# Run in block mode
|
|
225
|
+
mund-mcp --block
|
|
226
|
+
|
|
227
|
+
For more information, visit: https://github.com/your-org/mund-mcp
|
|
228
|
+
`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function main(): Promise<void> {
|
|
232
|
+
const args = process.argv.slice(2);
|
|
233
|
+
|
|
234
|
+
// Parse CLI arguments
|
|
235
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
236
|
+
printHelp();
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
241
|
+
console.log('0.1.0');
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Load config
|
|
246
|
+
const config = loadConfig();
|
|
247
|
+
|
|
248
|
+
// Override from CLI
|
|
249
|
+
const transportIndex = args.indexOf('--transport');
|
|
250
|
+
if (transportIndex !== -1 && args[transportIndex + 1]) {
|
|
251
|
+
config.transport = args[transportIndex + 1] as 'stdio' | 'http';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const portIndex = args.indexOf('--port');
|
|
255
|
+
if (portIndex !== -1 && args[portIndex + 1]) {
|
|
256
|
+
config.port = parseInt(args[portIndex + 1]);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const hostIndex = args.indexOf('--host');
|
|
260
|
+
if (hostIndex !== -1 && args[hostIndex + 1]) {
|
|
261
|
+
config.host = args[hostIndex + 1];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (args.includes('--block')) {
|
|
265
|
+
config.block_mode = true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Run appropriate transport
|
|
269
|
+
try {
|
|
270
|
+
if (config.transport === 'http') {
|
|
271
|
+
await runHTTP(config);
|
|
272
|
+
} else {
|
|
273
|
+
await runStdio(config);
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error('Failed to start Mund server:', error);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Run
|
|
282
|
+
main().catch(error => {
|
|
283
|
+
console.error('Fatal error:', error);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Email Notifier - Send alerts via email
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { INotifier, NotificationPayload, EmailConfig, Severity } from '../types.js';
|
|
7
|
+
|
|
8
|
+
// Note: In production, use nodemailer
|
|
9
|
+
// For now, we'll implement a basic interface that can be extended
|
|
10
|
+
|
|
11
|
+
export class EmailNotifier implements INotifier {
|
|
12
|
+
name = 'EmailNotifier';
|
|
13
|
+
private config: EmailConfig;
|
|
14
|
+
|
|
15
|
+
constructor(config: EmailConfig) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async send(payload: NotificationPayload): Promise<boolean> {
|
|
20
|
+
const { event } = payload;
|
|
21
|
+
|
|
22
|
+
// Check minimum severity
|
|
23
|
+
if (this.config.min_severity && !this.meetsMinSeverity(event.severity, this.config.min_severity)) {
|
|
24
|
+
return true; // Successfully skipped
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const emailContent = this.formatEmail(payload);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// In a real implementation, use nodemailer here
|
|
31
|
+
// For now, we'll log the email content
|
|
32
|
+
console.log('Email notification would be sent:');
|
|
33
|
+
console.log('To:', this.config.to_addresses.join(', '));
|
|
34
|
+
console.log('Subject:', emailContent.subject);
|
|
35
|
+
console.log('Body:', emailContent.html.substring(0, 500) + '...');
|
|
36
|
+
|
|
37
|
+
// Uncomment when nodemailer is configured:
|
|
38
|
+
// const transporter = nodemailer.createTransport({
|
|
39
|
+
// host: this.config.smtp_host,
|
|
40
|
+
// port: this.config.smtp_port,
|
|
41
|
+
// secure: this.config.smtp_secure,
|
|
42
|
+
// auth: this.config.smtp_user ? {
|
|
43
|
+
// user: this.config.smtp_user,
|
|
44
|
+
// pass: this.config.smtp_pass
|
|
45
|
+
// } : undefined
|
|
46
|
+
// });
|
|
47
|
+
//
|
|
48
|
+
// await transporter.sendMail({
|
|
49
|
+
// from: this.config.from_address,
|
|
50
|
+
// to: this.config.to_addresses.join(', '),
|
|
51
|
+
// subject: emailContent.subject,
|
|
52
|
+
// html: emailContent.html,
|
|
53
|
+
// text: emailContent.text
|
|
54
|
+
// });
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to send email notification:', error);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private formatEmail(payload: NotificationPayload): { subject: string; html: string; text: string } {
|
|
64
|
+
const { event } = payload;
|
|
65
|
+
const severityEmoji = this.getSeverityEmoji(event.severity);
|
|
66
|
+
const severityColor = this.getSeverityColor(event.severity);
|
|
67
|
+
|
|
68
|
+
const subject = `${severityEmoji} [Mund ${event.severity.toUpperCase()}] ${event.rule_name}`;
|
|
69
|
+
|
|
70
|
+
const html = `
|
|
71
|
+
<!DOCTYPE html>
|
|
72
|
+
<html>
|
|
73
|
+
<head>
|
|
74
|
+
<style>
|
|
75
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
|
|
76
|
+
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
77
|
+
.header { background: ${severityColor}; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
|
|
78
|
+
.content { background: #f8f9fa; padding: 20px; border: 1px solid #dee2e6; border-top: none; border-radius: 0 0 8px 8px; }
|
|
79
|
+
.snippet { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 4px; font-family: monospace; overflow-x: auto; }
|
|
80
|
+
.details { margin-top: 20px; }
|
|
81
|
+
.detail-row { display: flex; border-bottom: 1px solid #dee2e6; padding: 8px 0; }
|
|
82
|
+
.detail-label { font-weight: bold; width: 120px; flex-shrink: 0; }
|
|
83
|
+
.detail-value { color: #495057; }
|
|
84
|
+
.footer { margin-top: 20px; padding-top: 20px; border-top: 1px solid #dee2e6; font-size: 12px; color: #6c757d; }
|
|
85
|
+
</style>
|
|
86
|
+
</head>
|
|
87
|
+
<body>
|
|
88
|
+
<div class="container">
|
|
89
|
+
<div class="header">
|
|
90
|
+
<h1 style="margin: 0;">${severityEmoji} Mund Security Alert</h1>
|
|
91
|
+
<p style="margin: 10px 0 0 0; opacity: 0.9;">${event.rule_name}</p>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="content">
|
|
94
|
+
<h3>Detected Content</h3>
|
|
95
|
+
<div class="snippet">${this.escapeHtml(event.content_snippet)}</div>
|
|
96
|
+
|
|
97
|
+
<div class="details">
|
|
98
|
+
<h3>Event Details</h3>
|
|
99
|
+
<div class="detail-row">
|
|
100
|
+
<span class="detail-label">Severity:</span>
|
|
101
|
+
<span class="detail-value">${event.severity.toUpperCase()}</span>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="detail-row">
|
|
104
|
+
<span class="detail-label">Type:</span>
|
|
105
|
+
<span class="detail-value">${event.type}</span>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="detail-row">
|
|
108
|
+
<span class="detail-label">Action:</span>
|
|
109
|
+
<span class="detail-value">${event.action_taken}</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="detail-row">
|
|
112
|
+
<span class="detail-label">Rule ID:</span>
|
|
113
|
+
<span class="detail-value">${event.rule_id}</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="detail-row">
|
|
116
|
+
<span class="detail-label">Event ID:</span>
|
|
117
|
+
<span class="detail-value">${event.id}</span>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="detail-row">
|
|
120
|
+
<span class="detail-label">Timestamp:</span>
|
|
121
|
+
<span class="detail-value">${event.timestamp.toISOString()}</span>
|
|
122
|
+
</div>
|
|
123
|
+
${event.context.tool_name ? `
|
|
124
|
+
<div class="detail-row">
|
|
125
|
+
<span class="detail-label">Tool:</span>
|
|
126
|
+
<span class="detail-value">${event.context.tool_name}</span>
|
|
127
|
+
</div>
|
|
128
|
+
` : ''}
|
|
129
|
+
${event.context.agent_id ? `
|
|
130
|
+
<div class="detail-row">
|
|
131
|
+
<span class="detail-label">Agent:</span>
|
|
132
|
+
<span class="detail-value">${event.context.agent_id}</span>
|
|
133
|
+
</div>
|
|
134
|
+
` : ''}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="footer">
|
|
138
|
+
This alert was generated by Mund - The Guardian Protocol.<br>
|
|
139
|
+
Review and acknowledge this alert in your security dashboard.
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</body>
|
|
144
|
+
</html>`;
|
|
145
|
+
|
|
146
|
+
const text = `
|
|
147
|
+
MUND SECURITY ALERT
|
|
148
|
+
====================
|
|
149
|
+
|
|
150
|
+
${event.severity.toUpperCase()}: ${event.rule_name}
|
|
151
|
+
|
|
152
|
+
Detected Content:
|
|
153
|
+
${event.content_snippet}
|
|
154
|
+
|
|
155
|
+
Event Details:
|
|
156
|
+
- Severity: ${event.severity.toUpperCase()}
|
|
157
|
+
- Type: ${event.type}
|
|
158
|
+
- Action: ${event.action_taken}
|
|
159
|
+
- Rule ID: ${event.rule_id}
|
|
160
|
+
- Event ID: ${event.id}
|
|
161
|
+
- Timestamp: ${event.timestamp.toISOString()}
|
|
162
|
+
${event.context.tool_name ? `- Tool: ${event.context.tool_name}` : ''}
|
|
163
|
+
${event.context.agent_id ? `- Agent: ${event.context.agent_id}` : ''}
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
This alert was generated by Mund - The Guardian Protocol.
|
|
167
|
+
`;
|
|
168
|
+
|
|
169
|
+
return { subject, html, text };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private escapeHtml(text: string): string {
|
|
173
|
+
return text
|
|
174
|
+
.replace(/&/g, '&')
|
|
175
|
+
.replace(/</g, '<')
|
|
176
|
+
.replace(/>/g, '>')
|
|
177
|
+
.replace(/"/g, '"')
|
|
178
|
+
.replace(/'/g, ''');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private getSeverityColor(severity: Severity): string {
|
|
182
|
+
const colors: Record<Severity, string> = {
|
|
183
|
+
critical: '#dc3545',
|
|
184
|
+
high: '#fd7e14',
|
|
185
|
+
medium: '#ffc107',
|
|
186
|
+
low: '#17a2b8',
|
|
187
|
+
info: '#6c757d'
|
|
188
|
+
};
|
|
189
|
+
return colors[severity] || colors.info;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private getSeverityEmoji(severity: Severity): string {
|
|
193
|
+
const emojis: Record<Severity, string> = {
|
|
194
|
+
critical: '🚨',
|
|
195
|
+
high: '⚠️',
|
|
196
|
+
medium: '⚡',
|
|
197
|
+
low: 'ℹ️',
|
|
198
|
+
info: '📝'
|
|
199
|
+
};
|
|
200
|
+
return emojis[severity] || '📝';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private meetsMinSeverity(eventSeverity: Severity, minSeverity: Severity): boolean {
|
|
204
|
+
const order: Severity[] = ['critical', 'high', 'medium', 'low', 'info'];
|
|
205
|
+
const eventIndex = order.indexOf(eventSeverity);
|
|
206
|
+
const minIndex = order.indexOf(minSeverity);
|
|
207
|
+
return eventIndex <= minIndex;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export default EmailNotifier;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Notifications Index - Notification hub and exports
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { SlackNotifier } from './slack.js';
|
|
7
|
+
export { TeamsNotifier } from './teams.js';
|
|
8
|
+
export { EmailNotifier } from './email.js';
|
|
9
|
+
export { WebhookNotifier } from './webhook.js';
|
|
10
|
+
|
|
11
|
+
import { SlackNotifier } from './slack.js';
|
|
12
|
+
import { TeamsNotifier } from './teams.js';
|
|
13
|
+
import { EmailNotifier } from './email.js';
|
|
14
|
+
import { WebhookNotifier } from './webhook.js';
|
|
15
|
+
import type {
|
|
16
|
+
INotifier,
|
|
17
|
+
NotificationConfig,
|
|
18
|
+
NotificationPayload,
|
|
19
|
+
SecurityEvent,
|
|
20
|
+
MundConfig
|
|
21
|
+
} from '../types.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Notification Hub - Manages and dispatches notifications to all configured channels
|
|
25
|
+
*/
|
|
26
|
+
export class NotificationHub {
|
|
27
|
+
private notifiers: INotifier[] = [];
|
|
28
|
+
private config: MundConfig;
|
|
29
|
+
|
|
30
|
+
constructor(config: MundConfig) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.initializeNotifiers(config.notifications);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private initializeNotifiers(notificationConfig: NotificationConfig): void {
|
|
36
|
+
// Slack
|
|
37
|
+
if (notificationConfig.slack?.webhook_url) {
|
|
38
|
+
this.notifiers.push(new SlackNotifier(notificationConfig.slack));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Teams
|
|
42
|
+
if (notificationConfig.teams?.webhook_url) {
|
|
43
|
+
this.notifiers.push(new TeamsNotifier(notificationConfig.teams));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Email
|
|
47
|
+
if (notificationConfig.email?.smtp_host) {
|
|
48
|
+
this.notifiers.push(new EmailNotifier(notificationConfig.email));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Webhooks
|
|
52
|
+
if (notificationConfig.webhooks) {
|
|
53
|
+
for (const webhookConfig of notificationConfig.webhooks) {
|
|
54
|
+
this.notifiers.push(new WebhookNotifier(webhookConfig));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Send notification to all configured channels
|
|
61
|
+
*/
|
|
62
|
+
async notify(event: SecurityEvent): Promise<{ success: boolean; results: NotificationResult[] }> {
|
|
63
|
+
if (this.notifiers.length === 0) {
|
|
64
|
+
return { success: true, results: [] };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const payload: NotificationPayload = {
|
|
68
|
+
event,
|
|
69
|
+
config: this.config,
|
|
70
|
+
formatted_message: this.formatMessage(event)
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const results: NotificationResult[] = [];
|
|
74
|
+
|
|
75
|
+
for (const notifier of this.notifiers) {
|
|
76
|
+
try {
|
|
77
|
+
const success = await notifier.send(payload);
|
|
78
|
+
results.push({
|
|
79
|
+
notifier: notifier.name,
|
|
80
|
+
success,
|
|
81
|
+
error: success ? undefined : 'Failed to send notification'
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
results.push({
|
|
85
|
+
notifier: notifier.name,
|
|
86
|
+
success: false,
|
|
87
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const allSuccessful = results.every(r => r.success);
|
|
93
|
+
return { success: allSuccessful, results };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Format a human-readable message
|
|
98
|
+
*/
|
|
99
|
+
private formatMessage(event: SecurityEvent): string {
|
|
100
|
+
return `[${event.severity.toUpperCase()}] ${event.rule_name}: ${event.content_snippet}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Add a notifier at runtime
|
|
105
|
+
*/
|
|
106
|
+
addNotifier(notifier: INotifier): void {
|
|
107
|
+
this.notifiers.push(notifier);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Remove a notifier by name
|
|
112
|
+
*/
|
|
113
|
+
removeNotifier(name: string): void {
|
|
114
|
+
this.notifiers = this.notifiers.filter(n => n.name !== name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get list of configured notifiers
|
|
119
|
+
*/
|
|
120
|
+
getNotifierNames(): string[] {
|
|
121
|
+
return this.notifiers.map(n => n.name);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if any notifiers are configured
|
|
126
|
+
*/
|
|
127
|
+
hasNotifiers(): boolean {
|
|
128
|
+
return this.notifiers.length > 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface NotificationResult {
|
|
133
|
+
notifier: string;
|
|
134
|
+
success: boolean;
|
|
135
|
+
error?: string;
|
|
136
|
+
}
|