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.
- package/LICENSE +21 -0
- package/README.md +446 -0
- package/deploy/AWS-DEPLOYMENT.md +358 -0
- package/deploy/terraform/main.tf +362 -0
- package/deploy/terraform/terraform.tfvars.example +6 -0
- package/dist/agents/base.d.ts +44 -0
- package/dist/agents/base.js +96 -0
- package/dist/agents/index.d.ts +14 -0
- package/dist/agents/index.js +17 -0
- package/dist/agents/policy/evaluator.d.ts +15 -0
- package/dist/agents/policy/evaluator.js +183 -0
- package/dist/agents/policy/index.d.ts +12 -0
- package/dist/agents/policy/index.js +15 -0
- package/dist/agents/policy/validator.d.ts +15 -0
- package/dist/agents/policy/validator.js +182 -0
- package/dist/agents/scanners/gitleaks.d.ts +14 -0
- package/dist/agents/scanners/gitleaks.js +155 -0
- package/dist/agents/scanners/grype.d.ts +14 -0
- package/dist/agents/scanners/grype.js +109 -0
- package/dist/agents/scanners/index.d.ts +15 -0
- package/dist/agents/scanners/index.js +27 -0
- package/dist/agents/scanners/npm-audit.d.ts +13 -0
- package/dist/agents/scanners/npm-audit.js +129 -0
- package/dist/agents/scanners/semgrep.d.ts +14 -0
- package/dist/agents/scanners/semgrep.js +131 -0
- package/dist/agents/scanners/trivy.d.ts +14 -0
- package/dist/agents/scanners/trivy.js +122 -0
- package/dist/agents/types.d.ts +137 -0
- package/dist/agents/types.js +91 -0
- package/dist/auditor/index.d.ts +3 -0
- package/dist/auditor/index.js +2 -0
- package/dist/auditor/pipeline.d.ts +19 -0
- package/dist/auditor/pipeline.js +240 -0
- package/dist/auditor/validator.d.ts +17 -0
- package/dist/auditor/validator.js +58 -0
- package/dist/aura/client.d.ts +29 -0
- package/dist/aura/client.js +125 -0
- package/dist/aura/index.d.ts +4 -0
- package/dist/aura/index.js +2 -0
- package/dist/aura/server.d.ts +45 -0
- package/dist/aura/server.js +343 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +1433 -0
- package/dist/client/index.d.ts +41 -0
- package/dist/client/index.js +170 -0
- package/dist/compliance/index.d.ts +40 -0
- package/dist/compliance/index.js +292 -0
- package/dist/database/index.d.ts +77 -0
- package/dist/database/index.js +395 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +762 -0
- package/dist/integrations/aura-scanner.d.ts +69 -0
- package/dist/integrations/aura-scanner.js +155 -0
- package/dist/integrations/aws-scanner.d.ts +63 -0
- package/dist/integrations/aws-scanner.js +624 -0
- package/dist/integrations/config.d.ts +69 -0
- package/dist/integrations/config.js +212 -0
- package/dist/integrations/github.d.ts +45 -0
- package/dist/integrations/github.js +201 -0
- package/dist/integrations/gitlab.d.ts +36 -0
- package/dist/integrations/gitlab.js +110 -0
- package/dist/integrations/index.d.ts +11 -0
- package/dist/integrations/index.js +11 -0
- package/dist/integrations/local-scanner.d.ts +146 -0
- package/dist/integrations/local-scanner.js +1654 -0
- package/dist/integrations/notifications.d.ts +99 -0
- package/dist/integrations/notifications.js +305 -0
- package/dist/integrations/scanners.d.ts +57 -0
- package/dist/integrations/scanners.js +217 -0
- package/dist/integrations/slop-scanner.d.ts +69 -0
- package/dist/integrations/slop-scanner.js +155 -0
- package/dist/integrations/webhook.d.ts +37 -0
- package/dist/integrations/webhook.js +256 -0
- package/dist/orchestrator/index.d.ts +72 -0
- package/dist/orchestrator/index.js +187 -0
- package/dist/output/index.d.ts +152 -0
- package/dist/output/index.js +399 -0
- package/dist/pipeline/index.d.ts +72 -0
- package/dist/pipeline/index.js +313 -0
- package/dist/sbom/index.d.ts +94 -0
- package/dist/sbom/index.js +298 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/input.schema.d.ts +87 -0
- package/dist/schemas/input.schema.js +44 -0
- package/dist/schemas/output.schema.d.ts +115 -0
- package/dist/schemas/output.schema.js +64 -0
- package/dist/serve-visualizer.d.ts +2 -0
- package/dist/serve-visualizer.js +78 -0
- package/dist/slop/client.d.ts +29 -0
- package/dist/slop/client.js +125 -0
- package/dist/slop/index.d.ts +4 -0
- package/dist/slop/index.js +2 -0
- package/dist/slop/server.d.ts +45 -0
- package/dist/slop/server.js +343 -0
- package/dist/types/events.d.ts +62 -0
- package/dist/types/events.js +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/visualizer/index.d.ts +4 -0
- package/dist/visualizer/index.js +181 -0
- package/dist/websocket/index.d.ts +88 -0
- package/dist/websocket/index.js +195 -0
- package/dist/zones/index.d.ts +7 -0
- package/dist/zones/index.js +7 -0
- package/dist/zones/manager.d.ts +101 -0
- package/dist/zones/manager.js +304 -0
- package/dist/zones/types.d.ts +78 -0
- package/dist/zones/types.js +33 -0
- package/package.json +84 -0
- package/visualizer/app.js +0 -0
- package/visualizer/index-minimal.html +1771 -0
- package/visualizer/index.html +2933 -0
- package/visualizer/landing.html +1328 -0
- 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
|
+
}
|