omen-sec-cli 1.0.16 → 1.0.17

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/bin/index.js CHANGED
@@ -15,7 +15,7 @@ async function main() {
15
15
  }
16
16
 
17
17
  if (args.flags.version) {
18
- console.log('1.0.16');
18
+ console.log('1.0.17');
19
19
  return;
20
20
  }
21
21
 
package/core/generator.js CHANGED
@@ -18,6 +18,11 @@ export async function generateOutputs(scanData) {
18
18
  }
19
19
 
20
20
  const jsonContent = JSON.stringify(scanData, null, 2);
21
+
22
+ // Simple Schema Validation
23
+ if (!scanData.schema_version || !scanData.scan_id || !Array.isArray(scanData.vulnerabilities)) {
24
+ console.error(chalk.red('\n[Error] Report data validation failed. Schema 1.0 requirements not met.'));
25
+ }
21
26
 
22
27
  // JSON Report
23
28
  const jsonReportPath = path.join(outputDir, 'omen-report.json');
@@ -26,7 +31,28 @@ export async function generateOutputs(scanData) {
26
31
 
27
32
  // TXT Report
28
33
  const txtReportPath = path.join(outputDir, 'omen-report.txt');
29
- const txtContent = `OMEN SECURITY REPORT\n\nTarget: ${scanData.target}\nScore: ${scanData.score}\nRisk: ${scanData.riskLevel}\n\nVulnerabilities:\n${scanData.vulnerabilities.map(v => `- ${v.description}`).join('\n')}`;
34
+ let txtContent = `OMEN SECURITY REPORT\n`;
35
+ txtContent += `====================\n`;
36
+ txtContent += `Target: ${scanData.target}\n`;
37
+ txtContent += `Scan ID: ${scanData.scan_id}\n`;
38
+ txtContent += `Date: ${scanData.timestamp}\n`;
39
+ txtContent += `Score: ${scanData.score}/100\n`;
40
+ txtContent += `Risk Level: ${scanData.riskLevel}\n\n`;
41
+
42
+ txtContent += `VULNERABILITIES FOUND:\n`;
43
+ txtContent += `----------------------\n`;
44
+
45
+ if (scanData.vulnerabilities.length === 0) {
46
+ txtContent += `No vulnerabilities detected.\n`;
47
+ } else {
48
+ scanData.vulnerabilities.forEach(v => {
49
+ txtContent += `[${v.severity.toUpperCase()}] ${v.title || v.description}\n`;
50
+ txtContent += `Category: ${v.category} | Confidence: ${v.confidence}\n`;
51
+ txtContent += `Description: ${v.description}\n`;
52
+ if (v.remediation) txtContent += `Remediation: ${v.remediation}\n`;
53
+ txtContent += `----------------------\n`;
54
+ });
55
+ }
30
56
  await fs.writeFile(txtReportPath, txtContent);
31
57
  console.log(` /omen-reports/omen-report.txt`);
32
58
 
@@ -45,10 +45,15 @@ export async function scanLocalProject() {
45
45
  osvRes.data.vulns.forEach(vuln => {
46
46
  vulnerabilities.push({
47
47
  id: vuln.id,
48
- type: 'Vulnerable Component (OSV.dev)',
49
- severity: 'High',
48
+ kind: 'tech',
49
+ category: 'confirmed',
50
+ confidence: 'high',
51
+ severity: 'high',
52
+ title: `Vulnerable Dependency: ${name}`,
50
53
  description: `Real CVE found for ${name}@${cleanVersion}: ${vuln.summary || vuln.details}`,
51
- cwe: vuln.database_specific?.cwe_ids?.[0] || 'N/A'
54
+ cwe: vuln.database_specific?.cwe_ids?.[0] || 'N/A',
55
+ evidence: { package: name, version: cleanVersion, vuln_id: vuln.id },
56
+ remediation: `Update ${name} to a secure version as recommended by OSV/NVD.`
52
57
  });
53
58
  });
54
59
  }
@@ -57,27 +62,33 @@ export async function scanLocalProject() {
57
62
  }
58
63
  }
59
64
 
60
- // Exemplos de dependências conhecidas (fallback mock)
65
+ // Fallback mocks
61
66
  if (deps['lodash'] && deps['lodash'].match(/[~^]?4\.17\.[0-20]/)) {
62
67
  vulnerabilities.push({
63
68
  id: `LOC-VULN-${Date.now()}-1`,
64
- category: 'Confirmed',
65
- confidence: 'High',
66
- severity: 'High',
69
+ kind: 'tech',
70
+ category: 'confirmed',
71
+ confidence: 'high',
72
+ severity: 'high',
73
+ title: 'Vulnerable Lodash Version',
67
74
  description: `Outdated dependency detected in package.json: lodash (${deps['lodash']}). Known Prototype Pollution risk.`,
68
75
  cwe: 'CWE-1321',
69
- evidence: { file: 'package.json', finding: `lodash: ${deps['lodash']}` }
76
+ evidence: { file: 'package.json', finding: `lodash: ${deps['lodash']}` },
77
+ remediation: 'Update lodash to version 4.17.21 or higher.'
70
78
  });
71
79
  }
72
80
  if (deps['express'] && deps['express'].match(/[~^]?3\./)) {
73
81
  vulnerabilities.push({
74
82
  id: `LOC-VULN-${Date.now()}-2`,
75
- category: 'Confirmed',
76
- confidence: 'High',
77
- severity: 'High',
83
+ kind: 'tech',
84
+ category: 'confirmed',
85
+ confidence: 'high',
86
+ severity: 'high',
87
+ title: 'Critical Outdated Express',
78
88
  description: `Severely outdated Express.js version (${deps['express']}) detected. Multiple CVEs exist.`,
79
89
  cwe: 'CWE-1104',
80
- evidence: { file: 'package.json', finding: `express: ${deps['express']}` }
90
+ evidence: { file: 'package.json', finding: `express: ${deps['express']}` },
91
+ remediation: 'Upgrade Express.js to the latest stable version (4.x or 5.x).'
81
92
  });
82
93
  }
83
94
  } catch (err) {
@@ -98,55 +109,67 @@ export async function scanLocalProject() {
98
109
  filesScanned.push(path.basename(file));
99
110
 
100
111
  lines.forEach((line, index) => {
101
- // Regra 1: Hardcoded Secrets (AWS Keys, API Keys simples)
112
+ // Regra 1: Hardcoded Secrets
102
113
  if (/(api_key|apikey|secret|password|token)\s*=\s*['"][a-zA-Z0-9_-]{10,}['"]/i.test(line)) {
103
114
  vulnerabilities.push({
104
115
  id: `LOC-VULN-${Date.now()}-3`,
105
- category: 'Confirmed',
106
- confidence: 'Medium', // Could be a test key
107
- severity: 'Critical',
116
+ kind: 'content',
117
+ category: 'confirmed',
118
+ confidence: 'medium',
119
+ severity: 'critical',
120
+ title: 'Hardcoded Secret Detected',
108
121
  description: `Potential hardcoded secret found in ${path.basename(file)} at line ${index + 1}`,
109
122
  cwe: 'CWE-798',
110
- evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
123
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() },
124
+ remediation: 'Move secrets to environment variables or a secure vault.'
111
125
  });
112
126
  }
113
127
 
114
- // Regra 2: Uso de Eval (Code Injection)
128
+ // Regra 2: Uso de Eval
115
129
  if (/eval\s*\(/.test(line)) {
116
130
  vulnerabilities.push({
117
131
  id: `LOC-VULN-${Date.now()}-4`,
118
- category: 'Confirmed',
119
- confidence: 'High',
120
- severity: 'Critical',
132
+ kind: 'content',
133
+ category: 'confirmed',
134
+ confidence: 'high',
135
+ severity: 'critical',
136
+ title: 'Dangerous eval() Usage',
121
137
  description: `Dangerous use of eval() detected in ${path.basename(file)} at line ${index + 1}`,
122
138
  cwe: 'CWE-94',
123
- evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
139
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() },
140
+ remediation: 'Avoid using eval(). Use safer alternatives like JSON.parse() or specific logic.'
124
141
  });
125
142
  }
126
143
 
127
- // Regra 3: SQLi (Concatenação crua de strings com SELECT)
144
+ // Regra 3: SQLi
128
145
  if (/SELECT.*FROM.*WHERE.*(\+|`|\${)/i.test(line)) {
129
146
  vulnerabilities.push({
130
147
  id: `LOC-VULN-${Date.now()}-5`,
131
- category: 'Probable',
132
- confidence: 'Medium',
133
- severity: 'Critical',
148
+ kind: 'content',
149
+ category: 'probable',
150
+ confidence: 'medium',
151
+ severity: 'critical',
152
+ title: 'Potential SQL Injection',
134
153
  description: `Potential SQL Injection (raw string concatenation) in ${path.basename(file)} at line ${index + 1}.`,
135
154
  cwe: 'CWE-89',
136
- evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
155
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() },
156
+ remediation: 'Use parameterized queries or an ORM to prevent SQL injection.'
137
157
  });
138
158
  }
139
159
 
140
- // Regra 6: Local File Inclusion (LFI) via require/import dinâmico
160
+ // Regra 6: LFI
141
161
  if (/(require|import)\s*\(['"]?.*(\+|`|\${)/i.test(line)) {
142
162
  vulnerabilities.push({
143
163
  id: `LOC-VULN-${Date.now()}-6`,
144
- category: 'Probable',
145
- confidence: 'Medium',
146
- severity: 'High',
147
- description: `Potential LFI detected in ${path.basename(file)} at line ${index + 1}. Dynamic loading of files based on user input can lead to sensitive data exposure.`,
164
+ kind: 'content',
165
+ category: 'probable',
166
+ confidence: 'medium',
167
+ severity: 'high',
168
+ title: 'Potential LFI',
169
+ description: `Potential Local File Inclusion (LFI) detected in ${path.basename(file)} at line ${index + 1}.`,
148
170
  cwe: 'CWE-22',
149
- evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
171
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() },
172
+ remediation: 'Validate and sanitize file paths before dynamic loading.'
150
173
  });
151
174
  }
152
175
 
@@ -154,12 +177,15 @@ export async function scanLocalProject() {
154
177
  if (/\.prototype\.[a-zA-Z0-9_]+\s*=\s*/.test(line)) {
155
178
  vulnerabilities.push({
156
179
  id: `LOC-VULN-${Date.now()}-7`,
157
- category: 'Hardening',
158
- confidence: 'Low',
159
- severity: 'Medium',
160
- description: `Direct prototype modification in ${path.basename(file)} at line ${index + 1}. This can lead to Prototype Pollution if user input reaches this assignment.`,
180
+ kind: 'content',
181
+ category: 'hardening',
182
+ confidence: 'low',
183
+ severity: 'medium',
184
+ title: 'Prototype Modification Risk',
185
+ description: `Direct prototype modification in ${path.basename(file)} at line ${index + 1}.`,
161
186
  cwe: 'CWE-1321',
162
- evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
187
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() },
188
+ remediation: 'Avoid direct prototype modification. Use Object.defineProperty if necessary.'
163
189
  });
164
190
  }
165
191
 
@@ -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,19 +93,20 @@ 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.'
99
109
  });
100
- } else {
101
- headers_analysis["X-Frame-Options"] = headers['x-frame-options'];
102
110
  }
103
111
 
104
112
  // 4. Server Header Leak
@@ -106,27 +114,31 @@ export async function scanRemoteTarget(targetUrl) {
106
114
  headers_analysis["Server"] = headers['server'];
107
115
  vulnerabilities.push({
108
116
  id: `REM-VULN-${Date.now()}-5`,
109
- category: 'Informational',
110
- confidence: 'High',
111
- severity: 'Low',
112
- description: `Server header leaks technology stack: ${headers['server']}`,
117
+ kind: 'tech',
118
+ category: 'informational',
119
+ confidence: 'high',
120
+ severity: 'low',
121
+ title: 'Server Header Disclosure',
122
+ description: `Server header leaks technology stack information: ${headers['server']}`,
113
123
  cwe: 'CWE-200',
114
- evidence: { finding: `Server: ${headers['server']}` }
124
+ evidence: { finding: `Server: ${headers['server']}` },
125
+ remediation: 'Configure the web server to hide or spoof the "Server" header.'
115
126
  });
116
- } else {
117
- headers_analysis["Server"] = "Hidden (Good)";
118
127
  }
119
128
 
120
129
  // 5. X-Powered-By Leak
121
130
  if (headers['x-powered-by']) {
122
131
  vulnerabilities.push({
123
132
  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']}`,
133
+ kind: 'tech',
134
+ category: 'informational',
135
+ confidence: 'high',
136
+ severity: 'low',
137
+ title: 'X-Powered-By Disclosure',
138
+ description: `X-Powered-By header leaks framework information: ${headers['x-powered-by']}`,
128
139
  cwe: 'CWE-200',
129
- evidence: { finding: `X-Powered-By: ${headers['x-powered-by']}` }
140
+ evidence: { finding: `X-Powered-By: ${headers['x-powered-by']}` },
141
+ remediation: 'Disable the X-Powered-By header in the application framework settings.'
130
142
  });
131
143
  }
132
144
 
@@ -175,6 +187,22 @@ export async function scanRemoteTarget(targetUrl) {
175
187
  });
176
188
  }
177
189
 
190
+ // --- TECHNOLOGY FINGERPRINTING (Update categories) ---
191
+ if (techStack.length > 0) {
192
+ vulnerabilities.push({
193
+ id: `REM-TECH-${Date.now()}`,
194
+ kind: 'tech',
195
+ category: 'informational',
196
+ confidence: 'high',
197
+ severity: 'info',
198
+ title: 'Technology Stack Identified',
199
+ description: `Fingerprinting identified the following technologies: ${techStack.join(', ')}`,
200
+ cwe: 'CWE-200',
201
+ evidence: { tech_stack: techStack },
202
+ remediation: 'Minimal tech disclosure is recommended to prevent targeted attacks.'
203
+ });
204
+ }
205
+
178
206
  // --- DEEP CRAWL: robots.txt, sitemap.xml, and JS files ---
179
207
  const robotsUrl = new URL('/robots.txt', targetUrl).href;
180
208
  try {
@@ -335,22 +363,28 @@ async function validateFuzzerFinding(path, response, url) {
335
363
  if (typeof data === 'string' && !data.trim().startsWith('<html')) {
336
364
  return {
337
365
  id: `REM-CONFIRMED-FILE-${Date.now()}`,
338
- category: 'Confirmed',
339
- confidence: 'High',
340
- severity: 'Critical',
366
+ kind: 'content',
367
+ category: 'confirmed',
368
+ confidence: 'high',
369
+ severity: 'critical',
370
+ title: 'Sensitive File Exposed',
341
371
  description: `CRITICAL: Sensitive file exposed at ${url}. Contents contain raw configuration data.`,
342
372
  cwe: 'CWE-538',
343
- evidence: { ...evidence, reason: 'Raw sensitive file content detected (Non-HTML response on sensitive path)' }
373
+ evidence: { ...evidence, reason: 'Raw sensitive file content detected (Non-HTML response on sensitive path)' },
374
+ remediation: 'Immediately remove the file from the web server and ensure sensitive files are not publicly accessible.'
344
375
  };
345
376
  } else {
346
377
  return {
347
378
  id: `REM-POTENTIAL-FILE-${Date.now()}`,
348
- category: 'Informational',
349
- confidence: 'Low',
350
- severity: 'Info',
379
+ kind: 'path',
380
+ category: 'informational',
381
+ confidence: 'low',
382
+ severity: 'info',
383
+ title: 'Potential Sensitive Path Discovered',
351
384
  description: `Potential sensitive path found at ${url}, but returned HTML content. Likely a redirect or custom error page.`,
352
385
  cwe: 'CWE-200',
353
- evidence: { ...evidence, reason: 'HTML response on sensitive file path' }
386
+ evidence: { ...evidence, reason: 'HTML response on sensitive file path' },
387
+ remediation: 'Verify if this path should be accessible and ensure no sensitive information is leaked through custom error pages.'
354
388
  };
355
389
  }
356
390
  }
@@ -361,23 +395,29 @@ async function validateFuzzerFinding(path, response, url) {
361
395
  if (typeof data === 'string' && /password|login/i.test(data)) {
362
396
  return {
363
397
  id: `REM-INFO-LOGIN-${Date.now()}`,
364
- category: 'Informational',
365
- confidence: 'High',
366
- severity: 'Info',
398
+ kind: 'path',
399
+ category: 'informational',
400
+ confidence: 'high',
401
+ severity: 'info',
402
+ title: 'Admin Login Page Discovered',
367
403
  description: `Admin login page discovered at ${url}.`,
368
404
  cwe: 'CWE-200',
369
- evidence: { ...evidence, reason: '200 OK with login/password patterns detected in HTML.' }
405
+ evidence: { ...evidence, reason: '200 OK with login/password patterns detected in HTML.' },
406
+ remediation: 'Ensure the admin login page is protected by strong authentication and not easily discoverable if not necessary.'
370
407
  };
371
408
  }
372
409
  // If it's a 200 but not a login page, it could be an exposed panel
373
410
  return {
374
411
  id: `REM-PROBABLE-PANEL-${Date.now()}`,
375
- category: 'Probable',
376
- confidence: 'Low',
377
- severity: 'Medium',
412
+ kind: 'path',
413
+ category: 'probable',
414
+ confidence: 'low',
415
+ severity: 'medium',
416
+ title: 'Potential Admin Panel Exposure',
378
417
  description: `Potential exposed admin panel or dashboard at ${url}. Manual verification required.`,
379
418
  cwe: 'CWE-284',
380
- evidence: { ...evidence, reason: '200 OK on admin-like path, but no explicit login form detected. Could be an unauthorized dashboard.' }
419
+ evidence: { ...evidence, reason: '200 OK on admin-like path, but no explicit login form detected. Could be an unauthorized dashboard.' },
420
+ remediation: 'Restrict access to the admin panel using IP whitelisting or other access control mechanisms.'
381
421
  };
382
422
  }
383
423
 
@@ -385,15 +425,18 @@ async function validateFuzzerFinding(path, response, url) {
385
425
  if (status === 403) {
386
426
  return {
387
427
  id: `REM-INFO-FORBIDDEN-${Date.now()}`,
388
- category: 'Informational',
389
- confidence: 'Medium',
390
- severity: 'Info',
428
+ kind: 'path',
429
+ category: 'informational',
430
+ confidence: 'medium',
431
+ severity: 'info',
432
+ title: 'Protected Path Discovered',
391
433
  description: `Potential protected path discovered (403 Forbidden): ${url}. This confirms the path exists but access is restricted.`,
392
434
  cwe: 'CWE-204',
393
435
  evidence: {
394
436
  ...evidence,
395
437
  reason: 'Server returned 403 Forbidden. This confirms path existence but access control is active. No immediate exposure detected.'
396
- }
438
+ },
439
+ remediation: 'None required, but ensure that the 403 response does not leak information about the internal structure.'
397
440
  };
398
441
  }
399
442
 
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
  }
package/core/ui-server.js CHANGED
@@ -77,19 +77,19 @@ export async function startUIServer() {
77
77
  <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-10">
78
78
  <div class="card p-6 rounded-xl shadow-2xl">
79
79
  <h3 class="text-gray-500 uppercase text-xs font-bold tracking-widest mb-2">Security Score</h3>
80
- <p class="text-5xl font-black ${getSeverityClass(report.riskLevel)}">${report.score}/100</p>
80
+ <p class="text-5xl font-black ${getSeverityClass(report.riskLevel)}">${report.score || 0}/100</p>
81
81
  </div>
82
82
  <div class="card p-6 rounded-xl shadow-2xl">
83
83
  <h3 class="text-gray-500 uppercase text-xs font-bold tracking-widest mb-2">Risk Level</h3>
84
- <p class="text-5xl font-black ${getSeverityClass(report.riskLevel)}">${report.riskLevel}</p>
84
+ <p class="text-5xl font-black ${getSeverityClass(report.riskLevel)}">${report.riskLevel || 'N/A'}</p>
85
85
  </div>
86
86
  <div class="card p-6 rounded-xl shadow-2xl">
87
87
  <h3 class="text-gray-500 uppercase text-xs font-bold tracking-widest mb-2">Confirmed Issues</h3>
88
- <p class="text-5xl font-black text-white">${report.vulnerabilities.filter(v => v.category === 'Confirmed').length}</p>
88
+ <p class="text-5xl font-black text-white">${(report.vulnerabilities || []).filter(v => v.category === 'confirmed').length}</p>
89
89
  </div>
90
90
  <div class="card p-6 rounded-xl shadow-2xl">
91
91
  <h3 class="text-gray-500 uppercase text-xs font-bold tracking-widest mb-2">Probable Issues</h3>
92
- <p class="text-5xl font-black text-white">${report.vulnerabilities.filter(v => v.category === 'Probable').length}</p>
92
+ <p class="text-5xl font-black text-white">${(report.vulnerabilities || []).filter(v => v.category === 'probable').length}</p>
93
93
  </div>
94
94
  </div>
95
95
 
@@ -99,13 +99,13 @@ export async function startUIServer() {
99
99
  <span class="w-2 h-2 bg-red-500 rounded-full mr-2"></span> Top Critical Findings
100
100
  </h2>
101
101
  <div class="space-y-4">
102
- ${report.vulnerabilities.filter(v => v.severity === 'Critical' || v.severity === 'High').slice(0, 5).map(v => `
102
+ ${(report.vulnerabilities || []).filter(v => v.severity === 'critical' || v.severity === 'high').slice(0, 5).map(v => `
103
103
  <div class="p-4 bg-[#1c1c22] rounded-lg border border-gray-800">
104
104
  <div class="flex justify-between items-start mb-2">
105
- <span class="font-bold ${getSeverityClass(v.severity)} text-sm">${v.severity}</span>
105
+ <span class="font-bold ${getSeverityClass(v.severity)} text-sm uppercase">${v.severity}</span>
106
106
  <span class="px-2 py-0.5 rounded border text-[10px] uppercase font-bold ${getCategoryBadge(v.category)}">${v.category}</span>
107
107
  </div>
108
- <p class="text-sm font-medium text-gray-300">${v.description}</p>
108
+ <p class="text-sm font-medium text-gray-300">${v.title || v.description}</p>
109
109
  </div>
110
110
  `).join('') || '<p class="text-gray-500 italic">No critical findings discovered.</p>'}
111
111
  </div>
@@ -115,10 +115,10 @@ export async function startUIServer() {
115
115
  <span class="w-2 h-2 bg-blue-500 rounded-full mr-2"></span> Scan Intelligence
116
116
  </h2>
117
117
  <div class="space-y-4 text-sm">
118
- <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Target URL</span> <span class="mono text-gray-300">${report.target}</span> </div>
119
- <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Tech Stack</span> <span class="text-gray-300">${(report.attack_surface.tech_stack || []).join(', ') || 'N/A'}</span> </div>
120
- <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Endpoints Discovered</span> <span class="text-gray-300">${(report.attack_surface.endpoints || []).length}</span> </div>
121
- <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Forms Detected</span> <span class="text-gray-300">${(report.attack_surface.forms || []).length}</span> </div>
118
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Target URL</span> <span class="mono text-gray-300">${report.target || 'N/A'}</span> </div>
119
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Tech Stack</span> <span class="text-gray-300">${(report.attack_surface?.tech_stack || []).join(', ') || 'N/A'}</span> </div>
120
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Endpoints Discovered</span> <span class="text-gray-300">${(report.attack_surface?.endpoints || []).length}</span> </div>
121
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Forms Detected</span> <span class="text-gray-300">${(report.attack_surface?.forms || []).length}</span> </div>
122
122
  </div>
123
123
  </div>
124
124
  </div>
@@ -134,26 +134,30 @@ export async function startUIServer() {
134
134
  </div>
135
135
  </div>
136
136
  <div class="divide-y divide-gray-800">
137
- ${report.vulnerabilities.map((v, i) => `
137
+ ${(report.vulnerabilities || []).map((v, i) => `
138
138
  <div class="finding-row">
139
139
  <div class="p-4 flex items-center cursor-pointer gap-4" onclick="toggleEvidence(${i})">
140
140
  <div class="w-24 text-xs font-black uppercase ${getSeverityClass(v.severity)}">${v.severity}</div>
141
- <div class="flex-1 text-sm font-semibold text-gray-300">${v.description}</div>
141
+ <div class="flex-1 text-sm font-semibold text-gray-300">${v.title || v.description}</div>
142
142
  <div class="px-2 py-1 rounded border text-[10px] uppercase font-bold ${getCategoryBadge(v.category)}">${v.category}</div>
143
- <div class="text-[10px] mono text-gray-600">${v.id}</div>
143
+ <div class="text-[10px] mono text-gray-600">${v.kind || 'N/A'}</div>
144
144
  </div>
145
145
  <div id="evidence-${i}" class="evidence-panel p-6 bg-black border-t border-gray-800">
146
146
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
147
147
  <div>
148
148
  <h4 class="text-xs font-bold uppercase text-gray-500 mb-3 tracking-widest">Discovery Evidence</h4>
149
- <pre class="mono">${JSON.stringify(v.evidence, null, 2)}</pre>
149
+ <pre class="mono">${JSON.stringify(v.evidence || {}, null, 2)}</pre>
150
150
  </div>
151
151
  <div>
152
152
  <h4 class="text-xs font-bold uppercase text-gray-500 mb-3 tracking-widest">Technical Details</h4>
153
153
  <div class="space-y-3 text-sm">
154
- <p><span class="text-gray-500">CWE:</span> <span class="text-blue-400 mono">${v.cwe}</span></p>
155
- <p><span class="text-gray-500">Confidence:</span> <span class="font-bold text-gray-300">${v.confidence}</span></p>
156
- <p><span class="text-gray-500">Remediation:</span> <span class="text-gray-400 italic">Consult OMEN AI Protocol for detailed fix.</span></p>
154
+ <p><span class="text-gray-500 font-bold uppercase text-[10px]">CWE:</span> <span class="text-blue-400 mono">${v.cwe || 'N/A'}</span></p>
155
+ <p><span class="text-gray-500 font-bold uppercase text-[10px]">Confidence:</span> <span class="font-bold text-gray-300 uppercase">${v.confidence || 'N/A'}</span></p>
156
+ <p><span class="text-gray-500 font-bold uppercase text-[10px]">Description:</span> <span class="text-gray-300">${v.description || 'N/A'}</span></p>
157
+ <div class="mt-4 p-3 bg-blue-900/20 border border-blue-800 rounded">
158
+ <h5 class="text-[10px] font-bold text-blue-400 uppercase mb-1">Remediation</h5>
159
+ <p class="text-xs text-gray-300">${v.remediation || 'Consult OMEN AI Protocol for detailed fix.'}</p>
160
+ </div>
157
161
  </div>
158
162
  </div>
159
163
  </div>
@@ -170,18 +174,18 @@ export async function startUIServer() {
170
174
  <div class="lg:col-span-2 space-y-8">
171
175
  <div class="card p-6 rounded-xl">
172
176
  <h3 class="text-xl font-bold mb-4">Discovered Assets</h3>
173
- <pre class="mono max-h-[600px]">${(report.attack_surface.endpoints || []).join('\n') || 'No endpoints discovered.'}</pre>
177
+ <pre class="mono max-h-[600px]">${(report.attack_surface?.endpoints || []).join('\n') || 'No endpoints discovered.'}</pre>
174
178
  </div>
175
179
  </div>
176
180
  <div class="space-y-8">
177
181
  <div class="card p-6 rounded-xl">
178
182
  <h3 class="text-xl font-bold mb-4">Forms & Inputs</h3>
179
- <pre class="mono">${JSON.stringify(report.attack_surface.forms || [], null, 2)}</pre>
183
+ <pre class="mono">${JSON.stringify(report.attack_surface?.forms || [], null, 2)}</pre>
180
184
  </div>
181
185
  <div class="card p-6 rounded-xl">
182
186
  <h3 class="text-xl font-bold mb-4">Tech Fingerprint</h3>
183
187
  <div class="flex flex-wrap gap-2">
184
- ${(report.attack_surface.tech_stack || []).map(t => `<span class="px-3 py-1 bg-gray-800 rounded-full text-xs font-bold text-blue-400 border border-gray-700">${t}</span>`).join('') || '<span class="text-gray-500 italic">No stack identified.</span>'}
188
+ ${(report.attack_surface?.tech_stack || []).map(t => `<span class="px-3 py-1 bg-gray-800 rounded-full text-xs font-bold text-blue-400 border border-gray-700">${t}</span>`).join('') || '<span class="text-gray-500 italic">No stack identified.</span>'}
185
189
  </div>
186
190
  </div>
187
191
  </div>
@@ -198,7 +202,7 @@ export async function startUIServer() {
198
202
  </div>
199
203
 
200
204
  <footer class="text-center text-gray-600 mt-16 border-t border-gray-900 pt-8 mb-10">
201
- <p class="text-xs uppercase tracking-widest font-bold mb-2">OMEN Security Framework - v1.0.16</p>
205
+ <p class="text-xs uppercase tracking-widest font-bold mb-2">OMEN Security Framework - v1.0.17</p>
202
206
  <p class="text-[10px] text-gray-700 italic">"The eye that never sleeps, the code that never fails."</p>
203
207
  </footer>
204
208
  </div>
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "omen-sec-cli",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "OMEN — AI Security Engine",
5
+ "engines": {
6
+ "node": ">=20.0.0"
7
+ },
5
8
  "main": "bin/index.js",
6
9
  "type": "module",
7
10
  "bin": {
package/ui/banner.js CHANGED
@@ -9,7 +9,7 @@ export function showBanner() {
9
9
  ╚██████╔╝██║ ╚═╝ ██║███████╗██║ ╚████║
10
10
  `));
11
11
  console.log(chalk.cyan.bold(' OMEN — AI Security Engine '));
12
- console.log(chalk.gray(' Version: 1.0.16 \n'));
12
+ console.log(chalk.gray(' Version: 1.0.17 \n'));
13
13
  }
14
14
 
15
15
  export function showHelp() {