omen-sec-cli 1.0.15 → 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.14');
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,36 +395,48 @@ 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',
367
- description: `Admin login page discovered at ${url}. This is part of the attack surface.`,
398
+ kind: 'path',
399
+ category: 'informational',
400
+ confidence: 'high',
401
+ severity: 'info',
402
+ title: 'Admin Login Page Discovered',
403
+ description: `Admin login page discovered at ${url}.`,
368
404
  cwe: 'CWE-200',
369
- evidence
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: 'Medium',
377
- severity: 'High',
378
- description: `Potential exposed admin panel at ${url}. Manual verification required.`,
379
- cwe: 'CWE-284', // Improper Access Control
380
- evidence
412
+ kind: 'path',
413
+ category: 'probable',
414
+ confidence: 'low',
415
+ severity: 'medium',
416
+ title: 'Potential Admin Panel Exposure',
417
+ description: `Potential exposed admin panel or dashboard at ${url}. Manual verification required.`,
418
+ cwe: 'CWE-284',
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
 
384
- // Rule 4: Treat 403 Forbidden as low-confidence informational
424
+ // Rule 4: Treat 403 Forbidden as low-confidence informational (Path Enumeration)
385
425
  if (status === 403) {
386
426
  return {
387
427
  id: `REM-INFO-FORBIDDEN-${Date.now()}`,
388
- category: 'Informational',
389
- confidence: 'Low',
390
- severity: 'Info',
391
- description: `Path exists but access is restricted (403 Forbidden): ${url}. This confirms the path's existence but does not prove exposure.`,
392
- cwe: 'CWE-204', // Response Discrepancy (Path Enumeration)
393
- evidence
428
+ kind: 'path',
429
+ category: 'informational',
430
+ confidence: 'medium',
431
+ severity: 'info',
432
+ title: 'Protected Path Discovered',
433
+ description: `Potential protected path discovered (403 Forbidden): ${url}. This confirms the path exists but access is restricted.`,
434
+ cwe: 'CWE-204',
435
+ evidence: {
436
+ ...evidence,
437
+ reason: 'Server returned 403 Forbidden. This confirms path existence but access control is active. No immediate exposure detected.'
438
+ },
439
+ remediation: 'None required, but ensure that the 403 response does not leak information about the internal structure.'
394
440
  };
395
441
  }
396
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
@@ -20,10 +20,11 @@ export async function startUIServer() {
20
20
  return 'text-green-500';
21
21
  };
22
22
 
23
- const getConfidenceClass = (confidence) => {
24
- if (confidence === 'High') return 'text-green-400';
25
- if (confidence === 'Medium') return 'text-yellow-400';
26
- return 'text-red-400';
23
+ const getCategoryBadge = (category) => {
24
+ if (category === 'Confirmed') return 'bg-red-900/50 text-red-400 border-red-800';
25
+ if (category === 'Probable') return 'bg-orange-900/50 text-orange-400 border-orange-800';
26
+ if (category === 'Hardening') return 'bg-blue-900/50 text-blue-400 border-blue-800';
27
+ return 'bg-gray-800 text-gray-400 border-gray-700';
27
28
  };
28
29
 
29
30
  const html = `
@@ -32,61 +33,134 @@ export async function startUIServer() {
32
33
  <head>
33
34
  <meta charset="UTF-8">
34
35
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
35
- <title>OMEN SEC-CLI Dashboard</title>
36
+ <title>OMEN SEC-CLI Evidence Center</title>
36
37
  <script src="https://cdn.tailwindcss.com"></script>
38
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
37
39
  <style>
38
40
  body { background-color: #0a0a0c; color: #e0e0e0; font-family: 'Inter', sans-serif; }
41
+ .mono { font-family: 'JetBrains Mono', monospace; }
39
42
  .card { background-color: #16161a; border: 1px solid #2d2d3a; }
40
- .tab { cursor: pointer; padding: 10px 15px; border-bottom: 2px solid transparent; }
41
- .tab.active { border-bottom: 2px solid #ef4444; color: #ef4444; }
42
- .collapsible { cursor: pointer; }
43
- .content { display: none; }
44
- pre { background-color: #000; padding: 10px; border-radius: 5px; overflow-x: auto; }
43
+ .tab-btn { cursor: pointer; padding: 12px 24px; border-bottom: 2px solid transparent; transition: all 0.2s; font-weight: 600; }
44
+ .tab-btn:hover { background-color: #1f1f26; }
45
+ .tab-btn.active { border-bottom: 2px solid #ef4444; color: #ef4444; background-color: #1f1f26; }
46
+ .content-area { display: none; }
47
+ .content-area.active { display: block; }
48
+ pre { background-color: #000; padding: 15px; border-radius: 8px; border: 1px solid #333; overflow-x: auto; font-size: 0.85rem; }
49
+ .finding-row { transition: background-color 0.2s; }
50
+ .finding-row:hover { background-color: #1f1f26; }
51
+ .evidence-panel { display: none; }
45
52
  </style>
46
53
  </head>
47
54
  <body class="p-4 md:p-8">
48
55
  <div class="max-w-7xl mx-auto">
49
- <header class="flex justify-between items-center mb-8 border-b border-gray-800 pb-4">
50
- <h1 class="text-3xl font-bold tracking-tighter text-red-500">OMEN <span class="text-white">EVIDENCE-CENTER</span></h1>
51
- <div class="text-right text-sm">
52
- <p class="text-gray-400">Scan ID: ${report.scan_id}</p>
53
- <p class="text-gray-400">${new Date(report.timestamp).toLocaleString()}</p>
56
+ <header class="flex justify-between items-end mb-10 border-b border-gray-800 pb-6">
57
+ <div>
58
+ <h1 class="text-4xl font-extrabold tracking-tighter text-red-500 mb-1">OMEN <span class="text-white">SEC-CLI</span></h1>
59
+ <p class="text-gray-500 font-medium">Professional DevSecOps Audit Framework</p>
60
+ </div>
61
+ <div class="text-right text-sm font-medium text-gray-500">
62
+ <p>Scan ID: <span class="text-gray-300 mono">${report.scan_id}</span></p>
63
+ <p>Date: <span class="text-gray-300">${new Date(report.timestamp).toLocaleString()}</span></p>
54
64
  </div>
55
65
  </header>
56
66
 
57
- <!-- Tabs -->
58
- <div class="flex border-b border-gray-700 mb-6">
59
- <div class="tab active" onclick="showTab('dashboard')">Dashboard</div>
60
- <div class="tab" onclick="showTab('findings')">Findings</div>
61
- <div class="tab" onclick="showTab('surface')">Attack Surface</div>
62
- <div class="tab" onclick="showTab('history')">History</div>
67
+ <!-- Navigation Tabs -->
68
+ <div class="flex border-b border-gray-800 mb-8 bg-[#111116] rounded-t-xl overflow-hidden">
69
+ <div class="tab-btn active" data-target="dashboard">Dashboard</div>
70
+ <div class="tab-btn" data-target="findings">Findings</div>
71
+ <div class="tab-btn" data-target="surface">Attack Surface</div>
72
+ <div class="tab-btn" data-target="history">History</div>
63
73
  </div>
64
74
 
65
75
  <!-- Dashboard Tab -->
66
- <div id="dashboard" class="tab-content block">
67
- <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
68
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Security Score</h3><p class="text-4xl font-bold ${getSeverityClass(report.riskLevel)}">${report.score}/100</p></div>
69
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Risk Level</h3><p class="text-4xl font-bold ${getSeverityClass(report.riskLevel)}">${report.riskLevel}</p></div>
70
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Confirmed</h3><p class="text-4xl font-bold">${report.vulnerabilities.filter(v => v.category === 'Confirmed').length}</p></div>
71
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Probable</h3><p class="text-4xl font-bold">${report.vulnerabilities.filter(v => v.category === 'Probable').length}</p></div>
76
+ <div id="dashboard" class="content-area active">
77
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-10">
78
+ <div class="card p-6 rounded-xl shadow-2xl">
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 || 0}/100</p>
81
+ </div>
82
+ <div class="card p-6 rounded-xl shadow-2xl">
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 || 'N/A'}</p>
85
+ </div>
86
+ <div class="card p-6 rounded-xl shadow-2xl">
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>
89
+ </div>
90
+ <div class="card p-6 rounded-xl shadow-2xl">
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>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
97
+ <div class="card p-6 rounded-xl">
98
+ <h2 class="text-xl font-bold mb-6 flex items-center">
99
+ <span class="w-2 h-2 bg-red-500 rounded-full mr-2"></span> Top Critical Findings
100
+ </h2>
101
+ <div class="space-y-4">
102
+ ${(report.vulnerabilities || []).filter(v => v.severity === 'critical' || v.severity === 'high').slice(0, 5).map(v => `
103
+ <div class="p-4 bg-[#1c1c22] rounded-lg border border-gray-800">
104
+ <div class="flex justify-between items-start mb-2">
105
+ <span class="font-bold ${getSeverityClass(v.severity)} text-sm uppercase">${v.severity}</span>
106
+ <span class="px-2 py-0.5 rounded border text-[10px] uppercase font-bold ${getCategoryBadge(v.category)}">${v.category}</span>
107
+ </div>
108
+ <p class="text-sm font-medium text-gray-300">${v.title || v.description}</p>
109
+ </div>
110
+ `).join('') || '<p class="text-gray-500 italic">No critical findings discovered.</p>'}
111
+ </div>
112
+ </div>
113
+ <div class="card p-6 rounded-xl">
114
+ <h2 class="text-xl font-bold mb-6 flex items-center">
115
+ <span class="w-2 h-2 bg-blue-500 rounded-full mr-2"></span> Scan Intelligence
116
+ </h2>
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 || '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
+ </div>
123
+ </div>
72
124
  </div>
73
125
  </div>
74
126
 
75
127
  <!-- Findings Tab -->
76
- <div id="findings" class="tab-content hidden">
77
- <div class="card p-4 rounded-lg">
78
- <h2 class="text-xl font-bold mb-4">Vulnerability Details</h2>
79
- <div class="space-y-3">
80
- ${report.vulnerabilities.map((v, i) => `
81
- <div>
82
- <div class="collapsible flex justify-between items-center p-3 bg-gray-800 rounded-t-lg" onclick="toggleCollapse(${i})">
83
- <span class="font-bold ${getSeverityClass(v.severity)}">${v.severity}</span>
84
- <span class="flex-1 mx-4">${v.description}</span>
85
- <span class="${getConfidenceClass(v.confidence)}">${v.confidence} Confidence</span>
128
+ <div id="findings" class="content-area">
129
+ <div class="card rounded-xl overflow-hidden">
130
+ <div class="p-6 border-b border-gray-800 flex justify-between items-center bg-[#111116]">
131
+ <h2 class="text-xl font-bold">Vulnerability Evidence Catalog</h2>
132
+ <div class="flex gap-2">
133
+ <span class="text-xs font-bold text-gray-500 uppercase tracking-tighter">Click rows to expand evidence</span>
134
+ </div>
135
+ </div>
136
+ <div class="divide-y divide-gray-800">
137
+ ${(report.vulnerabilities || []).map((v, i) => `
138
+ <div class="finding-row">
139
+ <div class="p-4 flex items-center cursor-pointer gap-4" onclick="toggleEvidence(${i})">
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.title || v.description}</div>
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.kind || 'N/A'}</div>
86
144
  </div>
87
- <div id="content-${i}" class="content p-4 bg-gray-900 rounded-b-lg">
88
- <h4 class="font-bold mb-2">Evidence</h4>
89
- <pre>${JSON.stringify(v.evidence, null, 2)}</pre>
145
+ <div id="evidence-${i}" class="evidence-panel p-6 bg-black border-t border-gray-800">
146
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
147
+ <div>
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>
150
+ </div>
151
+ <div>
152
+ <h4 class="text-xs font-bold uppercase text-gray-500 mb-3 tracking-widest">Technical Details</h4>
153
+ <div class="space-y-3 text-sm">
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>
161
+ </div>
162
+ </div>
163
+ </div>
90
164
  </div>
91
165
  </div>
92
166
  `).join('')}
@@ -95,36 +169,70 @@ export async function startUIServer() {
95
169
  </div>
96
170
 
97
171
  <!-- Attack Surface Tab -->
98
- <div id="surface" class="tab-content hidden">
99
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
100
- <div class="card p-4 rounded-lg"> <h3 class="font-bold mb-2">Tech Stack</h3> <pre>${Array.isArray(report.attack_surface.tech_stack) ? report.attack_surface.tech_stack.join('\n') : (report.attack_surface.tech_stack || '')}</pre> </div>
101
- <div class="card p-4 rounded-lg"> <h3 class="font-bold mb-2">Forms</h3> <pre>${JSON.stringify(report.attack_surface.forms || report.attack_surface.forms_detected || [], null, 2)}</pre> </div>
102
- <div class="card p-4 rounded-lg col-span-1 md:col-span-2"> <h3 class="font-bold mb-2">Discovered Links</h3> <pre class="max-h-96">${Array.isArray(report.attack_surface.endpoints) ? report.attack_surface.endpoints.join('\n') : (report.attack_surface.endpoints || report.attack_surface.endpoints_discovered || '')}</pre> </div>
172
+ <div id="surface" class="content-area">
173
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
174
+ <div class="lg:col-span-2 space-y-8">
175
+ <div class="card p-6 rounded-xl">
176
+ <h3 class="text-xl font-bold mb-4">Discovered Assets</h3>
177
+ <pre class="mono max-h-[600px]">${(report.attack_surface?.endpoints || []).join('\n') || 'No endpoints discovered.'}</pre>
178
+ </div>
179
+ </div>
180
+ <div class="space-y-8">
181
+ <div class="card p-6 rounded-xl">
182
+ <h3 class="text-xl font-bold mb-4">Forms & Inputs</h3>
183
+ <pre class="mono">${JSON.stringify(report.attack_surface?.forms || [], null, 2)}</pre>
184
+ </div>
185
+ <div class="card p-6 rounded-xl">
186
+ <h3 class="text-xl font-bold mb-4">Tech Fingerprint</h3>
187
+ <div class="flex flex-wrap gap-2">
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>'}
189
+ </div>
190
+ </div>
191
+ </div>
103
192
  </div>
104
193
  </div>
105
194
 
106
195
  <!-- History Tab -->
107
- <div id="history" class="tab-content hidden">
108
- <div class="card p-4 rounded-lg">
109
- <h2 class="text-xl font-bold mb-4">Scan History</h2>
110
- <p class="text-gray-400">Feature coming soon. Historical data is being saved in the .omen/history/ directory.</p>
196
+ <div id="history" class="content-area text-center py-20">
197
+ <div class="card p-10 rounded-2xl max-w-lg mx-auto border-dashed border-gray-700 bg-transparent">
198
+ <h2 class="text-2xl font-bold mb-4 text-gray-400">Scan Timeline</h2>
199
+ <p class="text-gray-500 mb-6">OMEN is currently tracking your security posture. Historical data is being indexed in <code class="mono text-red-400 bg-red-950/30 px-2 py-1 rounded">.omen/history/</code></p>
200
+ <div class="inline-block px-4 py-2 bg-gray-800 rounded-full text-xs font-bold uppercase tracking-widest text-gray-400">Feature arriving in v1.0.17</div>
111
201
  </div>
112
202
  </div>
113
203
 
114
- <footer class="text-center text-gray-600 mt-12 border-t border-gray-800 pt-4">
115
- <p>OMEN Security Framework - v1.0.14</p>
204
+ <footer class="text-center text-gray-600 mt-16 border-t border-gray-900 pt-8 mb-10">
205
+ <p class="text-xs uppercase tracking-widest font-bold mb-2">OMEN Security Framework - v1.0.17</p>
206
+ <p class="text-[10px] text-gray-700 italic">"The eye that never sleeps, the code that never fails."</p>
116
207
  </footer>
117
208
  </div>
209
+
118
210
  <script>
119
- function showTab(tabName) {
120
- document.querySelectorAll('.tab-content').forEach(tab => tab.classList.add('hidden'));
121
- document.getElementById(tabName).classList.remove('hidden');
122
- document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
123
- document.querySelector(`[onclick="showTab('${tabName}')"]`).classList.add('active');
124
- }
125
- function toggleCollapse(index) {
126
- const content = document.getElementById('content-' + index);
127
- content.style.display = content.style.display === 'block' ? 'none' : 'block';
211
+ // Robust Tab System
212
+ document.querySelectorAll('.tab-btn').forEach(btn => {
213
+ btn.addEventListener('click', () => {
214
+ const target = btn.getAttribute('data-target');
215
+
216
+ // Update buttons
217
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
218
+ btn.classList.add('active');
219
+
220
+ // Update areas
221
+ document.querySelectorAll('.content-area').forEach(area => {
222
+ area.classList.remove('active');
223
+ if (area.id === target) area.classList.add('active');
224
+ });
225
+ });
226
+ });
227
+
228
+ // Robust Evidence Toggle
229
+ function toggleEvidence(index) {
230
+ const panel = document.getElementById('evidence-' + index);
231
+ if (panel.style.display === 'block') {
232
+ panel.style.display = 'none';
233
+ } else {
234
+ panel.style.display = 'block';
235
+ }
128
236
  }
129
237
  </script>
130
238
  </body>
@@ -132,7 +240,13 @@ export async function startUIServer() {
132
240
  `;
133
241
  res.send(html);
134
242
  } catch (err) {
135
- res.status(500).send(`<h1>Error loading report</h1><p>Please run a scan first to generate omen-report.json. Error: ${err.message}</p>`);
243
+ res.status(500).send(`
244
+ <body style="background:#0a0a0c; color:#ff4d4d; font-family:sans-serif; padding:50px; text-align:center;">
245
+ <h1>Dashboard Failed to Load</h1>
246
+ <p style="color:#888;">Error details: ${err.message}</p>
247
+ <p style="margin-top:20px;"><a href="/" style="color:#fff; text-decoration:none; border:1px solid #333; padding:10px 20px; rounded:5px;">Retry</a></p>
248
+ </body>
249
+ `);
136
250
  }
137
251
  });
138
252
 
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "omen-sec-cli",
3
- "version": "1.0.15",
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.14 \n'));
12
+ console.log(chalk.gray(' Version: 1.0.17 \n'));
13
13
  }
14
14
 
15
15
  export function showHelp() {