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 +1 -1
- package/core/generator.js +27 -1
- package/core/local-scanner.js +64 -38
- package/core/remote-scanner.js +107 -61
- package/core/scanner.js +34 -24
- package/core/ui-server.js +175 -61
- package/package.json +4 -1
- package/ui/banner.js +1 -1
package/bin/index.js
CHANGED
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
|
-
|
|
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
|
|
package/core/local-scanner.js
CHANGED
|
@@ -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
|
-
|
|
49
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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:
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
package/core/remote-scanner.js
CHANGED
|
@@ -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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
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 !== '
|
|
70
|
+
if (v.category !== 'confirmed' && v.category !== 'probable') return acc;
|
|
56
71
|
|
|
57
72
|
let severityWeight = 0;
|
|
58
|
-
if (v.severity === '
|
|
59
|
-
else if (v.severity === '
|
|
60
|
-
else if (v.severity === '
|
|
61
|
-
else if (v.severity === '
|
|
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 === '
|
|
65
|
-
if (v.confidence === '
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
24
|
-
if (
|
|
25
|
-
if (
|
|
26
|
-
return 'text-
|
|
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
|
|
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:
|
|
41
|
-
.tab
|
|
42
|
-
.
|
|
43
|
-
.content { display: none; }
|
|
44
|
-
|
|
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-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
<p class="text-gray-
|
|
53
|
-
|
|
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-
|
|
59
|
-
<div class="tab active"
|
|
60
|
-
<div class="tab"
|
|
61
|
-
<div class="tab"
|
|
62
|
-
<div class="tab"
|
|
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="
|
|
67
|
-
<div class="grid grid-cols-
|
|
68
|
-
<div class="card p-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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="
|
|
77
|
-
<div class="card
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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="
|
|
88
|
-
<
|
|
89
|
-
|
|
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="
|
|
99
|
-
<div class="grid grid-cols-1
|
|
100
|
-
<div class="
|
|
101
|
-
|
|
102
|
-
|
|
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="
|
|
108
|
-
<div class="card p-
|
|
109
|
-
<h2 class="text-
|
|
110
|
-
<p class="text-gray-
|
|
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-
|
|
115
|
-
<p>OMEN Security Framework - v1.0.
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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(
|
|
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
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.
|
|
12
|
+
console.log(chalk.gray(' Version: 1.0.17 \n'));
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function showHelp() {
|