@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.
Files changed (98) hide show
  1. package/dist/analyzers/code-analyzer.d.ts +24 -0
  2. package/dist/analyzers/code-analyzer.d.ts.map +1 -0
  3. package/dist/analyzers/code-analyzer.js +73 -0
  4. package/dist/analyzers/code-analyzer.js.map +1 -0
  5. package/dist/analyzers/exfiltration-detector.d.ts +40 -0
  6. package/dist/analyzers/exfiltration-detector.d.ts.map +1 -0
  7. package/dist/analyzers/exfiltration-detector.js +220 -0
  8. package/dist/analyzers/exfiltration-detector.js.map +1 -0
  9. package/dist/analyzers/index.d.ts +26 -0
  10. package/dist/analyzers/index.d.ts.map +1 -0
  11. package/dist/analyzers/index.js +61 -0
  12. package/dist/analyzers/index.js.map +1 -0
  13. package/dist/analyzers/injection-detector.d.ts +36 -0
  14. package/dist/analyzers/injection-detector.d.ts.map +1 -0
  15. package/dist/analyzers/injection-detector.js +165 -0
  16. package/dist/analyzers/injection-detector.js.map +1 -0
  17. package/dist/analyzers/pii-detector.d.ts +44 -0
  18. package/dist/analyzers/pii-detector.d.ts.map +1 -0
  19. package/dist/analyzers/pii-detector.js +190 -0
  20. package/dist/analyzers/pii-detector.js.map +1 -0
  21. package/dist/analyzers/secret-scanner.d.ts +32 -0
  22. package/dist/analyzers/secret-scanner.d.ts.map +1 -0
  23. package/dist/analyzers/secret-scanner.js +119 -0
  24. package/dist/analyzers/secret-scanner.js.map +1 -0
  25. package/dist/constants.d.ts +18 -0
  26. package/dist/constants.d.ts.map +1 -0
  27. package/dist/constants.js +580 -0
  28. package/dist/constants.js.map +1 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +242 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/notifications/email.d.ts +18 -0
  34. package/dist/notifications/email.d.ts.map +1 -0
  35. package/dist/notifications/email.js +192 -0
  36. package/dist/notifications/email.js.map +1 -0
  37. package/dist/notifications/index.d.ts +51 -0
  38. package/dist/notifications/index.d.ts.map +1 -0
  39. package/dist/notifications/index.js +107 -0
  40. package/dist/notifications/index.js.map +1 -0
  41. package/dist/notifications/slack.d.ts +17 -0
  42. package/dist/notifications/slack.d.ts.map +1 -0
  43. package/dist/notifications/slack.js +128 -0
  44. package/dist/notifications/slack.js.map +1 -0
  45. package/dist/notifications/teams.d.ts +17 -0
  46. package/dist/notifications/teams.d.ts.map +1 -0
  47. package/dist/notifications/teams.js +136 -0
  48. package/dist/notifications/teams.js.map +1 -0
  49. package/dist/notifications/webhook.d.ts +15 -0
  50. package/dist/notifications/webhook.d.ts.map +1 -0
  51. package/dist/notifications/webhook.js +67 -0
  52. package/dist/notifications/webhook.js.map +1 -0
  53. package/dist/storage/index.d.ts +11 -0
  54. package/dist/storage/index.d.ts.map +1 -0
  55. package/dist/storage/index.js +26 -0
  56. package/dist/storage/index.js.map +1 -0
  57. package/dist/storage/memory.d.ts +35 -0
  58. package/dist/storage/memory.d.ts.map +1 -0
  59. package/dist/storage/memory.js +144 -0
  60. package/dist/storage/memory.js.map +1 -0
  61. package/dist/tools/configuration.d.ts +8 -0
  62. package/dist/tools/configuration.d.ts.map +1 -0
  63. package/dist/tools/configuration.js +399 -0
  64. package/dist/tools/configuration.js.map +1 -0
  65. package/dist/tools/index.d.ts +15 -0
  66. package/dist/tools/index.d.ts.map +1 -0
  67. package/dist/tools/index.js +16 -0
  68. package/dist/tools/index.js.map +1 -0
  69. package/dist/tools/monitoring.d.ts +10 -0
  70. package/dist/tools/monitoring.d.ts.map +1 -0
  71. package/dist/tools/monitoring.js +476 -0
  72. package/dist/tools/monitoring.js.map +1 -0
  73. package/dist/types.d.ts +207 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +6 -0
  76. package/dist/types.js.map +1 -0
  77. package/package.json +62 -0
  78. package/rules/default.yaml +189 -0
  79. package/src/analyzers/code-analyzer.ts +88 -0
  80. package/src/analyzers/exfiltration-detector.ts +257 -0
  81. package/src/analyzers/index.ts +74 -0
  82. package/src/analyzers/injection-detector.ts +191 -0
  83. package/src/analyzers/pii-detector.ts +220 -0
  84. package/src/analyzers/secret-scanner.ts +142 -0
  85. package/src/constants.ts +603 -0
  86. package/src/index.ts +285 -0
  87. package/src/notifications/email.ts +211 -0
  88. package/src/notifications/index.ts +136 -0
  89. package/src/notifications/slack.ts +170 -0
  90. package/src/notifications/teams.ts +200 -0
  91. package/src/notifications/webhook.ts +80 -0
  92. package/src/storage/index.ts +29 -0
  93. package/src/storage/memory.ts +181 -0
  94. package/src/tools/configuration.ts +478 -0
  95. package/src/tools/index.ts +29 -0
  96. package/src/tools/monitoring.ts +559 -0
  97. package/src/types.ts +300 -0
  98. 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, '&amp;')
175
+ .replace(/</g, '&lt;')
176
+ .replace(/>/g, '&gt;')
177
+ .replace(/"/g, '&quot;')
178
+ .replace(/'/g, '&#039;');
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
+ }