omen-sec-cli 1.0.12 → 1.0.15

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.6');
18
+ console.log('1.0.14');
19
19
  return;
20
20
  }
21
21
 
@@ -1,5 +1,26 @@
1
1
  export function getMassiveAIProtocol(scanData) {
2
- return `
2
+ const summary = `
3
+ ================================================================================
4
+ OMEN SEC-CLI: TARGET INTELLIGENCE SUMMARY
5
+ ================================================================================
6
+ Target: ${scanData.target}
7
+ Scan Date: ${new Date().toISOString()}
8
+ Risk Level: ${scanData.riskLevel} (${scanData.score}/100)
9
+
10
+ --- DETECTED SURFACE ---
11
+ Endpoints Discovered: ${scanData.attack_surface.endpoints.length}
12
+ Forms Found: ${scanData.attack_surface.forms.length}
13
+ Tech Stack: ${(scanData.attack_surface.tech_stack || []).join(', ')}
14
+
15
+ --- TOP VULNERABILITIES ---
16
+ ${scanData.vulnerabilities.slice(0, 5).map(v => `[${v.severity}] ${v.description} (Category: ${v.category})`).join('\n')}
17
+
18
+ ================================================================================
19
+ FULL SCAN DATA ATTACHED BELOW
20
+ ================================================================================
21
+ `;
22
+
23
+ return summary + `
3
24
  ================================================================================
4
25
  [OMEN_AI_PROTOCOL_V2_MAXIMUM_OVERRIDE]
5
26
  ================================================================================
@@ -0,0 +1,35 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export async function compareWithPreviousScan() {
5
+ const historyDir = path.join(process.cwd(), '.omen', 'history');
6
+ try {
7
+ const files = await fs.readdir(historyDir);
8
+ if (files.length < 2) return null;
9
+
10
+ // Sort files by modification time to get the last two
11
+ const sortedFiles = files.map(async file => {
12
+ const stats = await fs.stat(path.join(historyDir, file));
13
+ return { name: file, mtime: stats.mtime };
14
+ });
15
+ const promisedFiles = await Promise.all(sortedFiles);
16
+ promisedFiles.sort((a, b) => b.mtime - a.mtime);
17
+
18
+ const [currentFile, previousFile] = promisedFiles;
19
+
20
+ const currentData = JSON.parse(await fs.readFile(path.join(historyDir, currentFile.name), 'utf-8'));
21
+ const previousData = JSON.parse(await fs.readFile(path.join(historyDir, previousFile.name), 'utf-8'));
22
+
23
+ const currentVulnIds = new Set(currentData.vulnerabilities.map(v => v.description)); // Use description for simple diff
24
+ const previousVulnIds = new Set(previousData.vulnerabilities.map(v => v.description));
25
+
26
+ const newVulnerabilities = currentData.vulnerabilities.filter(v => !previousVulnIds.has(v.description));
27
+ const fixedVulnerabilities = previousData.vulnerabilities.filter(v => !currentVulnIds.has(v.description));
28
+ const scoreDiff = currentData.score - previousData.score;
29
+
30
+ return { newVulnerabilities, fixedVulnerabilities, scoreDiff };
31
+
32
+ } catch (err) {
33
+ return null;
34
+ }
35
+ }
package/core/engine.js CHANGED
@@ -3,6 +3,7 @@ import { runScannerSteps } from './scanner.js';
3
3
  import { generateOutputs } from './generator.js';
4
4
  import { showCommunitySection } from '../ui/banner.js';
5
5
  import { getMassiveAIProtocol } from './ai-protocol.js';
6
+ import { compareWithPreviousScan } from './diff-engine.js';
6
7
 
7
8
  export async function runScan(args) {
8
9
  const target = args.flags.local ? 'Local Project' : args.target;
@@ -20,7 +21,23 @@ export async function runScan(args) {
20
21
 
21
22
  await generateOutputs(scanData);
22
23
 
23
- console.log(chalk.green(`\n[✔] Scan completed successfully!`));
24
+ // Compare with previous scan
25
+ const diff = await compareWithPreviousScan();
26
+ if (diff) {
27
+ console.log(chalk.bold('\n--- Scan Comparison ---'));
28
+ if (diff.newVulnerabilities.length > 0) {
29
+ console.log(chalk.red(`[+] ${diff.newVulnerabilities.length} New Vulnerabilities Found:`));
30
+ diff.newVulnerabilities.forEach(v => console.log(` - ${v.description}`));
31
+ }
32
+ if (diff.fixedVulnerabilities.length > 0) {
33
+ console.log(chalk.green(`[✔] ${diff.fixedVulnerabilities.length} Vulnerabilities Fixed:`));
34
+ diff.fixedVulnerabilities.forEach(v => console.log(` - ${v.description}`));
35
+ }
36
+ console.log(chalk.yellow(`Score Change: ${diff.scoreDiff > 0 ? '+' : ''}${diff.scoreDiff} points`));
37
+ console.log(chalk.gray('-----------------------\n'));
38
+ }
39
+
40
+ console.log(chalk.green(`[✔] Scan completed successfully!`));
24
41
  console.log(chalk.white(` Report JSON: ./omen-reports/omen-report.json`));
25
42
  console.log(chalk.white(` AI Prompt: ./omen-reports/omen-ai.txt\n`));
26
43
 
package/core/generator.js CHANGED
@@ -6,18 +6,22 @@ import { getMassiveAIProtocol } from './ai-protocol.js';
6
6
  export async function generateOutputs(scanData) {
7
7
  const cwd = process.cwd();
8
8
  const outputDir = path.join(cwd, 'omen-reports');
9
+ const historyDir = path.join(cwd, '.omen', 'history');
9
10
 
10
- // Criar a pasta se não existir
11
+ // Criar as pastas se não existirem
11
12
  try {
12
13
  await fs.mkdir(outputDir, { recursive: true });
14
+ await fs.mkdir(historyDir, { recursive: true });
13
15
  } catch (err) {
14
- console.error(chalk.red(`Failed to create output directory: ${err.message}`));
16
+ console.error(chalk.red(`Failed to create output directories: ${err.message}`));
15
17
  return;
16
18
  }
19
+
20
+ const jsonContent = JSON.stringify(scanData, null, 2);
17
21
 
18
22
  // JSON Report
19
23
  const jsonReportPath = path.join(outputDir, 'omen-report.json');
20
- await fs.writeFile(jsonReportPath, JSON.stringify(scanData, null, 2));
24
+ await fs.writeFile(jsonReportPath, jsonContent);
21
25
  console.log(` /omen-reports/omen-report.json`);
22
26
 
23
27
  // TXT Report
@@ -30,4 +34,9 @@ export async function generateOutputs(scanData) {
30
34
  const aiReportPath = path.join(outputDir, 'omen-ai.txt');
31
35
  await fs.writeFile(aiReportPath, getMassiveAIProtocol(scanData));
32
36
  console.log(` /omen-reports/omen-ai.txt`);
37
+
38
+ // Save historical report
39
+ const timestamp = new Date().toISOString().replace(/:/g, '-');
40
+ const historyReportPath = path.join(historyDir, `omen-report-${timestamp}.json`);
41
+ await fs.writeFile(historyReportPath, jsonContent);
33
42
  }
@@ -61,19 +61,23 @@ export async function scanLocalProject() {
61
61
  if (deps['lodash'] && deps['lodash'].match(/[~^]?4\.17\.[0-20]/)) {
62
62
  vulnerabilities.push({
63
63
  id: `LOC-VULN-${Date.now()}-1`,
64
- type: 'Vulnerable Components',
64
+ category: 'Confirmed',
65
+ confidence: 'High',
65
66
  severity: 'High',
66
- description: `Outdated dependency detected in package.json: lodash (${deps['lodash']}). Prototype Pollution risk.`,
67
- cwe: 'CWE-1321'
67
+ description: `Outdated dependency detected in package.json: lodash (${deps['lodash']}). Known Prototype Pollution risk.`,
68
+ cwe: 'CWE-1321',
69
+ evidence: { file: 'package.json', finding: `lodash: ${deps['lodash']}` }
68
70
  });
69
71
  }
70
72
  if (deps['express'] && deps['express'].match(/[~^]?3\./)) {
71
73
  vulnerabilities.push({
72
74
  id: `LOC-VULN-${Date.now()}-2`,
73
- type: 'Vulnerable Components',
75
+ category: 'Confirmed',
76
+ confidence: 'High',
74
77
  severity: 'High',
75
78
  description: `Severely outdated Express.js version (${deps['express']}) detected. Multiple CVEs exist.`,
76
- cwe: 'CWE-1104'
79
+ cwe: 'CWE-1104',
80
+ evidence: { file: 'package.json', finding: `express: ${deps['express']}` }
77
81
  });
78
82
  }
79
83
  } catch (err) {
@@ -98,10 +102,12 @@ export async function scanLocalProject() {
98
102
  if (/(api_key|apikey|secret|password|token)\s*=\s*['"][a-zA-Z0-9_-]{10,}['"]/i.test(line)) {
99
103
  vulnerabilities.push({
100
104
  id: `LOC-VULN-${Date.now()}-3`,
101
- type: 'Sensitive Data Exposure',
105
+ category: 'Confirmed',
106
+ confidence: 'Medium', // Could be a test key
102
107
  severity: 'Critical',
103
108
  description: `Potential hardcoded secret found in ${path.basename(file)} at line ${index + 1}`,
104
- cwe: 'CWE-798'
109
+ cwe: 'CWE-798',
110
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
105
111
  });
106
112
  }
107
113
 
@@ -109,10 +115,12 @@ export async function scanLocalProject() {
109
115
  if (/eval\s*\(/.test(line)) {
110
116
  vulnerabilities.push({
111
117
  id: `LOC-VULN-${Date.now()}-4`,
112
- type: 'Code Injection',
118
+ category: 'Confirmed',
119
+ confidence: 'High',
113
120
  severity: 'Critical',
114
121
  description: `Dangerous use of eval() detected in ${path.basename(file)} at line ${index + 1}`,
115
- cwe: 'CWE-94'
122
+ cwe: 'CWE-94',
123
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
116
124
  });
117
125
  }
118
126
 
@@ -120,10 +128,12 @@ export async function scanLocalProject() {
120
128
  if (/SELECT.*FROM.*WHERE.*(\+|`|\${)/i.test(line)) {
121
129
  vulnerabilities.push({
122
130
  id: `LOC-VULN-${Date.now()}-5`,
123
- type: 'SQL Injection',
131
+ category: 'Probable',
132
+ confidence: 'Medium',
124
133
  severity: 'Critical',
125
- description: `Critical SQL Injection vulnerability (raw string concatenation) in ${path.basename(file)} at line ${index + 1}. Attacker can bypass authentication or dump the entire database.`,
126
- cwe: 'CWE-89'
134
+ description: `Potential SQL Injection (raw string concatenation) in ${path.basename(file)} at line ${index + 1}.`,
135
+ cwe: 'CWE-89',
136
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
127
137
  });
128
138
  }
129
139
 
@@ -131,10 +141,12 @@ export async function scanLocalProject() {
131
141
  if (/(require|import)\s*\(['"]?.*(\+|`|\${)/i.test(line)) {
132
142
  vulnerabilities.push({
133
143
  id: `LOC-VULN-${Date.now()}-6`,
134
- type: 'Local File Inclusion',
144
+ category: 'Probable',
145
+ confidence: 'Medium',
135
146
  severity: 'High',
136
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.`,
137
- cwe: 'CWE-22'
148
+ cwe: 'CWE-22',
149
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
138
150
  });
139
151
  }
140
152
 
@@ -142,10 +154,12 @@ export async function scanLocalProject() {
142
154
  if (/\.prototype\.[a-zA-Z0-9_]+\s*=\s*/.test(line)) {
143
155
  vulnerabilities.push({
144
156
  id: `LOC-VULN-${Date.now()}-7`,
145
- type: 'Prototype Pollution Risk',
157
+ category: 'Hardening',
158
+ confidence: 'Low',
146
159
  severity: 'Medium',
147
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.`,
148
- cwe: 'CWE-1321'
161
+ cwe: 'CWE-1321',
162
+ evidence: { file: path.basename(file), line: index + 1, code: line.trim() }
149
163
  });
150
164
  }
151
165
 
@@ -174,6 +188,7 @@ export async function scanLocalProject() {
174
188
 
175
189
  return {
176
190
  localFilesScanned: filesScanned.length,
191
+ filesScanned,
177
192
  vulnerabilities
178
193
  };
179
194
  }
@@ -36,10 +36,16 @@ 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
- type: 'Security Misconfiguration',
39
+ category: 'Hardening',
40
+ confidence: 'High',
40
41
  severity: 'Medium',
41
42
  description: `HSTS Header is missing. This lacks forced HTTPS enforcement for browsers that have already visited the site.`,
42
- cwe: 'CWE-319'
43
+ cwe: 'CWE-319',
44
+ evidence: {
45
+ request: { headers: { ...response.request.headers } },
46
+ response: { status: response.status, headers: response.headers },
47
+ reason: 'Security header "Strict-Transport-Security" not found in server response.'
48
+ }
43
49
  });
44
50
  } else {
45
51
  headers_analysis["Strict-Transport-Security"] = headers['strict-transport-security'];
@@ -49,20 +55,28 @@ export async function scanRemoteTarget(targetUrl) {
49
55
  headers_analysis["Content-Security-Policy"] = "Missing";
50
56
  vulnerabilities.push({
51
57
  id: `REM-VULN-${Date.now()}-2`,
52
- type: 'Security Misconfiguration',
58
+ category: 'Confirmed',
59
+ confidence: 'High',
53
60
  severity: 'High',
54
61
  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.`,
55
- cwe: 'CWE-1022'
62
+ cwe: 'CWE-1022',
63
+ evidence: {
64
+ request: { headers: { ...response.request.headers } },
65
+ response: { status: response.status, headers: response.headers },
66
+ reason: 'Security header "Content-Security-Policy" not found in server response.'
67
+ }
56
68
  });
57
69
  } else {
58
70
  headers_analysis["Content-Security-Policy"] = headers['content-security-policy'];
59
71
  if (headers['content-security-policy'].includes("unsafe-inline")) {
60
72
  vulnerabilities.push({
61
73
  id: `REM-VULN-${Date.now()}-3`,
62
- type: 'Security Misconfiguration',
74
+ category: 'Confirmed',
75
+ confidence: 'High',
63
76
  severity: 'High',
64
77
  description: `Weak CSP detected: 'unsafe-inline' is allowed.`,
65
- cwe: 'CWE-16'
78
+ cwe: 'CWE-16',
79
+ evidence: { finding: `policy: ${headers['content-security-policy']}` }
66
80
  });
67
81
  }
68
82
  }
@@ -72,10 +86,16 @@ export async function scanRemoteTarget(targetUrl) {
72
86
  headers_analysis["X-Frame-Options"] = "Missing";
73
87
  vulnerabilities.push({
74
88
  id: `REM-VULN-${Date.now()}-4`,
75
- type: 'Security Misconfiguration',
89
+ category: 'Hardening',
90
+ confidence: 'High',
76
91
  severity: 'Low',
77
- description: `Missing X-Frame-Options. Vulnerable to Clickjacking.`,
78
- cwe: 'CWE-1021'
92
+ description: `Missing X-Frame-Options. Increases risk of Clickjacking.`,
93
+ cwe: 'CWE-1021',
94
+ evidence: {
95
+ request: { headers: { ...response.request.headers } },
96
+ response: { status: response.status, headers: response.headers },
97
+ reason: 'Security header "X-Frame-Options" not found. This allows the site to be embedded in iframes on third-party domains.'
98
+ }
79
99
  });
80
100
  } else {
81
101
  headers_analysis["X-Frame-Options"] = headers['x-frame-options'];
@@ -86,10 +106,12 @@ export async function scanRemoteTarget(targetUrl) {
86
106
  headers_analysis["Server"] = headers['server'];
87
107
  vulnerabilities.push({
88
108
  id: `REM-VULN-${Date.now()}-5`,
89
- type: 'Information Exposure',
109
+ category: 'Informational',
110
+ confidence: 'High',
90
111
  severity: 'Low',
91
112
  description: `Server header leaks technology stack: ${headers['server']}`,
92
- cwe: 'CWE-200'
113
+ cwe: 'CWE-200',
114
+ evidence: { finding: `Server: ${headers['server']}` }
93
115
  });
94
116
  } else {
95
117
  headers_analysis["Server"] = "Hidden (Good)";
@@ -99,10 +121,12 @@ export async function scanRemoteTarget(targetUrl) {
99
121
  if (headers['x-powered-by']) {
100
122
  vulnerabilities.push({
101
123
  id: `REM-VULN-${Date.now()}-6`,
102
- type: 'Information Exposure',
124
+ category: 'Informational',
125
+ confidence: 'High',
103
126
  severity: 'Low',
104
127
  description: `X-Powered-By header leaks framework: ${headers['x-powered-by']}`,
105
- cwe: 'CWE-200'
128
+ cwe: 'CWE-200',
129
+ evidence: { finding: `X-Powered-By: ${headers['x-powered-by']}` }
106
130
  });
107
131
  }
108
132
 
@@ -110,7 +134,7 @@ export async function scanRemoteTarget(targetUrl) {
110
134
  if (typeof html === 'string') {
111
135
  const $ = cheerio.load(html);
112
136
 
113
- // Discover Links & Params
137
+ // Discover Links, Params, and JS files
114
138
  $('a').each((i, link) => {
115
139
  const href = $(link).attr('href');
116
140
  if (href && !href.startsWith('#') && !href.startsWith('mailto:')) {
@@ -119,7 +143,6 @@ export async function scanRemoteTarget(targetUrl) {
119
143
  if (absoluteUrl.startsWith(targetUrl)) {
120
144
  discoveredLinks.add(absoluteUrl);
121
145
 
122
- // Extract query parameters
123
146
  const urlObj = new URL(absoluteUrl);
124
147
  urlObj.searchParams.forEach((value, name) => discoveredParams.add(name));
125
148
  }
@@ -127,6 +150,18 @@ export async function scanRemoteTarget(targetUrl) {
127
150
  }
128
151
  });
129
152
 
153
+ $('script[src]').each((i, script) => {
154
+ const src = $(script).attr('src');
155
+ if (src) {
156
+ try {
157
+ const absoluteUrl = new URL(src, targetUrl).href;
158
+ if (absoluteUrl.startsWith(targetUrl)) {
159
+ discoveredLinks.add(absoluteUrl);
160
+ }
161
+ } catch (e) {}
162
+ }
163
+ });
164
+
130
165
  // Discover Forms
131
166
  $('form').each((i, form) => {
132
167
  const action = $(form).attr('action') || '';
@@ -140,6 +175,46 @@ export async function scanRemoteTarget(targetUrl) {
140
175
  });
141
176
  }
142
177
 
178
+ // --- DEEP CRAWL: robots.txt, sitemap.xml, and JS files ---
179
+ const robotsUrl = new URL('/robots.txt', targetUrl).href;
180
+ try {
181
+ const robotsRes = await axios.get(robotsUrl, { timeout: 5000 });
182
+ const robotsLines = robotsRes.data.split('\n');
183
+ robotsLines.forEach(line => {
184
+ if (line.toLowerCase().startsWith('allow:') || line.toLowerCase().startsWith('disallow:')) {
185
+ const path = line.split(':')[1].trim();
186
+ if (path !== '/') discoveredLinks.add(new URL(path, targetUrl).href);
187
+ }
188
+ if (line.toLowerCase().startsWith('sitemap:')) {
189
+ discoveredLinks.add(line.split(':')[1].trim());
190
+ }
191
+ });
192
+ } catch (e) {}
193
+
194
+ // Process sitemaps and JS files found
195
+ const extraLinks = new Set();
196
+ for (const link of discoveredLinks) {
197
+ if (link.endsWith('.xml')) {
198
+ try {
199
+ const sitemapRes = await axios.get(link, { timeout: 5000 });
200
+ const $sitemap = cheerio.load(sitemapRes.data, { xmlMode: true });
201
+ $sitemap('loc').each((i, loc) => extraLinks.add($sitemap(loc).text()));
202
+ } catch (e) {}
203
+ } else if (link.endsWith('.js')) {
204
+ try {
205
+ const jsRes = await axios.get(link, { timeout: 10000 });
206
+ const jsContent = jsRes.data;
207
+ const paths = jsContent.match(/(['"])\/[a-zA-Z0-9_\-\/]+(\?.*)?\1/g) || [];
208
+ paths.forEach(path => {
209
+ const cleanPath = path.replace(/['"]/g, '');
210
+ extraLinks.add(new URL(cleanPath, targetUrl).href);
211
+ });
212
+ } catch (e) {}
213
+ }
214
+ }
215
+ extraLinks.forEach(link => discoveredLinks.add(link));
216
+
217
+
143
218
  // --- FUZZER (Path Discovery - Aggressive) ---
144
219
  const aggressivePaths = [
145
220
  '/.env', '/.git/config', '/admin', '/wp-admin', '/config.php', '/.vscode/settings.json',
@@ -239,4 +314,85 @@ export async function scanRemoteTarget(targetUrl) {
239
314
  discoveredParams: Array.from(discoveredParams),
240
315
  vulnerabilities
241
316
  };
317
+ }
318
+
319
+ async function validateFuzzerFinding(path, response, url) {
320
+ const { status, data } = response;
321
+ const evidence = {
322
+ request: { url, method: 'GET' },
323
+ response: { status, headers: response.headers, body_snippet: typeof data === 'string' ? data.substring(0, 250) : '' }
324
+ };
325
+
326
+ // Rule 1: Detect soft 404s on 200 OK responses
327
+ if (status === 200 && typeof data === 'string' && /(page not found|could not be found|404)/i.test(data)) {
328
+ return null; // Ignore soft 404s as they are not interesting paths
329
+ }
330
+
331
+ // Rule 2: Handle truly sensitive file exposures
332
+ const isSensitiveFile = /\.env|\.git|\.ssh|config|credentials|password/i.test(path);
333
+ if (isSensitiveFile && status === 200) {
334
+ // If content is not HTML, it's likely the raw file
335
+ if (typeof data === 'string' && !data.trim().startsWith('<html')) {
336
+ return {
337
+ id: `REM-CONFIRMED-FILE-${Date.now()}`,
338
+ category: 'Confirmed',
339
+ confidence: 'High',
340
+ severity: 'Critical',
341
+ description: `CRITICAL: Sensitive file exposed at ${url}. Contents contain raw configuration data.`,
342
+ cwe: 'CWE-538',
343
+ evidence: { ...evidence, reason: 'Raw sensitive file content detected (Non-HTML response on sensitive path)' }
344
+ };
345
+ } else {
346
+ return {
347
+ id: `REM-POTENTIAL-FILE-${Date.now()}`,
348
+ category: 'Informational',
349
+ confidence: 'Low',
350
+ severity: 'Info',
351
+ description: `Potential sensitive path found at ${url}, but returned HTML content. Likely a redirect or custom error page.`,
352
+ cwe: 'CWE-200',
353
+ evidence: { ...evidence, reason: 'HTML response on sensitive file path' }
354
+ };
355
+ }
356
+ }
357
+
358
+ // Rule 3: Analyze admin panel paths
359
+ const isAdminPath = /admin|dashboard|login/i.test(path);
360
+ if (isAdminPath && status === 200) {
361
+ if (typeof data === 'string' && /password|login/i.test(data)) {
362
+ return {
363
+ 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.`,
368
+ cwe: 'CWE-200',
369
+ evidence
370
+ };
371
+ }
372
+ // If it's a 200 but not a login page, it could be an exposed panel
373
+ return {
374
+ 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
381
+ };
382
+ }
383
+
384
+ // Rule 4: Treat 403 Forbidden as low-confidence informational
385
+ if (status === 403) {
386
+ return {
387
+ 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
394
+ };
395
+ }
396
+
397
+ return null; // By default, not a significant finding
242
398
  }
package/core/scanner.js CHANGED
@@ -17,10 +17,10 @@ export async function runScannerSteps(target, flags) {
17
17
  let allVulnerabilities = [];
18
18
  let headers_analysis = {};
19
19
  let attack_surface = {
20
- endpoints_discovered: 0,
21
- parameters_extracted: 0,
22
- forms_detected: 0,
23
- api_routes: 0
20
+ endpoints: [],
21
+ parameters: [],
22
+ forms: [],
23
+ tech_stack: []
24
24
  };
25
25
 
26
26
  for (let i = 0; i < steps.length; i++) {
@@ -32,32 +32,42 @@ export async function runScannerSteps(target, flags) {
32
32
  const remoteData = await scanRemoteTarget(target);
33
33
  headers_analysis = remoteData.headers_analysis;
34
34
  allVulnerabilities.push(...remoteData.vulnerabilities);
35
- attack_surface.endpoints_discovered = remoteData.discoveredLinks.length;
36
- attack_surface.parameters_extracted = remoteData.discoveredParams.length;
37
- attack_surface.forms_detected = remoteData.discoveredForms.length;
35
+ attack_surface.endpoints = remoteData.discoveredLinks;
36
+ attack_surface.parameters = remoteData.discoveredParams;
37
+ attack_surface.forms = remoteData.discoveredForms;
38
38
  attack_surface.tech_stack = remoteData.techStack;
39
39
  }
40
40
 
41
41
  if (step.text === 'Scanning endpoints...' && flags.local) {
42
42
  const localData = await scanLocalProject();
43
43
  allVulnerabilities.push(...localData.vulnerabilities);
44
- attack_surface.endpoints_discovered += localData.localFilesScanned;
44
+ attack_surface.endpoints.push(...(localData.filesScanned || []));
45
45
  }
46
46
 
47
47
  await sleep(step.delay);
48
48
  spinner.succeed(`[${i + 1}/${steps.length}] ${step.text}`);
49
49
  }
50
50
 
51
- // Calculate dynamic score
51
+ // Calculate dynamic score based on confidence and severity
52
52
  const baseScore = 100;
53
53
  const penalties = allVulnerabilities.reduce((acc, v) => {
54
- if (v.severity === 'Critical') return acc + 25;
55
- if (v.severity === 'High') return acc + 15;
56
- if (v.severity === 'Medium') return acc + 10;
57
- return acc + 5;
54
+ // Only penalize for Confirmed or Probable issues
55
+ if (v.category !== 'Confirmed' && v.category !== 'Probable') return acc;
56
+
57
+ 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;
62
+
63
+ let confidenceMultiplier = 1;
64
+ if (v.confidence === 'Medium') confidenceMultiplier = 0.6;
65
+ if (v.confidence === 'Low') confidenceMultiplier = 0.3;
66
+
67
+ return acc + (severityWeight * confidenceMultiplier);
58
68
  }, 0);
59
69
 
60
- const finalScore = Math.max(0, baseScore - penalties);
70
+ const finalScore = Math.max(0, Math.round(baseScore - penalties));
61
71
  let riskLevel = 'Low';
62
72
  if (finalScore < 50) riskLevel = 'Critical';
63
73
  else if (finalScore < 70) riskLevel = 'High';
package/core/ui-server.js CHANGED
@@ -13,6 +13,19 @@ export async function startUIServer() {
13
13
  const data = await fs.readFile(reportPath, 'utf-8');
14
14
  const report = JSON.parse(data);
15
15
 
16
+ const getSeverityClass = (severity) => {
17
+ if (severity === 'Critical') return 'text-red-500';
18
+ if (severity === 'High') return 'text-orange-500';
19
+ if (severity === 'Medium') return 'text-yellow-500';
20
+ return 'text-green-500';
21
+ };
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';
27
+ };
28
+
16
29
  const html = `
17
30
  <!DOCTYPE html>
18
31
  <html lang="en">
@@ -22,76 +35,109 @@ export async function startUIServer() {
22
35
  <title>OMEN SEC-CLI Dashboard</title>
23
36
  <script src="https://cdn.tailwindcss.com"></script>
24
37
  <style>
25
- body { background-color: #0a0a0c; color: #e0e0e0; font-family: 'Courier New', Courier, monospace; }
26
- .card { background-color: #16161a; border: 1px solid #333; }
27
- .critical { color: #ff4d4d; }
28
- .high { color: #ff944d; }
29
- .medium { color: #ffd11a; }
30
- .low { color: #4dff88; }
38
+ body { background-color: #0a0a0c; color: #e0e0e0; font-family: 'Inter', sans-serif; }
39
+ .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; }
31
45
  </style>
32
46
  </head>
33
- <body class="p-8">
34
- <div class="max-w-6xl mx-auto">
47
+ <body class="p-4 md:p-8">
48
+ <div class="max-w-7xl mx-auto">
35
49
  <header class="flex justify-between items-center mb-8 border-b border-gray-800 pb-4">
36
- <h1 class="text-3xl font-bold tracking-tighter text-red-500">OMEN <span class="text-white">SEC-CLI</span></h1>
37
- <div class="text-right">
38
- <p class="text-gray-500">Scan ID: ${report.scan_id}</p>
39
- <p class="text-gray-500">${new Date(report.timestamp).toLocaleString()}</p>
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>
40
54
  </div>
41
55
  </header>
42
56
 
43
- <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
44
- <div class="card p-6 rounded-lg shadow-xl">
45
- <h3 class="text-gray-400 mb-2">Security Score</h3>
46
- <p class="text-5xl font-bold ${report.score < 50 ? 'critical' : report.score < 75 ? 'high' : 'low'}">${report.score}/100</p>
47
- </div>
48
- <div class="card p-6 rounded-lg shadow-xl">
49
- <h3 class="text-gray-400 mb-2">Risk Level</h3>
50
- <p class="text-5xl font-bold ${report.riskLevel === 'Critical' ? 'critical' : report.riskLevel === 'High' ? 'high' : 'low'}">${report.riskLevel}</p>
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>
63
+ </div>
64
+
65
+ <!-- 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>
51
72
  </div>
52
- <div class="card p-6 rounded-lg shadow-xl">
53
- <h3 class="text-gray-400 mb-2">Vulnerabilities</h3>
54
- <p class="text-5xl font-bold text-white">${report.vulnerabilities.length}</p>
73
+ </div>
74
+
75
+ <!-- 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>
86
+ </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>
90
+ </div>
91
+ </div>
92
+ `).join('')}
93
+ </div>
55
94
  </div>
56
- <div class="card p-6 rounded-lg shadow-xl">
57
- <h3 class="text-gray-400 mb-2">Attack Surface</h3>
58
- <p class="text-sm text-gray-400">Endpoints: ${report.attack_surface.endpoints_discovered}</p>
59
- <p class="text-sm text-gray-400">Forms: ${report.attack_surface.forms_detected}</p>
60
- <p class="text-sm text-gray-400">Tech: ${(report.attack_surface.tech_stack || []).join(', ')}</p>
95
+ </div>
96
+
97
+ <!-- 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>
61
103
  </div>
62
104
  </div>
63
105
 
64
- <div class="card p-6 rounded-lg shadow-xl mb-8">
65
- <h2 class="text-xl font-bold mb-4 border-b border-gray-800 pb-2">Detected Vulnerabilities</h2>
66
- <div class="space-y-4">
67
- ${report.vulnerabilities.map(v => `
68
- <div class="border-l-4 ${v.severity === 'Critical' ? 'border-red-600' : v.severity === 'High' ? 'border-orange-500' : 'border-yellow-400'} bg-black bg-opacity-30 p-4 rounded">
69
- <div class="flex justify-between">
70
- <h4 class="font-bold text-lg">${v.type}</h4>
71
- <span class="px-2 py-1 rounded text-xs font-bold ${v.severity === 'Critical' ? 'bg-red-900 text-red-100' : v.severity === 'High' ? 'bg-orange-900 text-orange-100' : 'bg-yellow-900 text-yellow-100'}">${v.severity}</span>
72
- </div>
73
- <p class="text-gray-300 mt-2">${v.description}</p>
74
- <p class="text-gray-500 text-sm mt-1">CWE: ${v.cwe} | ID: ${v.id}</p>
75
- </div>
76
- `).join('')}
106
+ <!-- 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>
77
111
  </div>
78
112
  </div>
79
113
 
80
- <footer class="text-center text-gray-600 mt-12">
81
- <p>OMEN Security Framework - v1.0.6</p>
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>
82
116
  </footer>
83
117
  </div>
118
+ <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';
128
+ }
129
+ </script>
84
130
  </body>
85
131
  </html>
86
132
  `;
87
133
  res.send(html);
88
134
  } catch (err) {
89
- res.status(500).send(`<h1>Error loading report</h1><p>Please run a scan first to generate omen-report.json</p>`);
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>`);
90
136
  }
91
137
  });
92
138
 
93
139
  app.listen(port, () => {
94
- console.log(chalk.cyan(`\n[OMEN UI] Dashboard is running at:`));
140
+ console.log(chalk.cyan(`\n[OMEN UI] Evidence Center is running at:`));
95
141
  console.log(chalk.bold.green(` http://localhost:${port}\n`));
96
142
  console.log(chalk.gray(`Press Ctrl+C to stop the server.`));
97
143
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omen-sec-cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.15",
4
4
  "description": "OMEN — AI Security Engine",
5
5
  "main": "bin/index.js",
6
6
  "type": "module",
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.6 \n'));
12
+ console.log(chalk.gray(' Version: 1.0.14 \n'));
13
13
  }
14
14
 
15
15
  export function showHelp() {