@weave_protocol/mund 0.2.2 → 0.3.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/README.md +94 -0
  2. package/dist/analyzers/code-analyzer.d.ts.map +1 -1
  3. package/dist/analyzers/code-analyzer.js +2 -1
  4. package/dist/analyzers/code-analyzer.js.map +1 -1
  5. package/dist/analyzers/exfiltration-detector.d.ts.map +1 -1
  6. package/dist/analyzers/exfiltration-detector.js +2 -1
  7. package/dist/analyzers/exfiltration-detector.js.map +1 -1
  8. package/dist/analyzers/index.js +1 -1
  9. package/dist/analyzers/injection-detector.d.ts.map +1 -1
  10. package/dist/analyzers/injection-detector.js +2 -1
  11. package/dist/analyzers/injection-detector.js.map +1 -1
  12. package/dist/analyzers/mcp-server-analyzer.d.ts +2 -0
  13. package/dist/analyzers/mcp-server-analyzer.d.ts.map +1 -1
  14. package/dist/analyzers/mcp-server-analyzer.js.map +1 -1
  15. package/dist/analyzers/pii-detector.d.ts.map +1 -1
  16. package/dist/analyzers/pii-detector.js +2 -1
  17. package/dist/analyzers/pii-detector.js.map +1 -1
  18. package/dist/analyzers/regex.d.ts +10 -0
  19. package/dist/analyzers/regex.d.ts.map +1 -0
  20. package/dist/analyzers/regex.js +17 -0
  21. package/dist/analyzers/regex.js.map +1 -0
  22. package/dist/analyzers/secret-scanner.d.ts.map +1 -1
  23. package/dist/analyzers/secret-scanner.js +2 -1
  24. package/dist/analyzers/secret-scanner.js.map +1 -1
  25. package/dist/cli/scan.d.ts +49 -0
  26. package/dist/cli/scan.d.ts.map +1 -0
  27. package/dist/cli/scan.js +226 -0
  28. package/dist/cli/scan.js.map +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +10 -4
  31. package/dist/index.js.map +1 -1
  32. package/dist/notifications/email.d.ts +21 -8
  33. package/dist/notifications/email.d.ts.map +1 -1
  34. package/dist/notifications/email.js +32 -178
  35. package/dist/notifications/email.js.map +1 -1
  36. package/dist/osv.d.ts +94 -0
  37. package/dist/osv.d.ts.map +1 -0
  38. package/dist/osv.js +239 -0
  39. package/dist/osv.js.map +1 -0
  40. package/dist/sarif.d.ts +20 -0
  41. package/dist/sarif.d.ts.map +1 -0
  42. package/dist/sarif.js +122 -0
  43. package/dist/sarif.js.map +1 -0
  44. package/dist/scoring.d.ts +26 -0
  45. package/dist/scoring.d.ts.map +1 -0
  46. package/dist/scoring.js +49 -0
  47. package/dist/scoring.js.map +1 -0
  48. package/dist/threat-intel-manager.d.ts.map +1 -1
  49. package/dist/threat-intel-manager.js +2 -1
  50. package/dist/threat-intel-manager.js.map +1 -1
  51. package/dist/tools/configuration.d.ts.map +1 -1
  52. package/dist/tools/configuration.js +2 -1
  53. package/dist/tools/configuration.js.map +1 -1
  54. package/dist/tools/mcp-scanning.d.ts.map +1 -1
  55. package/dist/tools/mcp-scanning.js +4 -0
  56. package/dist/tools/mcp-scanning.js.map +1 -1
  57. package/dist/tools/monitoring.d.ts.map +1 -1
  58. package/dist/tools/monitoring.js +22 -1
  59. package/dist/tools/monitoring.js.map +1 -1
  60. package/dist/version.d.ts +6 -0
  61. package/dist/version.d.ts.map +1 -0
  62. package/dist/version.js +19 -0
  63. package/dist/version.js.map +1 -0
  64. package/package.json +5 -8
  65. package/src/analyzers/code-analyzer.ts +2 -1
  66. package/src/analyzers/exfiltration-detector.ts +2 -1
  67. package/src/analyzers/index.ts +1 -1
  68. package/src/analyzers/injection-detector.ts +2 -1
  69. package/src/analyzers/mcp-server-analyzer.ts +2 -0
  70. package/src/analyzers/pii-detector.ts +2 -1
  71. package/src/analyzers/regex.ts +17 -0
  72. package/src/analyzers/secret-scanner.ts +2 -1
  73. package/src/cli/scan.ts +251 -0
  74. package/src/index.ts +11 -4
  75. package/src/notifications/email.ts +38 -194
  76. package/src/osv.ts +310 -0
  77. package/src/sarif.ts +146 -0
  78. package/src/scoring.ts +64 -0
  79. package/src/threat-intel-manager.ts +2 -1
  80. package/src/tools/configuration.ts +2 -1
  81. package/src/tools/mcp-scanning.ts +5 -0
  82. package/src/tools/monitoring.ts +23 -1
  83. package/src/version.ts +20 -0
  84. package/tests/analyzers/engine.test.ts +60 -0
  85. package/tests/analyzers/injection-and-code.test.ts +80 -0
  86. package/tests/analyzers/mcp-server-analyzer.test.ts +82 -0
  87. package/tests/analyzers/regex.test.ts +35 -0
  88. package/tests/analyzers/rules.test.ts +54 -0
  89. package/tests/analyzers/secret-scanner.test.ts +80 -0
  90. package/tests/cli/scan.test.ts +92 -0
  91. package/tests/helpers.ts +51 -0
  92. package/tests/notifications/email.test.ts +68 -0
  93. package/tests/notifications/hub.test.ts +160 -0
  94. package/tests/osv.test.ts +117 -0
  95. package/tests/sarif.test.ts +59 -0
  96. package/tests/scoring.test.ts +49 -0
  97. package/tests/tools/monitoring.test.ts +95 -0
  98. package/vitest.config.ts +8 -0
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Mund - The Guardian Protocol
3
+ * `mund-mcp scan` - scan files on disk and report as text, JSON or SARIF
4
+ *
5
+ * mund-mcp scan [paths...] [--format=text|json|sarif] [--output=FILE]
6
+ * [--fail-on=critical|high|medium|low|none]
7
+ * [--no-osv] [--offline]
8
+ */
9
+
10
+ import { readFile, writeFile, readdir, stat } from 'fs/promises';
11
+ import { basename, join, relative, resolve, sep } from 'path';
12
+ import type { SecurityIssue, Severity } from '../types.js';
13
+ import { DEFAULT_RULES } from '../constants.js';
14
+ import { AnalyzerEngine } from '../analyzers/index.js';
15
+ import { computeRiskScore, type RiskScore } from '../scoring.js';
16
+ import { toSarif, type SarifTarget } from '../sarif.js';
17
+ import { OsvClient, parseNpmLockfile, advisoriesToIssues, type OsvSource } from '../osv.js';
18
+ import { getVersion } from '../version.js';
19
+
20
+ export type OutputFormat = 'text' | 'json' | 'sarif';
21
+ type FailOn = Severity | 'none';
22
+
23
+ export interface ScanCliOptions {
24
+ paths: string[];
25
+ format: OutputFormat;
26
+ output?: string;
27
+ failOn: FailOn;
28
+ osv: boolean;
29
+ offline: boolean;
30
+ }
31
+
32
+ export interface FileReport extends SarifTarget {
33
+ risk: RiskScore;
34
+ }
35
+
36
+ export interface ScanReport {
37
+ tool: { name: 'mund'; version: string };
38
+ risk: RiskScore;
39
+ files_scanned: number;
40
+ osv?: { source: OsvSource; lockfiles: number; unchecked: number; error?: string };
41
+ files: FileReport[];
42
+ }
43
+
44
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.turbo', 'vendor', '__pycache__', '.venv']);
45
+ const LOCKFILES = new Set(['package-lock.json', 'npm-shrinkwrap.json']);
46
+ const MAX_FILE_BYTES = 1024 * 1024;
47
+ const SEVERITY_RANK: Record<Severity, number> = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
48
+
49
+ export const SCAN_USAGE = `Usage: mund-mcp scan [paths...] [options]
50
+
51
+ Scan files for secrets, PII, injection, exfiltration and dangerous code,
52
+ plus known-vulnerable npm dependencies (via OSV.dev) in package-lock.json.
53
+
54
+ Options:
55
+ --format=FORMAT text (default), json, or sarif
56
+ --output=FILE Write the report to FILE instead of stdout
57
+ --fail-on=LEVEL Exit 1 if any finding is at or above LEVEL
58
+ (critical, high [default], medium, low, none)
59
+ --no-osv Skip dependency vulnerability lookups
60
+ --offline Use only the local OSV cache (also: MUND_OFFLINE=1)
61
+
62
+ Examples:
63
+ mund-mcp scan .
64
+ mund-mcp scan src --format=sarif --output=mund.sarif
65
+ `;
66
+
67
+ export function parseScanArgs(args: string[]): ScanCliOptions {
68
+ const opts: ScanCliOptions = { paths: [], format: 'text', failOn: 'high', osv: true, offline: false };
69
+
70
+ for (let i = 0; i < args.length; i++) {
71
+ const arg = args[i];
72
+ const [flag, inline] = arg.startsWith('--') && arg.includes('=') ? [arg.slice(0, arg.indexOf('=')), arg.slice(arg.indexOf('=') + 1)] : [arg, undefined];
73
+ const value = () => {
74
+ const v = inline ?? args[++i];
75
+ if (v === undefined) throw new Error(`${flag} needs a value`);
76
+ return v;
77
+ };
78
+
79
+ switch (flag) {
80
+ case '--format': {
81
+ const v = value();
82
+ if (v !== 'text' && v !== 'json' && v !== 'sarif') throw new Error(`Unknown format "${v}" (expected text, json or sarif)`);
83
+ opts.format = v;
84
+ break;
85
+ }
86
+ case '--output':
87
+ case '-o':
88
+ opts.output = value();
89
+ break;
90
+ case '--fail-on': {
91
+ const v = value();
92
+ if (v !== 'none' && !(v in SEVERITY_RANK)) throw new Error(`Unknown --fail-on level "${v}"`);
93
+ opts.failOn = v as FailOn;
94
+ break;
95
+ }
96
+ case '--no-osv':
97
+ opts.osv = false;
98
+ break;
99
+ case '--offline':
100
+ opts.offline = true;
101
+ break;
102
+ default:
103
+ if (flag.startsWith('-')) throw new Error(`Unknown option ${flag}`);
104
+ opts.paths.push(arg);
105
+ }
106
+ }
107
+
108
+ if (opts.paths.length === 0) opts.paths.push('.');
109
+ return opts;
110
+ }
111
+
112
+ export async function runScan(opts: ScanCliOptions, osvClient?: OsvClient): Promise<ScanReport> {
113
+ const cwd = process.cwd();
114
+ const engine = new AnalyzerEngine();
115
+ const files: FileReport[] = [];
116
+ const lockfiles: string[] = [];
117
+ let scanned = 0;
118
+
119
+ for (const file of await collectFiles(opts.paths.map(p => resolve(cwd, p)))) {
120
+ if (LOCKFILES.has(basename(file))) {
121
+ lockfiles.push(file);
122
+ continue;
123
+ }
124
+ const content = await readText(file);
125
+ if (content === null) continue;
126
+ scanned++;
127
+ const issues = await engine.analyzeAll(content, DEFAULT_RULES);
128
+ if (issues.length > 0) files.push({ uri: toUri(cwd, file), issues, risk: computeRiskScore(issues) });
129
+ }
130
+
131
+ let osv: ScanReport['osv'];
132
+ if (opts.osv && lockfiles.length > 0) {
133
+ const client = osvClient ?? new OsvClient({ offline: opts.offline });
134
+ let source: OsvSource = 'live';
135
+ let unchecked = 0;
136
+ let error: string | undefined;
137
+
138
+ for (const lockfile of lockfiles) {
139
+ const content = await readFile(lockfile, 'utf8');
140
+ let packages;
141
+ try {
142
+ packages = parseNpmLockfile(content);
143
+ } catch {
144
+ continue; // malformed lockfile; nothing to look up
145
+ }
146
+ const result = await client.lookup(packages);
147
+ // Report the weakest source across lockfiles.
148
+ if (result.source === 'unavailable' || (result.source === 'cache' && source === 'live')) source = result.source;
149
+ unchecked += result.unchecked.length;
150
+ error ??= result.error;
151
+
152
+ const issues = advisoriesToIssues(packages, result, content);
153
+ if (issues.length > 0) files.push({ uri: toUri(cwd, lockfile), issues, risk: computeRiskScore(issues) });
154
+ }
155
+ osv = { source, lockfiles: lockfiles.length, unchecked, ...(error ? { error } : {}) };
156
+ }
157
+
158
+ return {
159
+ tool: { name: 'mund', version: getVersion() },
160
+ risk: computeRiskScore(files.flatMap(f => f.issues)),
161
+ files_scanned: scanned,
162
+ ...(osv ? { osv } : {}),
163
+ files,
164
+ };
165
+ }
166
+
167
+ export function formatReport(report: ScanReport, format: OutputFormat): string {
168
+ if (format === 'json') return JSON.stringify(report, null, 2);
169
+ if (format === 'sarif') return JSON.stringify(toSarif(report.files, { toolVersion: report.tool.version }), null, 2);
170
+
171
+ const lines: string[] = [];
172
+ for (const file of report.files) {
173
+ lines.push(`${file.uri} (risk ${file.risk.score}/100, ${file.risk.label})`);
174
+ for (const issue of file.issues) {
175
+ const where = issue.location?.line ? `${issue.location.line}:${issue.location.column ?? 1}` : '-';
176
+ lines.push(` ${where.padEnd(8)} ${issue.severity.padEnd(8)} ${issue.rule_name}`);
177
+ }
178
+ lines.push('');
179
+ }
180
+
181
+ const total = report.files.reduce((n, f) => n + f.issues.length, 0);
182
+ const { counts } = report.risk;
183
+ lines.push(`Scanned ${report.files_scanned} file(s): ${total} finding(s) ` +
184
+ `(${counts.critical} critical, ${counts.high} high, ${counts.medium} medium, ${counts.low} low)`);
185
+ lines.push(`Risk score: ${report.risk.score}/100 (${report.risk.label})`);
186
+ if (report.osv) {
187
+ const note = report.osv.source === 'live' ? 'live' : `${report.osv.source} - ${report.osv.error ?? ''}`.trim();
188
+ lines.push(`Dependencies: OSV.dev (${note})` + (report.osv.unchecked ? `, ${report.osv.unchecked} package(s) unchecked` : ''));
189
+ }
190
+ return lines.join('\n');
191
+ }
192
+
193
+ export function shouldFail(report: ScanReport, failOn: FailOn): boolean {
194
+ if (failOn === 'none') return false;
195
+ const threshold = SEVERITY_RANK[failOn];
196
+ return report.files.some(f => f.issues.some((i: SecurityIssue) => SEVERITY_RANK[i.severity] <= threshold));
197
+ }
198
+
199
+ /** Entry point for `mund-mcp scan`. Returns the process exit code. */
200
+ export async function runScanCli(args: string[]): Promise<number> {
201
+ if (args.includes('--help') || args.includes('-h')) {
202
+ process.stdout.write(SCAN_USAGE);
203
+ return 0;
204
+ }
205
+
206
+ let opts: ScanCliOptions;
207
+ try {
208
+ opts = parseScanArgs(args);
209
+ } catch (error) {
210
+ process.stderr.write(`mund scan: ${(error as Error).message}\n\n${SCAN_USAGE}`);
211
+ return 2;
212
+ }
213
+
214
+ const report = await runScan(opts);
215
+ const out = formatReport(report, opts.format) + '\n';
216
+ if (opts.output) await writeFile(opts.output, out);
217
+ else process.stdout.write(out);
218
+
219
+ return shouldFail(report, opts.failOn) ? 1 : 0;
220
+ }
221
+
222
+ async function collectFiles(roots: string[]): Promise<string[]> {
223
+ const out: string[] = [];
224
+ const walk = async (path: string) => {
225
+ const info = await stat(path).catch(() => null);
226
+ if (!info) return;
227
+ if (info.isFile()) {
228
+ out.push(path);
229
+ } else if (info.isDirectory()) {
230
+ for (const entry of await readdir(path, { withFileTypes: true })) {
231
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
232
+ if (entry.isDirectory() || entry.isFile()) await walk(join(path, entry.name));
233
+ }
234
+ }
235
+ };
236
+ for (const root of roots) await walk(root);
237
+ return out.sort();
238
+ }
239
+
240
+ /** Read a file as UTF-8 text, or null if it's too big or looks binary. */
241
+ async function readText(file: string): Promise<string | null> {
242
+ const info = await stat(file);
243
+ if (info.size > MAX_FILE_BYTES) return null;
244
+ const buf = await readFile(file);
245
+ if (buf.subarray(0, 8000).includes(0)) return null;
246
+ return buf.toString('utf8');
247
+ }
248
+
249
+ function toUri(cwd: string, file: string): string {
250
+ return relative(cwd, file).split(sep).join('/');
251
+ }
package/src/index.ts CHANGED
@@ -15,6 +15,8 @@ import { createStorage } from './storage/index.js';
15
15
  import { AnalyzerEngine } from './analyzers/index.js';
16
16
  import { NotificationHub } from './notifications/index.js';
17
17
  import { registerAllTools } from './tools/index.js';
18
+ import { runScanCli } from './cli/scan.js';
19
+ import { getVersion } from './version.js';
18
20
 
19
21
  // Threat Intelligence imports
20
22
  import { threatIntel } from './threat-intel-manager.js';
@@ -154,7 +156,7 @@ async function createMundServer(config: MundConfig): Promise<McpServer> {
154
156
  // Create MCP server
155
157
  const server = new McpServer({
156
158
  name: 'mund-mcp-server',
157
- version: '0.2.0'
159
+ version: getVersion()
158
160
  });
159
161
 
160
162
  // Initialize storage
@@ -218,7 +220,7 @@ async function runHTTP(config: MundConfig): Promise<void> {
218
220
 
219
221
  // Health check endpoint
220
222
  app.get('/health', (_req, res) => {
221
- res.json({ status: 'ok', version: '0.2.0' });
223
+ res.json({ status: 'ok', version: getVersion() });
222
224
  });
223
225
 
224
226
  // Threat intel status endpoint
@@ -254,7 +256,8 @@ function printHelp(): void {
254
256
  Mund - The Guardian Protocol
255
257
  MCP-based security monitoring for agentic AI systems
256
258
 
257
- Usage: mund-mcp [options]
259
+ Usage: mund-mcp [options] Run the MCP server
260
+ mund-mcp scan [paths...] Scan files (see: mund-mcp scan --help)
258
261
 
259
262
  Options:
260
263
  --help, -h Show this help message
@@ -314,6 +317,10 @@ For more information, visit: https://github.com/Tyox-all/Weave_Protocol
314
317
  async function main(): Promise<void> {
315
318
  const args = process.argv.slice(2);
316
319
 
320
+ if (args[0] === 'scan') {
321
+ process.exit(await runScanCli(args.slice(1)));
322
+ }
323
+
317
324
  // Parse CLI arguments
318
325
  if (args.includes('--help') || args.includes('-h')) {
319
326
  printHelp();
@@ -321,7 +328,7 @@ async function main(): Promise<void> {
321
328
  }
322
329
 
323
330
  if (args.includes('--version') || args.includes('-v')) {
324
- console.log('0.2.0');
331
+ console.log(getVersion());
325
332
  process.exit(0);
326
333
  }
327
334
 
@@ -1,210 +1,54 @@
1
1
  /**
2
2
  * Mund - The Guardian Protocol
3
- * Email Notifier - Send alerts via email
3
+ * Email Notifier - NOT IMPLEMENTED in v0.2.x.
4
+ *
5
+ * The nodemailer dependency was removed because it carried a
6
+ * high-severity CVE and was never actually wired up (the previous
7
+ * revision had only comments like "In a real implementation, use
8
+ * nodemailer here"). Carrying a prod dep for a feature nobody uses
9
+ * meant inheriting its CVE for no benefit.
10
+ *
11
+ * This stub keeps the INotifier contract so NotificationManager can
12
+ * still wire it up when `notificationConfig.email.smtp_host` is set,
13
+ * but every send() call returns false and logs a one-time warning,
14
+ * so operators see email alerts aren't actually being delivered.
15
+ *
16
+ * When/if email notifications become a real feature:
17
+ * 1. npm install nodemailer@^10 @types/nodemailer
18
+ * 2. Replace this stub with a real implementation
19
+ * 3. Add test fixtures under test/notifications/
20
+ * 4. Remove the console.warn in the constructor
4
21
  */
5
22
 
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
23
+ import type { INotifier, NotificationPayload, EmailConfig } from '../types.js';
10
24
 
11
25
  export class EmailNotifier implements INotifier {
12
26
  name = 'EmailNotifier';
13
- private config: EmailConfig;
27
+ // Config is retained so a future real implementation can see what
28
+ // shape to expect. Public (not private) because noUnusedLocals flags
29
+ // unread private members even with an underscore prefix.
30
+ readonly config: EmailConfig;
31
+ private warned = false;
14
32
 
15
33
  constructor(config: EmailConfig) {
16
34
  this.config = config;
35
+ console.warn(
36
+ '[mund] EmailNotifier configured but NOT IMPLEMENTED in this version. ' +
37
+ 'Email alerts will be dropped. Use a webhook, Slack, or Teams notifier ' +
38
+ 'for actual delivery. Track at ' +
39
+ 'https://github.com/Tyox-all/Weave_Protocol/issues',
40
+ );
17
41
  }
18
42
 
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;
43
+ async send(_payload: NotificationPayload): Promise<boolean> {
44
+ if (!this.warned) {
45
+ console.error(
46
+ '[mund] EmailNotifier.send() called but email delivery is not ' +
47
+ 'implemented. Alert dropped. (This warning fires once per process.)',
48
+ );
49
+ this.warned = true;
60
50
  }
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;
51
+ return false;
208
52
  }
209
53
  }
210
54