aura-security 0.4.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +446 -0
  3. package/deploy/AWS-DEPLOYMENT.md +358 -0
  4. package/deploy/terraform/main.tf +362 -0
  5. package/deploy/terraform/terraform.tfvars.example +6 -0
  6. package/dist/agents/base.d.ts +44 -0
  7. package/dist/agents/base.js +96 -0
  8. package/dist/agents/index.d.ts +14 -0
  9. package/dist/agents/index.js +17 -0
  10. package/dist/agents/policy/evaluator.d.ts +15 -0
  11. package/dist/agents/policy/evaluator.js +183 -0
  12. package/dist/agents/policy/index.d.ts +12 -0
  13. package/dist/agents/policy/index.js +15 -0
  14. package/dist/agents/policy/validator.d.ts +15 -0
  15. package/dist/agents/policy/validator.js +182 -0
  16. package/dist/agents/scanners/gitleaks.d.ts +14 -0
  17. package/dist/agents/scanners/gitleaks.js +155 -0
  18. package/dist/agents/scanners/grype.d.ts +14 -0
  19. package/dist/agents/scanners/grype.js +109 -0
  20. package/dist/agents/scanners/index.d.ts +15 -0
  21. package/dist/agents/scanners/index.js +27 -0
  22. package/dist/agents/scanners/npm-audit.d.ts +13 -0
  23. package/dist/agents/scanners/npm-audit.js +129 -0
  24. package/dist/agents/scanners/semgrep.d.ts +14 -0
  25. package/dist/agents/scanners/semgrep.js +131 -0
  26. package/dist/agents/scanners/trivy.d.ts +14 -0
  27. package/dist/agents/scanners/trivy.js +122 -0
  28. package/dist/agents/types.d.ts +137 -0
  29. package/dist/agents/types.js +91 -0
  30. package/dist/auditor/index.d.ts +3 -0
  31. package/dist/auditor/index.js +2 -0
  32. package/dist/auditor/pipeline.d.ts +19 -0
  33. package/dist/auditor/pipeline.js +240 -0
  34. package/dist/auditor/validator.d.ts +17 -0
  35. package/dist/auditor/validator.js +58 -0
  36. package/dist/aura/client.d.ts +29 -0
  37. package/dist/aura/client.js +125 -0
  38. package/dist/aura/index.d.ts +4 -0
  39. package/dist/aura/index.js +2 -0
  40. package/dist/aura/server.d.ts +45 -0
  41. package/dist/aura/server.js +343 -0
  42. package/dist/cli.d.ts +17 -0
  43. package/dist/cli.js +1433 -0
  44. package/dist/client/index.d.ts +41 -0
  45. package/dist/client/index.js +170 -0
  46. package/dist/compliance/index.d.ts +40 -0
  47. package/dist/compliance/index.js +292 -0
  48. package/dist/database/index.d.ts +77 -0
  49. package/dist/database/index.js +395 -0
  50. package/dist/index.d.ts +25 -0
  51. package/dist/index.js +762 -0
  52. package/dist/integrations/aura-scanner.d.ts +69 -0
  53. package/dist/integrations/aura-scanner.js +155 -0
  54. package/dist/integrations/aws-scanner.d.ts +63 -0
  55. package/dist/integrations/aws-scanner.js +624 -0
  56. package/dist/integrations/config.d.ts +69 -0
  57. package/dist/integrations/config.js +212 -0
  58. package/dist/integrations/github.d.ts +45 -0
  59. package/dist/integrations/github.js +201 -0
  60. package/dist/integrations/gitlab.d.ts +36 -0
  61. package/dist/integrations/gitlab.js +110 -0
  62. package/dist/integrations/index.d.ts +11 -0
  63. package/dist/integrations/index.js +11 -0
  64. package/dist/integrations/local-scanner.d.ts +146 -0
  65. package/dist/integrations/local-scanner.js +1654 -0
  66. package/dist/integrations/notifications.d.ts +99 -0
  67. package/dist/integrations/notifications.js +305 -0
  68. package/dist/integrations/scanners.d.ts +57 -0
  69. package/dist/integrations/scanners.js +217 -0
  70. package/dist/integrations/slop-scanner.d.ts +69 -0
  71. package/dist/integrations/slop-scanner.js +155 -0
  72. package/dist/integrations/webhook.d.ts +37 -0
  73. package/dist/integrations/webhook.js +256 -0
  74. package/dist/orchestrator/index.d.ts +72 -0
  75. package/dist/orchestrator/index.js +187 -0
  76. package/dist/output/index.d.ts +152 -0
  77. package/dist/output/index.js +399 -0
  78. package/dist/pipeline/index.d.ts +72 -0
  79. package/dist/pipeline/index.js +313 -0
  80. package/dist/sbom/index.d.ts +94 -0
  81. package/dist/sbom/index.js +298 -0
  82. package/dist/schemas/index.d.ts +2 -0
  83. package/dist/schemas/index.js +2 -0
  84. package/dist/schemas/input.schema.d.ts +87 -0
  85. package/dist/schemas/input.schema.js +44 -0
  86. package/dist/schemas/output.schema.d.ts +115 -0
  87. package/dist/schemas/output.schema.js +64 -0
  88. package/dist/serve-visualizer.d.ts +2 -0
  89. package/dist/serve-visualizer.js +78 -0
  90. package/dist/slop/client.d.ts +29 -0
  91. package/dist/slop/client.js +125 -0
  92. package/dist/slop/index.d.ts +4 -0
  93. package/dist/slop/index.js +2 -0
  94. package/dist/slop/server.d.ts +45 -0
  95. package/dist/slop/server.js +343 -0
  96. package/dist/types/events.d.ts +62 -0
  97. package/dist/types/events.js +2 -0
  98. package/dist/types/index.d.ts +1 -0
  99. package/dist/types/index.js +1 -0
  100. package/dist/visualizer/index.d.ts +4 -0
  101. package/dist/visualizer/index.js +181 -0
  102. package/dist/websocket/index.d.ts +88 -0
  103. package/dist/websocket/index.js +195 -0
  104. package/dist/zones/index.d.ts +7 -0
  105. package/dist/zones/index.js +7 -0
  106. package/dist/zones/manager.d.ts +101 -0
  107. package/dist/zones/manager.js +304 -0
  108. package/dist/zones/types.d.ts +78 -0
  109. package/dist/zones/types.js +33 -0
  110. package/package.json +84 -0
  111. package/visualizer/app.js +0 -0
  112. package/visualizer/index-minimal.html +1771 -0
  113. package/visualizer/index.html +2933 -0
  114. package/visualizer/landing.html +1328 -0
  115. package/visualizer/styles.css +0 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Notification Integrations for aurasecurity
3
+ *
4
+ * Supports:
5
+ * - Slack (webhooks + bot API)
6
+ * - Discord (webhooks)
7
+ * - Email (SMTP)
8
+ * - Custom webhooks
9
+ */
10
+ export interface NotificationConfig {
11
+ slack?: {
12
+ webhookUrl?: string;
13
+ botToken?: string;
14
+ channel?: string;
15
+ enabled: boolean;
16
+ };
17
+ discord?: {
18
+ webhookUrl?: string;
19
+ enabled: boolean;
20
+ };
21
+ email?: {
22
+ smtp: {
23
+ host: string;
24
+ port: number;
25
+ secure: boolean;
26
+ user: string;
27
+ pass: string;
28
+ };
29
+ from: string;
30
+ to: string[];
31
+ enabled: boolean;
32
+ };
33
+ webhook?: {
34
+ url: string;
35
+ headers?: Record<string, string>;
36
+ enabled: boolean;
37
+ };
38
+ }
39
+ export interface NotificationPayload {
40
+ title: string;
41
+ message: string;
42
+ severity: 'low' | 'medium' | 'high' | 'critical';
43
+ auditId?: string;
44
+ target?: string;
45
+ findings?: {
46
+ critical: number;
47
+ high: number;
48
+ medium: number;
49
+ low: number;
50
+ };
51
+ link?: string;
52
+ }
53
+ export declare class NotificationService {
54
+ private config;
55
+ private dbPath?;
56
+ constructor(config?: NotificationConfig, dbPath?: string);
57
+ /**
58
+ * Load configuration from database settings
59
+ */
60
+ loadFromDatabase(): void;
61
+ /**
62
+ * Send notification to all configured channels
63
+ */
64
+ notify(payload: NotificationPayload): Promise<{
65
+ sent: string[];
66
+ failed: string[];
67
+ }>;
68
+ /**
69
+ * Send to Slack webhook
70
+ */
71
+ private sendSlack;
72
+ /**
73
+ * Send to Discord webhook
74
+ */
75
+ private sendDiscord;
76
+ /**
77
+ * Send to custom webhook
78
+ */
79
+ private sendWebhook;
80
+ /**
81
+ * Test a specific notification channel
82
+ */
83
+ testChannel(channel: 'slack' | 'discord' | 'webhook'): Promise<{
84
+ success: boolean;
85
+ error?: string;
86
+ }>;
87
+ private getSeverityColor;
88
+ private getSeverityColorInt;
89
+ private getSeverityEmoji;
90
+ }
91
+ /**
92
+ * Create notification from scan result
93
+ */
94
+ export declare function createNotificationFromAudit(auditId: string, type: string, target: string, summary: {
95
+ critical: number;
96
+ high: number;
97
+ medium: number;
98
+ low: number;
99
+ }, baseUrl?: string): NotificationPayload;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Notification Integrations for aurasecurity
3
+ *
4
+ * Supports:
5
+ * - Slack (webhooks + bot API)
6
+ * - Discord (webhooks)
7
+ * - Email (SMTP)
8
+ * - Custom webhooks
9
+ */
10
+ import { getDatabase } from '../database/index.js';
11
+ export class NotificationService {
12
+ config;
13
+ dbPath;
14
+ constructor(config = {}, dbPath) {
15
+ this.config = config;
16
+ this.dbPath = dbPath;
17
+ }
18
+ /**
19
+ * Load configuration from database settings
20
+ */
21
+ loadFromDatabase() {
22
+ if (!this.dbPath)
23
+ return;
24
+ const db = getDatabase(this.dbPath);
25
+ const settings = db.getSettings('notifications.');
26
+ // Parse Slack settings
27
+ if (settings['notifications.slack.enabled'] === 'true') {
28
+ this.config.slack = {
29
+ enabled: true,
30
+ webhookUrl: settings['notifications.slack.webhookUrl'],
31
+ botToken: settings['notifications.slack.botToken'],
32
+ channel: settings['notifications.slack.channel']
33
+ };
34
+ }
35
+ // Parse Discord settings
36
+ if (settings['notifications.discord.enabled'] === 'true') {
37
+ this.config.discord = {
38
+ enabled: true,
39
+ webhookUrl: settings['notifications.discord.webhookUrl']
40
+ };
41
+ }
42
+ // Parse webhook settings
43
+ if (settings['notifications.webhook.enabled'] === 'true') {
44
+ this.config.webhook = {
45
+ enabled: true,
46
+ url: settings['notifications.webhook.url'],
47
+ headers: settings['notifications.webhook.headers']
48
+ ? JSON.parse(settings['notifications.webhook.headers'])
49
+ : undefined
50
+ };
51
+ }
52
+ }
53
+ /**
54
+ * Send notification to all configured channels
55
+ */
56
+ async notify(payload) {
57
+ const results = { sent: [], failed: [] };
58
+ // Try each enabled channel
59
+ const promises = [];
60
+ if (this.config.slack?.enabled && this.config.slack.webhookUrl) {
61
+ promises.push(this.sendSlack(payload)
62
+ .then(() => { results.sent.push('slack'); })
63
+ .catch((err) => {
64
+ console.error('[NOTIFY] Slack failed:', err.message);
65
+ results.failed.push('slack');
66
+ }));
67
+ }
68
+ if (this.config.discord?.enabled && this.config.discord.webhookUrl) {
69
+ promises.push(this.sendDiscord(payload)
70
+ .then(() => { results.sent.push('discord'); })
71
+ .catch((err) => {
72
+ console.error('[NOTIFY] Discord failed:', err.message);
73
+ results.failed.push('discord');
74
+ }));
75
+ }
76
+ if (this.config.webhook?.enabled && this.config.webhook.url) {
77
+ promises.push(this.sendWebhook(payload)
78
+ .then(() => { results.sent.push('webhook'); })
79
+ .catch((err) => {
80
+ console.error('[NOTIFY] Webhook failed:', err.message);
81
+ results.failed.push('webhook');
82
+ }));
83
+ }
84
+ await Promise.all(promises);
85
+ // Record in database
86
+ if (this.dbPath) {
87
+ try {
88
+ const db = getDatabase(this.dbPath);
89
+ db.recordNotification(payload.auditId || 'manual', results.sent.join(','), results.failed.length === 0);
90
+ }
91
+ catch (err) {
92
+ console.error('[NOTIFY] Failed to record notification:', err);
93
+ }
94
+ }
95
+ return results;
96
+ }
97
+ /**
98
+ * Send to Slack webhook
99
+ */
100
+ async sendSlack(payload) {
101
+ const webhookUrl = this.config.slack?.webhookUrl;
102
+ if (!webhookUrl)
103
+ throw new Error('Slack webhook URL not configured');
104
+ const color = this.getSeverityColor(payload.severity);
105
+ const emoji = this.getSeverityEmoji(payload.severity);
106
+ const slackPayload = {
107
+ attachments: [{
108
+ color,
109
+ blocks: [
110
+ {
111
+ type: 'header',
112
+ text: {
113
+ type: 'plain_text',
114
+ text: `${emoji} ${payload.title}`,
115
+ emoji: true
116
+ }
117
+ },
118
+ {
119
+ type: 'section',
120
+ text: {
121
+ type: 'mrkdwn',
122
+ text: payload.message
123
+ }
124
+ },
125
+ ...(payload.findings ? [{
126
+ type: 'section',
127
+ fields: [
128
+ { type: 'mrkdwn', text: `*Critical:* ${payload.findings.critical}` },
129
+ { type: 'mrkdwn', text: `*High:* ${payload.findings.high}` },
130
+ { type: 'mrkdwn', text: `*Medium:* ${payload.findings.medium}` },
131
+ { type: 'mrkdwn', text: `*Low:* ${payload.findings.low}` }
132
+ ]
133
+ }] : []),
134
+ ...(payload.target ? [{
135
+ type: 'context',
136
+ elements: [{
137
+ type: 'mrkdwn',
138
+ text: `📁 Target: \`${payload.target}\``
139
+ }]
140
+ }] : []),
141
+ ...(payload.link ? [{
142
+ type: 'actions',
143
+ elements: [{
144
+ type: 'button',
145
+ text: { type: 'plain_text', text: 'View Details' },
146
+ url: payload.link,
147
+ action_id: 'view_audit'
148
+ }]
149
+ }] : [])
150
+ ]
151
+ }]
152
+ };
153
+ const response = await fetch(webhookUrl, {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify(slackPayload)
157
+ });
158
+ if (!response.ok) {
159
+ throw new Error(`Slack returned ${response.status}: ${await response.text()}`);
160
+ }
161
+ console.log('[NOTIFY] Slack notification sent');
162
+ }
163
+ /**
164
+ * Send to Discord webhook
165
+ */
166
+ async sendDiscord(payload) {
167
+ const webhookUrl = this.config.discord?.webhookUrl;
168
+ if (!webhookUrl)
169
+ throw new Error('Discord webhook URL not configured');
170
+ const color = this.getSeverityColorInt(payload.severity);
171
+ const emoji = this.getSeverityEmoji(payload.severity);
172
+ const discordPayload = {
173
+ embeds: [{
174
+ title: `${emoji} ${payload.title}`,
175
+ description: payload.message,
176
+ color,
177
+ fields: payload.findings ? [
178
+ { name: '🔴 Critical', value: String(payload.findings.critical), inline: true },
179
+ { name: '🟠 High', value: String(payload.findings.high), inline: true },
180
+ { name: '🟡 Medium', value: String(payload.findings.medium), inline: true },
181
+ { name: '🟢 Low', value: String(payload.findings.low), inline: true }
182
+ ] : [],
183
+ footer: payload.target ? { text: `Target: ${payload.target}` } : undefined,
184
+ timestamp: new Date().toISOString()
185
+ }]
186
+ };
187
+ const response = await fetch(webhookUrl, {
188
+ method: 'POST',
189
+ headers: { 'Content-Type': 'application/json' },
190
+ body: JSON.stringify(discordPayload)
191
+ });
192
+ if (!response.ok) {
193
+ throw new Error(`Discord returned ${response.status}: ${await response.text()}`);
194
+ }
195
+ console.log('[NOTIFY] Discord notification sent');
196
+ }
197
+ /**
198
+ * Send to custom webhook
199
+ */
200
+ async sendWebhook(payload) {
201
+ const webhookConfig = this.config.webhook;
202
+ if (!webhookConfig?.url)
203
+ throw new Error('Webhook URL not configured');
204
+ const response = await fetch(webhookConfig.url, {
205
+ method: 'POST',
206
+ headers: {
207
+ 'Content-Type': 'application/json',
208
+ ...webhookConfig.headers
209
+ },
210
+ body: JSON.stringify({
211
+ event: 'audit_complete',
212
+ timestamp: new Date().toISOString(),
213
+ ...payload
214
+ })
215
+ });
216
+ if (!response.ok) {
217
+ throw new Error(`Webhook returned ${response.status}: ${await response.text()}`);
218
+ }
219
+ console.log('[NOTIFY] Custom webhook notification sent');
220
+ }
221
+ /**
222
+ * Test a specific notification channel
223
+ */
224
+ async testChannel(channel) {
225
+ const testPayload = {
226
+ title: 'aurasecurity Test',
227
+ message: 'This is a test notification from aurasecurity.',
228
+ severity: 'low',
229
+ findings: { critical: 0, high: 0, medium: 1, low: 2 },
230
+ target: 'test'
231
+ };
232
+ try {
233
+ switch (channel) {
234
+ case 'slack':
235
+ await this.sendSlack(testPayload);
236
+ break;
237
+ case 'discord':
238
+ await this.sendDiscord(testPayload);
239
+ break;
240
+ case 'webhook':
241
+ await this.sendWebhook(testPayload);
242
+ break;
243
+ }
244
+ return { success: true };
245
+ }
246
+ catch (err) {
247
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
248
+ }
249
+ }
250
+ getSeverityColor(severity) {
251
+ switch (severity) {
252
+ case 'critical': return '#ff0000';
253
+ case 'high': return '#ff6600';
254
+ case 'medium': return '#ffcc00';
255
+ case 'low': return '#00cc00';
256
+ default: return '#666666';
257
+ }
258
+ }
259
+ getSeverityColorInt(severity) {
260
+ switch (severity) {
261
+ case 'critical': return 0xff0000;
262
+ case 'high': return 0xff6600;
263
+ case 'medium': return 0xffcc00;
264
+ case 'low': return 0x00cc00;
265
+ default: return 0x666666;
266
+ }
267
+ }
268
+ getSeverityEmoji(severity) {
269
+ switch (severity) {
270
+ case 'critical': return '🚨';
271
+ case 'high': return '⚠️';
272
+ case 'medium': return '⚡';
273
+ case 'low': return 'ℹ️';
274
+ default: return '🔍';
275
+ }
276
+ }
277
+ }
278
+ /**
279
+ * Create notification from scan result
280
+ */
281
+ export function createNotificationFromAudit(auditId, type, target, summary, baseUrl) {
282
+ const total = summary.critical + summary.high + summary.medium + summary.low;
283
+ let severity = 'low';
284
+ if (summary.critical > 0)
285
+ severity = 'critical';
286
+ else if (summary.high > 0)
287
+ severity = 'high';
288
+ else if (summary.medium > 0)
289
+ severity = 'medium';
290
+ const title = total > 0
291
+ ? `Security Scan Found ${total} Issue${total > 1 ? 's' : ''}`
292
+ : 'Security Scan Complete';
293
+ const message = total > 0
294
+ ? `A ${type} scan of \`${target}\` found security issues that require attention.`
295
+ : `A ${type} scan of \`${target}\` completed with no findings.`;
296
+ return {
297
+ title,
298
+ message,
299
+ severity,
300
+ auditId,
301
+ target,
302
+ findings: summary,
303
+ link: baseUrl ? `${baseUrl}/#audit/${auditId}` : undefined
304
+ };
305
+ }
@@ -0,0 +1,57 @@
1
+ import type { EvidenceBundle } from '../types/events.js';
2
+ export interface VulnerabilityFinding {
3
+ id: string;
4
+ severity: 'critical' | 'high' | 'medium' | 'low';
5
+ title: string;
6
+ description?: string;
7
+ package?: string;
8
+ version?: string;
9
+ fixedIn?: string;
10
+ cve?: string;
11
+ cwes?: string[];
12
+ file?: string;
13
+ line?: number;
14
+ }
15
+ export interface ScanResult {
16
+ scanner: string;
17
+ timestamp: string;
18
+ findings: VulnerabilityFinding[];
19
+ summary: {
20
+ critical: number;
21
+ high: number;
22
+ medium: number;
23
+ low: number;
24
+ };
25
+ raw?: string;
26
+ }
27
+ export declare abstract class ScannerParser {
28
+ abstract name: string;
29
+ abstract parse(input: string | object): ScanResult;
30
+ toEvidenceBundle(result: ScanResult): Partial<EvidenceBundle>;
31
+ protected normalizeSeverity(sev: string): VulnerabilityFinding['severity'];
32
+ }
33
+ export declare class SnykParser extends ScannerParser {
34
+ name: string;
35
+ parse(input: string | object): ScanResult;
36
+ private summarize;
37
+ }
38
+ export declare class TrivyParser extends ScannerParser {
39
+ name: string;
40
+ parse(input: string | object): ScanResult;
41
+ private summarize;
42
+ }
43
+ export declare class SemgrepParser extends ScannerParser {
44
+ name: string;
45
+ parse(input: string | object): ScanResult;
46
+ private summarize;
47
+ }
48
+ export declare class NpmAuditParser extends ScannerParser {
49
+ name: string;
50
+ parse(input: string | object): ScanResult;
51
+ private summarize;
52
+ }
53
+ export declare class GenericParser extends ScannerParser {
54
+ name: string;
55
+ parse(input: string | object): ScanResult;
56
+ }
57
+ export declare function getParser(scannerName: string): ScannerParser;
@@ -0,0 +1,217 @@
1
+ // Scanner Parsers - Parse output from security scanning tools
2
+ // Supports: Snyk, Trivy, Semgrep, npm audit, and custom formats
3
+ export class ScannerParser {
4
+ toEvidenceBundle(result) {
5
+ const vulnScan = `critical: ${result.summary.critical}\nhigh: ${result.summary.high}\nmedium: ${result.summary.medium}\nlow: ${result.summary.low}`;
6
+ return { vuln_scan: vulnScan };
7
+ }
8
+ normalizeSeverity(sev) {
9
+ const s = sev.toLowerCase();
10
+ if (s === 'critical' || s === 'crit')
11
+ return 'critical';
12
+ if (s === 'high' || s === 'h')
13
+ return 'high';
14
+ if (s === 'medium' || s === 'med' || s === 'moderate')
15
+ return 'medium';
16
+ return 'low';
17
+ }
18
+ }
19
+ // Snyk JSON output parser
20
+ export class SnykParser extends ScannerParser {
21
+ name = 'snyk';
22
+ parse(input) {
23
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
24
+ const findings = [];
25
+ const vulnerabilities = data.vulnerabilities || [];
26
+ for (const vuln of vulnerabilities) {
27
+ findings.push({
28
+ id: vuln.id,
29
+ severity: this.normalizeSeverity(vuln.severity),
30
+ title: vuln.title,
31
+ description: vuln.description,
32
+ package: vuln.packageName || vuln.moduleName,
33
+ version: vuln.version,
34
+ fixedIn: vuln.fixedIn?.[0],
35
+ cve: vuln.identifiers?.CVE?.[0],
36
+ cwes: vuln.identifiers?.CWE
37
+ });
38
+ }
39
+ return {
40
+ scanner: this.name,
41
+ timestamp: new Date().toISOString(),
42
+ findings,
43
+ summary: this.summarize(findings),
44
+ raw: typeof input === 'string' ? input : JSON.stringify(input)
45
+ };
46
+ }
47
+ summarize(findings) {
48
+ return {
49
+ critical: findings.filter(f => f.severity === 'critical').length,
50
+ high: findings.filter(f => f.severity === 'high').length,
51
+ medium: findings.filter(f => f.severity === 'medium').length,
52
+ low: findings.filter(f => f.severity === 'low').length
53
+ };
54
+ }
55
+ }
56
+ // Trivy JSON output parser
57
+ export class TrivyParser extends ScannerParser {
58
+ name = 'trivy';
59
+ parse(input) {
60
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
61
+ const findings = [];
62
+ const results = data.Results || [];
63
+ for (const result of results) {
64
+ const vulns = result.Vulnerabilities || [];
65
+ for (const vuln of vulns) {
66
+ findings.push({
67
+ id: vuln.VulnerabilityID,
68
+ severity: this.normalizeSeverity(vuln.Severity),
69
+ title: vuln.Title || vuln.VulnerabilityID,
70
+ description: vuln.Description,
71
+ package: vuln.PkgName,
72
+ version: vuln.InstalledVersion,
73
+ fixedIn: vuln.FixedVersion,
74
+ cve: vuln.VulnerabilityID.startsWith('CVE') ? vuln.VulnerabilityID : undefined,
75
+ cwes: vuln.CweIDs
76
+ });
77
+ }
78
+ }
79
+ return {
80
+ scanner: this.name,
81
+ timestamp: new Date().toISOString(),
82
+ findings,
83
+ summary: this.summarize(findings),
84
+ raw: typeof input === 'string' ? input : JSON.stringify(input)
85
+ };
86
+ }
87
+ summarize(findings) {
88
+ return {
89
+ critical: findings.filter(f => f.severity === 'critical').length,
90
+ high: findings.filter(f => f.severity === 'high').length,
91
+ medium: findings.filter(f => f.severity === 'medium').length,
92
+ low: findings.filter(f => f.severity === 'low').length
93
+ };
94
+ }
95
+ }
96
+ // Semgrep JSON output parser (SAST)
97
+ export class SemgrepParser extends ScannerParser {
98
+ name = 'semgrep';
99
+ parse(input) {
100
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
101
+ const findings = [];
102
+ const results = data.results || [];
103
+ for (const result of results) {
104
+ findings.push({
105
+ id: result.check_id,
106
+ severity: this.normalizeSeverity(result.extra?.severity || 'medium'),
107
+ title: result.extra?.message || result.check_id,
108
+ description: result.extra?.metadata?.description,
109
+ file: result.path,
110
+ line: result.start?.line,
111
+ cwes: result.extra?.metadata?.cwe ? [result.extra.metadata.cwe] : undefined
112
+ });
113
+ }
114
+ return {
115
+ scanner: this.name,
116
+ timestamp: new Date().toISOString(),
117
+ findings,
118
+ summary: this.summarize(findings),
119
+ raw: typeof input === 'string' ? input : JSON.stringify(input)
120
+ };
121
+ }
122
+ summarize(findings) {
123
+ return {
124
+ critical: findings.filter(f => f.severity === 'critical').length,
125
+ high: findings.filter(f => f.severity === 'high').length,
126
+ medium: findings.filter(f => f.severity === 'medium').length,
127
+ low: findings.filter(f => f.severity === 'low').length
128
+ };
129
+ }
130
+ }
131
+ // npm audit JSON output parser
132
+ export class NpmAuditParser extends ScannerParser {
133
+ name = 'npm-audit';
134
+ parse(input) {
135
+ const data = typeof input === 'string' ? JSON.parse(input) : input;
136
+ const findings = [];
137
+ // npm audit v2 format
138
+ const vulnerabilities = data.vulnerabilities || {};
139
+ for (const [pkgName, vuln] of Object.entries(vulnerabilities)) {
140
+ const v = vuln;
141
+ findings.push({
142
+ id: `npm-${pkgName}`,
143
+ severity: this.normalizeSeverity(v.severity || 'medium'),
144
+ title: v.title || `Vulnerability in ${pkgName}`,
145
+ description: v.url,
146
+ package: pkgName,
147
+ version: v.range,
148
+ fixedIn: v.fixAvailable ? 'Update available' : undefined
149
+ });
150
+ }
151
+ return {
152
+ scanner: this.name,
153
+ timestamp: new Date().toISOString(),
154
+ findings,
155
+ summary: this.summarize(findings),
156
+ raw: typeof input === 'string' ? input : JSON.stringify(input)
157
+ };
158
+ }
159
+ summarize(findings) {
160
+ return {
161
+ critical: findings.filter(f => f.severity === 'critical').length,
162
+ high: findings.filter(f => f.severity === 'high').length,
163
+ medium: findings.filter(f => f.severity === 'medium').length,
164
+ low: findings.filter(f => f.severity === 'low').length
165
+ };
166
+ }
167
+ }
168
+ // Generic parser for simple formats
169
+ export class GenericParser extends ScannerParser {
170
+ name = 'generic';
171
+ parse(input) {
172
+ // Parse simple format like "critical: 5\nhigh: 10"
173
+ if (typeof input === 'string') {
174
+ const summary = { critical: 0, high: 0, medium: 0, low: 0 };
175
+ const lines = input.split('\n');
176
+ for (const line of lines) {
177
+ const match = line.match(/(critical|high|medium|low)[:\s]+(\d+)/i);
178
+ if (match) {
179
+ const sev = match[1].toLowerCase();
180
+ summary[sev] = parseInt(match[2], 10);
181
+ }
182
+ }
183
+ return {
184
+ scanner: this.name,
185
+ timestamp: new Date().toISOString(),
186
+ findings: [],
187
+ summary,
188
+ raw: input
189
+ };
190
+ }
191
+ // Object format
192
+ const data = input;
193
+ return {
194
+ scanner: this.name,
195
+ timestamp: new Date().toISOString(),
196
+ findings: [],
197
+ summary: {
198
+ critical: data.critical || 0,
199
+ high: data.high || 0,
200
+ medium: data.medium || 0,
201
+ low: data.low || 0
202
+ },
203
+ raw: JSON.stringify(input)
204
+ };
205
+ }
206
+ }
207
+ // Factory function to get appropriate parser
208
+ export function getParser(scannerName) {
209
+ switch (scannerName.toLowerCase()) {
210
+ case 'snyk': return new SnykParser();
211
+ case 'trivy': return new TrivyParser();
212
+ case 'semgrep': return new SemgrepParser();
213
+ case 'npm':
214
+ case 'npm-audit': return new NpmAuditParser();
215
+ default: return new GenericParser();
216
+ }
217
+ }