omen-sec-cli 1.0.16 → 1.0.18

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.
@@ -36,47 +36,54 @@ export async function scanRemoteTarget(targetUrl) {
36
36
  headers_analysis["Strict-Transport-Security"] = "Missing";
37
37
  vulnerabilities.push({
38
38
  id: `REM-VULN-${Date.now()}-1`,
39
- category: 'Hardening',
40
- confidence: 'High',
41
- severity: 'Medium',
42
- description: `HSTS Header is missing. This lacks forced HTTPS enforcement for browsers that have already visited the site.`,
39
+ kind: 'header',
40
+ category: 'hardening',
41
+ confidence: 'high',
42
+ severity: 'medium',
43
+ title: 'HSTS Header Missing',
44
+ description: `HTTP Strict-Transport-Security (HSTS) header is missing. This prevents the browser from enforcing HTTPS-only connections for future visits.`,
43
45
  cwe: 'CWE-319',
44
46
  evidence: {
45
47
  request: { headers: { ...response.request.headers } },
46
48
  response: { status: response.status, headers: response.headers },
47
49
  reason: 'Security header "Strict-Transport-Security" not found in server response.'
48
- }
50
+ },
51
+ remediation: 'Implement the Strict-Transport-Security header with a long max-age (e.g., 31536000) and the includeSubDomains directive.'
49
52
  });
50
- } else {
51
- headers_analysis["Strict-Transport-Security"] = headers['strict-transport-security'];
52
53
  }
53
54
 
54
55
  if (!headers['content-security-policy']) {
55
56
  headers_analysis["Content-Security-Policy"] = "Missing";
56
57
  vulnerabilities.push({
57
58
  id: `REM-VULN-${Date.now()}-2`,
58
- category: 'Confirmed',
59
- confidence: 'High',
60
- severity: 'High',
59
+ kind: 'header',
60
+ category: 'confirmed',
61
+ confidence: 'high',
62
+ severity: 'high',
63
+ title: 'Content-Security-Policy Missing',
61
64
  description: `CSP header is missing. Without a strict Content-Security-Policy, the application is highly vulnerable to Cross-Site Scripting (XSS) and data injection attacks.`,
62
65
  cwe: 'CWE-1022',
63
66
  evidence: {
64
67
  request: { headers: { ...response.request.headers } },
65
68
  response: { status: response.status, headers: response.headers },
66
69
  reason: 'Security header "Content-Security-Policy" not found in server response.'
67
- }
70
+ },
71
+ remediation: 'Define a strict Content-Security-Policy to restrict source domains for scripts, styles, and other resources.'
68
72
  });
69
73
  } else {
70
74
  headers_analysis["Content-Security-Policy"] = headers['content-security-policy'];
71
75
  if (headers['content-security-policy'].includes("unsafe-inline")) {
72
76
  vulnerabilities.push({
73
77
  id: `REM-VULN-${Date.now()}-3`,
74
- category: 'Confirmed',
75
- confidence: 'High',
76
- severity: 'High',
77
- description: `Weak CSP detected: 'unsafe-inline' is allowed.`,
78
+ kind: 'header',
79
+ category: 'confirmed',
80
+ confidence: 'high',
81
+ severity: 'high',
82
+ title: 'Insecure CSP (unsafe-inline)',
83
+ description: `The Content-Security-Policy allows 'unsafe-inline' for scripts or styles, significantly weakening protection against XSS.`,
78
84
  cwe: 'CWE-16',
79
- evidence: { finding: `policy: ${headers['content-security-policy']}` }
85
+ evidence: { finding: `policy: ${headers['content-security-policy']}` },
86
+ remediation: 'Refactor the application to avoid inline scripts and styles, then remove "unsafe-inline" from the CSP.'
80
87
  });
81
88
  }
82
89
  }
@@ -86,50 +93,101 @@ export async function scanRemoteTarget(targetUrl) {
86
93
  headers_analysis["X-Frame-Options"] = "Missing";
87
94
  vulnerabilities.push({
88
95
  id: `REM-VULN-${Date.now()}-4`,
89
- category: 'Hardening',
90
- confidence: 'High',
91
- severity: 'Low',
92
- description: `Missing X-Frame-Options. Increases risk of Clickjacking.`,
96
+ kind: 'header',
97
+ category: 'hardening',
98
+ confidence: 'high',
99
+ severity: 'low',
100
+ title: 'X-Frame-Options Missing',
101
+ description: `Missing X-Frame-Options header. This allows the application to be embedded in an iframe on other domains, increasing Clickjacking risk.`,
93
102
  cwe: 'CWE-1021',
94
103
  evidence: {
95
104
  request: { headers: { ...response.request.headers } },
96
105
  response: { status: response.status, headers: response.headers },
97
106
  reason: 'Security header "X-Frame-Options" not found. This allows the site to be embedded in iframes on third-party domains.'
98
- }
107
+ },
108
+ remediation: 'Set the X-Frame-Options header to DENY or SAMEORIGIN.'
109
+ });
110
+ }
111
+
112
+ // 3.1 Analisar X-Content-Type-Options
113
+ if (!headers['x-content-type-options']) {
114
+ headers_analysis["X-Content-Type-Options"] = "Missing";
115
+ vulnerabilities.push({
116
+ id: `REM-VULN-${Date.now()}-6`,
117
+ kind: 'header',
118
+ category: 'hardening',
119
+ confidence: 'high',
120
+ severity: 'low',
121
+ title: 'X-Content-Type-Options Missing',
122
+ description: `The X-Content-Type-Options: nosniff header is missing. This could allow the browser to "sniff" the content type, potentially leading to MIME-type sniffing attacks.`,
123
+ cwe: 'CWE-116',
124
+ evidence: { response: { headers: response.headers } },
125
+ remediation: 'Add the "X-Content-Type-Options: nosniff" header to all responses.'
99
126
  });
100
- } else {
101
- headers_analysis["X-Frame-Options"] = headers['x-frame-options'];
102
127
  }
103
128
 
104
129
  // 4. Server Header Leak
105
- if (headers['server']) {
130
+ if (headers['server'] && !headers['server'].includes('Cloudflare') && !headers['server'].includes('Vercel')) {
106
131
  headers_analysis["Server"] = headers['server'];
107
132
  vulnerabilities.push({
108
133
  id: `REM-VULN-${Date.now()}-5`,
109
- category: 'Informational',
110
- confidence: 'High',
111
- severity: 'Low',
112
- description: `Server header leaks technology stack: ${headers['server']}`,
134
+ kind: 'tech',
135
+ category: 'informational',
136
+ confidence: 'high',
137
+ severity: 'low',
138
+ title: 'Server Header Disclosure',
139
+ description: `Server header leaks technology stack information: ${headers['server']}`,
113
140
  cwe: 'CWE-200',
114
- evidence: { finding: `Server: ${headers['server']}` }
141
+ evidence: { finding: `Server: ${headers['server']}` },
142
+ remediation: 'Configure the web server to hide or spoof the "Server" header.'
115
143
  });
116
- } else {
117
- headers_analysis["Server"] = "Hidden (Good)";
118
144
  }
119
145
 
120
- // 5. X-Powered-By Leak
121
- if (headers['x-powered-by']) {
122
- vulnerabilities.push({
123
- id: `REM-VULN-${Date.now()}-6`,
124
- category: 'Informational',
125
- confidence: 'High',
126
- severity: 'Low',
127
- description: `X-Powered-By header leaks framework: ${headers['x-powered-by']}`,
128
- cwe: 'CWE-200',
129
- evidence: { finding: `X-Powered-By: ${headers['x-powered-by']}` }
130
- });
146
+ // 6. CORS Analysis
147
+ if (headers['access-control-allow-origin']) {
148
+ const cors = headers['access-control-allow-origin'];
149
+ if (cors === '*') {
150
+ vulnerabilities.push({
151
+ id: `REM-CORS-${Date.now()}`,
152
+ kind: 'header',
153
+ category: 'confirmed',
154
+ confidence: 'high',
155
+ severity: 'high',
156
+ title: 'Permissive CORS Policy',
157
+ description: `The application allows Cross-Origin Resource Sharing from any domain (Access-Control-Allow-Origin: *).`,
158
+ cwe: 'CWE-942',
159
+ evidence: { finding: `Access-Control-Allow-Origin: ${cors}` },
160
+ remediation: 'Restrict Access-Control-Allow-Origin to trusted domains only.'
161
+ });
162
+ }
131
163
  }
132
164
 
165
+ // 7. Cookie Security
166
+ const setCookies = headers['set-cookie'] || [];
167
+ const cookies = Array.isArray(setCookies) ? setCookies : [setCookies];
168
+ cookies.forEach(cookie => {
169
+ const issues = [];
170
+ if (!cookie.toLowerCase().includes('secure')) issues.push('Missing "Secure" flag');
171
+ if (!cookie.toLowerCase().includes('httponly')) issues.push('Missing "HttpOnly" flag');
172
+ if (!cookie.toLowerCase().includes('samesite')) issues.push('Missing "SameSite" attribute');
173
+
174
+ if (issues.length > 0) {
175
+ vulnerabilities.push({
176
+ id: `REM-COOKIE-${Date.now()}`,
177
+ kind: 'header',
178
+ category: 'confirmed',
179
+ confidence: 'high',
180
+ severity: 'medium',
181
+ title: 'Insecure Cookie Configuration',
182
+ description: `Cookie detected with missing security flags: ${issues.join(', ')}.`,
183
+ cwe: 'CWE-614',
184
+ evidence: { cookie: cookie.split(';')[0] + '...', issues },
185
+ remediation: 'Apply Secure, HttpOnly, and SameSite=Strict/Lax attributes to all sensitive cookies.'
186
+ });
187
+ }
188
+ });
189
+
190
+
133
191
  // --- SPIDER / CRAWLER (DEEP) ---
134
192
  if (typeof html === 'string') {
135
193
  const $ = cheerio.load(html);
@@ -175,6 +233,22 @@ export async function scanRemoteTarget(targetUrl) {
175
233
  });
176
234
  }
177
235
 
236
+ // --- TECHNOLOGY FINGERPRINTING (Update categories) ---
237
+ if (techStack.length > 0) {
238
+ vulnerabilities.push({
239
+ id: `REM-TECH-${Date.now()}`,
240
+ kind: 'tech',
241
+ category: 'informational',
242
+ confidence: 'high',
243
+ severity: 'info',
244
+ title: 'Technology Stack Identified',
245
+ description: `Fingerprinting identified the following technologies: ${techStack.join(', ')}`,
246
+ cwe: 'CWE-200',
247
+ evidence: { tech_stack: techStack },
248
+ remediation: 'Minimal tech disclosure is recommended to prevent targeted attacks.'
249
+ });
250
+ }
251
+
178
252
  // --- DEEP CRAWL: robots.txt, sitemap.xml, and JS files ---
179
253
  const robotsUrl = new URL('/robots.txt', targetUrl).href;
180
254
  try {
@@ -228,41 +302,32 @@ export async function scanRemoteTarget(targetUrl) {
228
302
  const fuzzUrl = new URL(path, targetUrl).href;
229
303
  const fuzzRes = await axios.get(fuzzUrl, {
230
304
  timeout: 5000,
231
- validateStatus: (status) => status === 200 || status === 403
305
+ validateStatus: (status) => status >= 200 && status < 500
232
306
  });
233
307
 
234
- if (fuzzRes.status === 200) {
235
- vulnerabilities.push({
236
- id: `REM-FUZZ-${Date.now()}-${path.replace(/\//g, '-')}`,
237
- type: 'Sensitive Path Exposed',
238
- severity: path.includes('.env') || path.includes('.git') || path.includes('.ssh') ? 'Critical' : 'High',
239
- description: `Exposed sensitive path discovered: ${fuzzUrl}. This path reveals internal configurations or credentials.`,
240
- cwe: 'CWE-200'
241
- });
242
- } else if (fuzzRes.status === 403) {
243
- vulnerabilities.push({
244
- id: `REM-FUZZ-${Date.now()}-${path.replace(/\//g, '-')}`,
245
- type: 'Potential Sensitive Path',
246
- severity: 'Low',
247
- description: `Path discovered but access forbidden (403): ${fuzzUrl}. Might indicate internal structure exposure.`,
248
- cwe: 'CWE-204'
249
- });
308
+ const finding = await validateFuzzerFinding(path, fuzzRes, fuzzUrl);
309
+ if (finding) {
310
+ vulnerabilities.push(finding);
250
311
  }
251
- } catch (e) {}
312
+ } catch (e) {
313
+ // Silently skip if path doesn't exist or times out
314
+ }
252
315
  }
253
316
 
254
317
  // --- OFFENSIVE PARAMETER FUZZING ---
255
318
  const injectionPayloads = [
256
- { type: 'SQLi', param: "' OR 1=1--", severity: 'Critical' },
257
- { type: 'XSS', param: "<script>alert('OMEN')</script>", severity: 'High' },
258
- { type: 'LFI', param: "/etc/passwd", severity: 'Critical' }
319
+ { type: 'SQLi', param: "' OR '1'='1", severity: 'Critical', cwe: 'CWE-89' },
320
+ { type: 'SQLi', param: '" OR "1"="1', severity: 'Critical', cwe: 'CWE-89' },
321
+ { type: 'XSS', param: "<script>alert('OMEN')</script>", severity: 'High', cwe: 'CWE-79' },
322
+ { type: 'XSS', param: "javascript:alert('OMEN')", severity: 'High', cwe: 'CWE-79' },
323
+ { type: 'LFI', param: "../../../../../etc/passwd", severity: 'Critical', cwe: 'CWE-22' }
259
324
  ];
260
325
 
261
326
  // Combine params from links and forms
262
327
  const allParams = Array.from(discoveredParams);
263
328
  discoveredForms.forEach(f => f.inputs.forEach(i => allParams.push(i)));
264
329
 
265
- const uniqueParams = [...new Set(allParams)].slice(0, 5); // Fuzz top 5 params
330
+ const uniqueParams = [...new Set(allParams)].slice(0, 10); // Fuzz top 10 params
266
331
 
267
332
  for (const param of uniqueParams) {
268
333
  for (const payload of injectionPayloads) {
@@ -270,26 +335,68 @@ export async function scanRemoteTarget(targetUrl) {
270
335
  const testUrl = new URL(targetUrl);
271
336
  testUrl.searchParams.append(param, payload.param);
272
337
 
273
- const res = await axios.get(testUrl.href, { timeout: 5000, validateStatus: () => true });
338
+ const res = await axios.get(testUrl.href, {
339
+ timeout: 5000,
340
+ validateStatus: () => true,
341
+ headers: { 'User-Agent': 'OMEN-SEC-CLI/1.0.17 (Security Audit)' }
342
+ });
274
343
 
344
+ const evidence = {
345
+ request: { url: testUrl.href, method: 'GET', parameter: param, payload: payload.param },
346
+ response: { status: res.status, body_snippet: typeof res.data === 'string' ? res.data.substring(0, 200) : '' }
347
+ };
348
+
275
349
  if (payload.type === 'XSS' && typeof res.data === 'string' && res.data.includes(payload.param)) {
276
350
  vulnerabilities.push({
277
- id: `REM-INJ-${Date.now()}-XSS`,
278
- type: 'Reflected XSS',
279
- severity: 'High',
280
- description: `Confirmed XSS at ${targetUrl} via parameter '${param}'. Payload was reflected in HTML.`,
281
- cwe: 'CWE-79'
351
+ id: `REM-INJ-${Date.now()}-XSS-${param}`,
352
+ kind: 'injection',
353
+ category: 'confirmed',
354
+ confidence: 'high',
355
+ severity: payload.severity,
356
+ title: 'Reflected Cross-Site Scripting (XSS)',
357
+ description: `Confirmed reflected XSS at ${targetUrl} via parameter '${param}'. The payload was reflected verbatim in the response body.`,
358
+ cwe: payload.cwe,
359
+ evidence,
360
+ remediation: 'Sanitize all user inputs before reflecting them in the HTML. Use output encoding libraries.'
282
361
  });
283
362
  }
284
363
 
285
- if (payload.type === 'SQLi' && (res.status === 500 || (typeof res.data === 'string' && /SQL|database|syntax/i.test(res.data)))) {
286
- vulnerabilities.push({
287
- id: `REM-INJ-${Date.now()}-SQLI`,
288
- type: 'SQL Injection',
289
- severity: 'Critical',
290
- description: `Potential SQLi detected via parameter '${param}'. Server showed error patterns on payload.`,
291
- cwe: 'CWE-89'
292
- });
364
+ const sqliPatterns = [
365
+ /SQL syntax/i, /mysql_fetch/i, /PostgreSQL.*ERROR/i, /Oracle.*Error/i,
366
+ /SQLite3::/i, /Dynamic SQL/i, /Syntax error.*in query/i
367
+ ];
368
+
369
+ if (payload.type === 'SQLi') {
370
+ const hasErrorPattern = typeof res.data === 'string' && sqliPatterns.some(p => p.test(res.data));
371
+ if (hasErrorPattern || res.status === 500) {
372
+ vulnerabilities.push({
373
+ id: `REM-INJ-${Date.now()}-SQLI-${param}`,
374
+ kind: 'injection',
375
+ category: 'probable',
376
+ confidence: hasErrorPattern ? 'high' : 'medium',
377
+ severity: payload.severity,
378
+ title: 'Potential SQL Injection',
379
+ description: `Potential SQLi detected via parameter '${param}'. The server returned a 500 error or a known SQL error pattern.`,
380
+ cwe: payload.cwe,
381
+ evidence,
382
+ remediation: 'Use parameterized queries or an ORM. Never concatenate user input directly into SQL strings.'
383
+ });
384
+ }
385
+ }
386
+
387
+ if (payload.type === 'LFI' && typeof res.data === 'string' && (res.data.includes('root:x:0:0') || res.data.includes('[boot loader]'))) {
388
+ vulnerabilities.push({
389
+ id: `REM-INJ-${Date.now()}-LFI-${param}`,
390
+ kind: 'injection',
391
+ category: 'confirmed',
392
+ confidence: 'high',
393
+ severity: payload.severity,
394
+ title: 'Local File Inclusion (LFI)',
395
+ description: `Confirmed LFI at ${targetUrl} via parameter '${param}'. System file content was detected in the response.`,
396
+ cwe: payload.cwe,
397
+ evidence,
398
+ remediation: 'Validate file paths against a whitelist and avoid using user-supplied input for file operations.'
399
+ });
293
400
  }
294
401
  } catch (e) {}
295
402
  }
@@ -335,22 +442,28 @@ async function validateFuzzerFinding(path, response, url) {
335
442
  if (typeof data === 'string' && !data.trim().startsWith('<html')) {
336
443
  return {
337
444
  id: `REM-CONFIRMED-FILE-${Date.now()}`,
338
- category: 'Confirmed',
339
- confidence: 'High',
340
- severity: 'Critical',
445
+ kind: 'content',
446
+ category: 'confirmed',
447
+ confidence: 'high',
448
+ severity: 'critical',
449
+ title: 'Sensitive File Exposed',
341
450
  description: `CRITICAL: Sensitive file exposed at ${url}. Contents contain raw configuration data.`,
342
451
  cwe: 'CWE-538',
343
- evidence: { ...evidence, reason: 'Raw sensitive file content detected (Non-HTML response on sensitive path)' }
452
+ evidence: { ...evidence, reason: 'Raw sensitive file content detected (Non-HTML response on sensitive path)' },
453
+ remediation: 'Immediately remove the file from the web server and ensure sensitive files are not publicly accessible.'
344
454
  };
345
455
  } else {
346
456
  return {
347
457
  id: `REM-POTENTIAL-FILE-${Date.now()}`,
348
- category: 'Informational',
349
- confidence: 'Low',
350
- severity: 'Info',
458
+ kind: 'path',
459
+ category: 'informational',
460
+ confidence: 'low',
461
+ severity: 'info',
462
+ title: 'Potential Sensitive Path Discovered',
351
463
  description: `Potential sensitive path found at ${url}, but returned HTML content. Likely a redirect or custom error page.`,
352
464
  cwe: 'CWE-200',
353
- evidence: { ...evidence, reason: 'HTML response on sensitive file path' }
465
+ evidence: { ...evidence, reason: 'HTML response on sensitive file path' },
466
+ remediation: 'Verify if this path should be accessible and ensure no sensitive information is leaked through custom error pages.'
354
467
  };
355
468
  }
356
469
  }
@@ -361,23 +474,29 @@ async function validateFuzzerFinding(path, response, url) {
361
474
  if (typeof data === 'string' && /password|login/i.test(data)) {
362
475
  return {
363
476
  id: `REM-INFO-LOGIN-${Date.now()}`,
364
- category: 'Informational',
365
- confidence: 'High',
366
- severity: 'Info',
477
+ kind: 'path',
478
+ category: 'informational',
479
+ confidence: 'high',
480
+ severity: 'info',
481
+ title: 'Admin Login Page Discovered',
367
482
  description: `Admin login page discovered at ${url}.`,
368
483
  cwe: 'CWE-200',
369
- evidence: { ...evidence, reason: '200 OK with login/password patterns detected in HTML.' }
484
+ evidence: { ...evidence, reason: '200 OK with login/password patterns detected in HTML.' },
485
+ remediation: 'Ensure the admin login page is protected by strong authentication and not easily discoverable if not necessary.'
370
486
  };
371
487
  }
372
488
  // If it's a 200 but not a login page, it could be an exposed panel
373
489
  return {
374
490
  id: `REM-PROBABLE-PANEL-${Date.now()}`,
375
- category: 'Probable',
376
- confidence: 'Low',
377
- severity: 'Medium',
491
+ kind: 'path',
492
+ category: 'probable',
493
+ confidence: 'low',
494
+ severity: 'medium',
495
+ title: 'Potential Admin Panel Exposure',
378
496
  description: `Potential exposed admin panel or dashboard at ${url}. Manual verification required.`,
379
497
  cwe: 'CWE-284',
380
- evidence: { ...evidence, reason: '200 OK on admin-like path, but no explicit login form detected. Could be an unauthorized dashboard.' }
498
+ evidence: { ...evidence, reason: '200 OK on admin-like path, but no explicit login form detected. Could be an unauthorized dashboard.' },
499
+ remediation: 'Restrict access to the admin panel using IP whitelisting or other access control mechanisms.'
381
500
  };
382
501
  }
383
502
 
@@ -385,15 +504,18 @@ async function validateFuzzerFinding(path, response, url) {
385
504
  if (status === 403) {
386
505
  return {
387
506
  id: `REM-INFO-FORBIDDEN-${Date.now()}`,
388
- category: 'Informational',
389
- confidence: 'Medium',
390
- severity: 'Info',
507
+ kind: 'path',
508
+ category: 'informational',
509
+ confidence: 'medium',
510
+ severity: 'info',
511
+ title: 'Protected Path Discovered',
391
512
  description: `Potential protected path discovered (403 Forbidden): ${url}. This confirms the path exists but access is restricted.`,
392
513
  cwe: 'CWE-204',
393
514
  evidence: {
394
515
  ...evidence,
395
516
  reason: 'Server returned 403 Forbidden. This confirms path existence but access control is active. No immediate exposure detected.'
396
- }
517
+ },
518
+ remediation: 'None required, but ensure that the 403 response does not leak information about the internal structure.'
397
519
  };
398
520
  }
399
521
 
@@ -0,0 +1,46 @@
1
+ export function generateFixPlan(scanData) {
2
+ let md = `# 🛡️ OMEN Security Fix Plan: ${scanData.target || 'Local Project'}\n\n`;
3
+ md += `> **Data do Scan:** ${scanData.timestamp || new Date().toLocaleString()} \n`;
4
+ md += `> **Score Atual:** ${scanData.score || 0}/100 \n`;
5
+ md += `> **Vulnerabilidades:** ${scanData.vulnerabilities?.length || 0}\n\n`;
6
+
7
+ md += `Este documento é um guia passo-a-passo para remediar as falhas encontradas. Após aplicar as correções, execute \`omen verify\` para confirmar a resolução.\n\n`;
8
+
9
+ const vulnerabilities = scanData.vulnerabilities || [];
10
+ const prioritized = [...vulnerabilities].sort((a, b) => {
11
+ const weights = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
12
+ return (weights[b.severity] || 0) - (weights[a.severity] || 0);
13
+ });
14
+
15
+ md += `## 🚨 Checklist de Remediação\n\n`;
16
+
17
+ if (prioritized.length === 0) {
18
+ md += `✅ Nenhuma vulnerabilidade encontrada que exija ação imediata.\n`;
19
+ } else {
20
+ prioritized.forEach((v, index) => {
21
+ const severityEmoji = v.severity === 'critical' ? '🔴' : v.severity === 'high' ? '🟠' : v.severity === 'medium' ? '🟡' : '🔵';
22
+
23
+ md += `### [ ] ${severityEmoji} [${v.severity.toUpperCase()}] ${v.title || v.description}\n`;
24
+ md += `- **ID:** \`${v.id}\` \n`;
25
+ md += `- **CWE:** ${v.cwe || 'N/A'} \n`;
26
+ md += `- **Confiança:** ${v.confidence || 'medium'} \n\n`;
27
+
28
+ if (v.evidence) {
29
+ md += `#### 🔍 Evidência\n`;
30
+ if (v.evidence.file) md += `- **Arquivo:** \`${v.evidence.file}\`${v.evidence.line ? ` (Linha ${v.evidence.line})` : ''} \n`;
31
+ if (v.evidence.code) md += `\`\`\`javascript\n${v.evidence.code}\n\`\`\`\n`;
32
+ if (v.evidence.finding) md += `- **Detalhe:** ${v.evidence.finding} \n`;
33
+ }
34
+
35
+ md += `#### 🛠️ Estratégia de Correção\n`;
36
+ md += `${v.remediation || 'Consulte a documentação de segurança para mitigar este risco.'}\n\n`;
37
+
38
+ md += `#### ✅ Comando de Verificação\n`;
39
+ md += `\`npx omen-sec-cli verify --id ${v.id}\` (ou apenas \`omen verify\` para checar tudo)\n\n`;
40
+ md += `---\n\n`;
41
+ });
42
+ }
43
+
44
+ md += `\n*Gerado automaticamente pelo OMEN SEC-CLI v1.0.17 - Protocolo Zero-Copy AI Ativo*\n`;
45
+ return md;
46
+ }
@@ -0,0 +1,25 @@
1
+ export function generateMarkdownReport(scanData) {
2
+ let md = `# OMEN Audit Report: ${scanData.target}\n\n`;
3
+ md += `**Scan ID:** \`${scanData.scan_id}\` \n`;
4
+ md += `**Timestamp:** ${scanData.timestamp} \n`;
5
+ md += `**Security Score:** ${scanData.score}/100 \n`;
6
+ md += `**Risk Level:** ${scanData.riskLevel}\n\n`;
7
+
8
+ md += `## Executive Summary\n`;
9
+ md += `The security audit identified ${scanData.vulnerabilities.length} total issues. `;
10
+ const confirmed = scanData.vulnerabilities.filter(v => v.category === 'confirmed').length;
11
+ md += `Of these, ${confirmed} are confirmed vulnerabilities that require immediate attention.\n\n`;
12
+
13
+ md += `## Detailed Findings\n\n`;
14
+ scanData.vulnerabilities.forEach(v => {
15
+ md += `### [${v.severity.toUpperCase()}] ${v.title || v.description}\n`;
16
+ md += `- **Category:** ${v.category}\n`;
17
+ md += `- **Confidence:** ${v.confidence}\n`;
18
+ md += `- **CWE:** ${v.cwe}\n\n`;
19
+ md += `#### Description\n${v.description}\n\n`;
20
+ md += `#### Remediation\n${v.remediation || 'Follow security best practices.'}\n\n`;
21
+ md += `---\n\n`;
22
+ });
23
+
24
+ return md;
25
+ }
@@ -0,0 +1,39 @@
1
+ import { execa } from 'execa';
2
+ import chalk from 'chalk';
3
+ import axios from 'axios';
4
+
5
+ export async function bootLocalApp(command, port = 3000, timeout = 30000) {
6
+ if (!command) return { success: false, error: 'No boot strategy found' };
7
+
8
+ console.log(chalk.cyan(`[Runner] Starting local app with: ${command}...`));
9
+
10
+ const [cmd, ...args] = command.split(' ');
11
+ const subprocess = execa(cmd, args, {
12
+ cleanup: true,
13
+ all: true
14
+ });
15
+
16
+ let bootLogs = '';
17
+ subprocess.all.on('data', (chunk) => {
18
+ bootLogs += chunk.toString();
19
+ });
20
+
21
+ // Wait for healthcheck or timeout
22
+ const start = Date.now();
23
+ while (Date.now() - start < timeout) {
24
+ try {
25
+ const res = await axios.get(`http://localhost:${port}`, { timeout: 1000, validateStatus: () => true });
26
+ if (res.status < 500) {
27
+ console.log(chalk.green(`[Runner] App is up and running on port ${port}!`));
28
+ return { success: true, subprocess, logs: bootLogs, port };
29
+ }
30
+ } catch (e) {
31
+ // Not up yet
32
+ }
33
+ await new Promise(r => setTimeout(r, 2000));
34
+ }
35
+
36
+ // If we reach here, boot failed or timed out
37
+ subprocess.kill();
38
+ return { success: false, error: 'App failed to start or healthcheck timed out', logs: bootLogs };
39
+ }
package/core/scanner.js CHANGED
@@ -48,39 +48,49 @@ export async function runScannerSteps(target, flags) {
48
48
  spinner.succeed(`[${i + 1}/${steps.length}] ${step.text}`);
49
49
  }
50
50
 
51
+ const scanData = {
52
+ schema_version: "1.0",
53
+ target: target,
54
+ scan_id: `OMEN-${Date.now()}`,
55
+ timestamp: new Date().toISOString(),
56
+ score: 100,
57
+ riskLevel: 'Low',
58
+ attack_surface: {
59
+ endpoints: attack_surface.endpoints || [],
60
+ parameters: attack_surface.parameters || [],
61
+ forms: attack_surface.forms || [],
62
+ tech_stack: attack_surface.tech_stack || []
63
+ },
64
+ vulnerabilities: allVulnerabilities
65
+ };
66
+
51
67
  // Calculate dynamic score based on confidence and severity
52
- const baseScore = 100;
53
- const penalties = allVulnerabilities.reduce((acc, v) => {
68
+ let penalties = scanData.vulnerabilities.reduce((acc, v) => {
54
69
  // Only penalize for Confirmed or Probable issues
55
- if (v.category !== 'Confirmed' && v.category !== 'Probable') return acc;
70
+ if (v.category !== 'confirmed' && v.category !== 'probable') return acc;
56
71
 
57
72
  let severityWeight = 0;
58
- if (v.severity === 'Critical') severityWeight = 25;
59
- else if (v.severity === 'High') severityWeight = 15;
60
- else if (v.severity === 'Medium') severityWeight = 10;
61
- else if (v.severity === 'Low') severityWeight = 5;
73
+ if (v.severity === 'critical') severityWeight = 25;
74
+ else if (v.severity === 'high') severityWeight = 15;
75
+ else if (v.severity === 'medium') severityWeight = 10;
76
+ else if (v.severity === 'low') severityWeight = 5;
77
+
78
+ // Reduzir peso de achados baseados em caminhos (potential/enumeration)
79
+ if (v.kind === 'path' && v.category === 'probable') severityWeight *= 0.5;
62
80
 
63
81
  let confidenceMultiplier = 1;
64
- if (v.confidence === 'Medium') confidenceMultiplier = 0.6;
65
- if (v.confidence === 'Low') confidenceMultiplier = 0.3;
82
+ if (v.confidence === 'medium') confidenceMultiplier = 0.6;
83
+ if (v.confidence === 'low') confidenceMultiplier = 0.3;
66
84
 
67
85
  return acc + (severityWeight * confidenceMultiplier);
68
86
  }, 0);
69
87
 
70
- const finalScore = Math.max(0, Math.round(baseScore - penalties));
71
- let riskLevel = 'Low';
72
- if (finalScore < 50) riskLevel = 'Critical';
73
- else if (finalScore < 70) riskLevel = 'High';
74
- else if (finalScore < 90) riskLevel = 'Medium';
88
+ scanData.score = Math.max(0, Math.round(100 - penalties));
75
89
 
76
- return {
77
- target,
78
- scan_id: `OMEN-REQ-${Date.now()}`,
79
- timestamp: new Date().toISOString(),
80
- score: finalScore,
81
- riskLevel: riskLevel,
82
- attack_surface,
83
- headers_analysis,
84
- vulnerabilities: allVulnerabilities.length > 0 ? allVulnerabilities : [{ id: 'INFO', type: 'Clean', severity: 'Info', description: 'No immediate vulnerabilities detected in the surface scan.' }]
85
- };
90
+ if (scanData.score < 40) scanData.riskLevel = 'Critical';
91
+ else if (scanData.score < 60) scanData.riskLevel = 'High';
92
+ else if (scanData.score < 80) scanData.riskLevel = 'Medium';
93
+ else scanData.riskLevel = 'Low';
94
+
95
+ return scanData;
86
96
  }