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 +1 -1
- package/core/generator.js +27 -1
- package/core/local-scanner.js +64 -38
- package/core/remote-scanner.js +98 -55
- package/core/scanner.js +34 -24
- package/core/ui-server.js +26 -22
- 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,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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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
|
@@ -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 === '
|
|
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 === '
|
|
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 === '
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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">
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
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() {
|