omen-sec-cli 1.0.14 → 1.0.16

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.16');
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
  ================================================================================
@@ -188,6 +188,7 @@ export async function scanLocalProject() {
188
188
 
189
189
  return {
190
190
  localFilesScanned: filesScanned.length,
191
+ filesScanned,
191
192
  vulnerabilities
192
193
  };
193
194
  }
@@ -41,7 +41,11 @@ export async function scanRemoteTarget(targetUrl) {
41
41
  severity: 'Medium',
42
42
  description: `HSTS Header is missing. This lacks forced HTTPS enforcement for browsers that have already visited the site.`,
43
43
  cwe: 'CWE-319',
44
- evidence: { request: { headers: { ...response.request.headers } }, response: { headers: response.headers } }
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
+ }
45
49
  });
46
50
  } else {
47
51
  headers_analysis["Strict-Transport-Security"] = headers['strict-transport-security'];
@@ -56,7 +60,11 @@ export async function scanRemoteTarget(targetUrl) {
56
60
  severity: 'High',
57
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.`,
58
62
  cwe: 'CWE-1022',
59
- evidence: { request: { headers: { ...response.request.headers } }, response: { headers: response.headers } }
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
+ }
60
68
  });
61
69
  } else {
62
70
  headers_analysis["Content-Security-Policy"] = headers['content-security-policy'];
@@ -83,7 +91,11 @@ export async function scanRemoteTarget(targetUrl) {
83
91
  severity: 'Low',
84
92
  description: `Missing X-Frame-Options. Increases risk of Clickjacking.`,
85
93
  cwe: 'CWE-1021',
86
- evidence: { request: { headers: { ...response.request.headers } }, response: { headers: response.headers } }
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
+ }
87
99
  });
88
100
  } else {
89
101
  headers_analysis["X-Frame-Options"] = headers['x-frame-options'];
@@ -326,9 +338,19 @@ async function validateFuzzerFinding(path, response, url) {
326
338
  category: 'Confirmed',
327
339
  confidence: 'High',
328
340
  severity: 'Critical',
329
- description: `CRITICAL: Sensitive file exposed at ${url}. Contents may contain credentials, private keys, or configuration secrets.`,
330
- cwe: 'CWE-538', // File and Directory Information Exposure
331
- evidence
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' }
332
354
  };
333
355
  }
334
356
  }
@@ -342,33 +364,36 @@ async function validateFuzzerFinding(path, response, url) {
342
364
  category: 'Informational',
343
365
  confidence: 'High',
344
366
  severity: 'Info',
345
- description: `Admin login page discovered at ${url}. This is part of the attack surface.`,
367
+ description: `Admin login page discovered at ${url}.`,
346
368
  cwe: 'CWE-200',
347
- evidence
369
+ evidence: { ...evidence, reason: '200 OK with login/password patterns detected in HTML.' }
348
370
  };
349
371
  }
350
372
  // If it's a 200 but not a login page, it could be an exposed panel
351
373
  return {
352
374
  id: `REM-PROBABLE-PANEL-${Date.now()}`,
353
375
  category: 'Probable',
354
- confidence: 'Medium',
355
- severity: 'High',
356
- description: `Potential exposed admin panel at ${url}. Manual verification required.`,
357
- cwe: 'CWE-284', // Improper Access Control
358
- evidence
376
+ confidence: 'Low',
377
+ severity: 'Medium',
378
+ description: `Potential exposed admin panel or dashboard at ${url}. Manual verification required.`,
379
+ cwe: 'CWE-284',
380
+ evidence: { ...evidence, reason: '200 OK on admin-like path, but no explicit login form detected. Could be an unauthorized dashboard.' }
359
381
  };
360
382
  }
361
383
 
362
- // Rule 4: Treat 403 Forbidden as low-confidence informational
384
+ // Rule 4: Treat 403 Forbidden as low-confidence informational (Path Enumeration)
363
385
  if (status === 403) {
364
386
  return {
365
387
  id: `REM-INFO-FORBIDDEN-${Date.now()}`,
366
388
  category: 'Informational',
367
- confidence: 'Low',
389
+ confidence: 'Medium',
368
390
  severity: 'Info',
369
- description: `Path exists but is protected (403 Forbidden): ${url}. This confirms the path's existence.`,
370
- cwe: 'CWE-406', // Insufficient Guarantees of Data Integrity
371
- evidence
391
+ description: `Potential protected path discovered (403 Forbidden): ${url}. This confirms the path exists but access is restricted.`,
392
+ cwe: 'CWE-204',
393
+ evidence: {
394
+ ...evidence,
395
+ reason: 'Server returned 403 Forbidden. This confirms path existence but access control is active. No immediate exposure detected.'
396
+ }
372
397
  };
373
398
  }
374
399
 
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,16 +32,16 @@ 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);
package/core/ui-server.js CHANGED
@@ -20,10 +20,11 @@ export async function startUIServer() {
20
20
  return 'text-green-500';
21
21
  };
22
22
 
23
- const getConfidenceClass = (confidence) => {
24
- if (confidence === 'High') return 'text-green-400';
25
- if (confidence === 'Medium') return 'text-yellow-400';
26
- return 'text-red-400';
23
+ const getCategoryBadge = (category) => {
24
+ if (category === 'Confirmed') return 'bg-red-900/50 text-red-400 border-red-800';
25
+ if (category === 'Probable') return 'bg-orange-900/50 text-orange-400 border-orange-800';
26
+ if (category === 'Hardening') return 'bg-blue-900/50 text-blue-400 border-blue-800';
27
+ return 'bg-gray-800 text-gray-400 border-gray-700';
27
28
  };
28
29
 
29
30
  const html = `
@@ -32,61 +33,130 @@ export async function startUIServer() {
32
33
  <head>
33
34
  <meta charset="UTF-8">
34
35
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
35
- <title>OMEN SEC-CLI Dashboard</title>
36
+ <title>OMEN SEC-CLI Evidence Center</title>
36
37
  <script src="https://cdn.tailwindcss.com"></script>
38
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
37
39
  <style>
38
40
  body { background-color: #0a0a0c; color: #e0e0e0; font-family: 'Inter', sans-serif; }
41
+ .mono { font-family: 'JetBrains Mono', monospace; }
39
42
  .card { background-color: #16161a; border: 1px solid #2d2d3a; }
40
- .tab { cursor: pointer; padding: 10px 15px; border-bottom: 2px solid transparent; }
41
- .tab.active { border-bottom: 2px solid #ef4444; color: #ef4444; }
42
- .collapsible { cursor: pointer; }
43
- .content { display: none; }
44
- pre { background-color: #000; padding: 10px; border-radius: 5px; overflow-x: auto; }
43
+ .tab-btn { cursor: pointer; padding: 12px 24px; border-bottom: 2px solid transparent; transition: all 0.2s; font-weight: 600; }
44
+ .tab-btn:hover { background-color: #1f1f26; }
45
+ .tab-btn.active { border-bottom: 2px solid #ef4444; color: #ef4444; background-color: #1f1f26; }
46
+ .content-area { display: none; }
47
+ .content-area.active { display: block; }
48
+ pre { background-color: #000; padding: 15px; border-radius: 8px; border: 1px solid #333; overflow-x: auto; font-size: 0.85rem; }
49
+ .finding-row { transition: background-color 0.2s; }
50
+ .finding-row:hover { background-color: #1f1f26; }
51
+ .evidence-panel { display: none; }
45
52
  </style>
46
53
  </head>
47
54
  <body class="p-4 md:p-8">
48
55
  <div class="max-w-7xl mx-auto">
49
- <header class="flex justify-between items-center mb-8 border-b border-gray-800 pb-4">
50
- <h1 class="text-3xl font-bold tracking-tighter text-red-500">OMEN <span class="text-white">EVIDENCE-CENTER</span></h1>
51
- <div class="text-right text-sm">
52
- <p class="text-gray-400">Scan ID: ${report.scan_id}</p>
53
- <p class="text-gray-400">${new Date(report.timestamp).toLocaleString()}</p>
56
+ <header class="flex justify-between items-end mb-10 border-b border-gray-800 pb-6">
57
+ <div>
58
+ <h1 class="text-4xl font-extrabold tracking-tighter text-red-500 mb-1">OMEN <span class="text-white">SEC-CLI</span></h1>
59
+ <p class="text-gray-500 font-medium">Professional DevSecOps Audit Framework</p>
60
+ </div>
61
+ <div class="text-right text-sm font-medium text-gray-500">
62
+ <p>Scan ID: <span class="text-gray-300 mono">${report.scan_id}</span></p>
63
+ <p>Date: <span class="text-gray-300">${new Date(report.timestamp).toLocaleString()}</span></p>
54
64
  </div>
55
65
  </header>
56
66
 
57
- <!-- Tabs -->
58
- <div class="flex border-b border-gray-700 mb-6">
59
- <div class="tab active" onclick="showTab('dashboard')">Dashboard</div>
60
- <div class="tab" onclick="showTab('findings')">Findings</div>
61
- <div class="tab" onclick="showTab('surface')">Attack Surface</div>
62
- <div class="tab" onclick="showTab('history')">History</div>
67
+ <!-- Navigation Tabs -->
68
+ <div class="flex border-b border-gray-800 mb-8 bg-[#111116] rounded-t-xl overflow-hidden">
69
+ <div class="tab-btn active" data-target="dashboard">Dashboard</div>
70
+ <div class="tab-btn" data-target="findings">Findings</div>
71
+ <div class="tab-btn" data-target="surface">Attack Surface</div>
72
+ <div class="tab-btn" data-target="history">History</div>
63
73
  </div>
64
74
 
65
75
  <!-- Dashboard Tab -->
66
- <div id="dashboard" class="tab-content block">
67
- <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
68
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Security Score</h3><p class="text-4xl font-bold ${getSeverityClass(report.riskLevel)}">${report.score}/100</p></div>
69
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Risk Level</h3><p class="text-4xl font-bold ${getSeverityClass(report.riskLevel)}">${report.riskLevel}</p></div>
70
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Confirmed</h3><p class="text-4xl font-bold">${report.vulnerabilities.filter(v => v.category === 'Confirmed').length}</p></div>
71
- <div class="card p-4 rounded-lg"><h3 class="text-gray-400">Probable</h3><p class="text-4xl font-bold">${report.vulnerabilities.filter(v => v.category === 'Probable').length}</p></div>
76
+ <div id="dashboard" class="content-area active">
77
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-10">
78
+ <div class="card p-6 rounded-xl shadow-2xl">
79
+ <h3 class="text-gray-500 uppercase text-xs font-bold tracking-widest mb-2">Security Score</h3>
80
+ <p class="text-5xl font-black ${getSeverityClass(report.riskLevel)}">${report.score}/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}</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">${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.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}</span> </div>
119
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Tech Stack</span> <span class="text-gray-300">${(report.attack_surface.tech_stack || []).join(', ') || 'N/A'}</span> </div>
120
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Endpoints Discovered</span> <span class="text-gray-300">${(report.attack_surface.endpoints || []).length}</span> </div>
121
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Forms Detected</span> <span class="text-gray-300">${(report.attack_surface.forms || []).length}</span> </div>
122
+ </div>
123
+ </div>
72
124
  </div>
73
125
  </div>
74
126
 
75
127
  <!-- Findings Tab -->
76
- <div id="findings" class="tab-content hidden">
77
- <div class="card p-4 rounded-lg">
78
- <h2 class="text-xl font-bold mb-4">Vulnerability Details</h2>
79
- <div class="space-y-3">
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">
80
137
  ${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>
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.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.id}</div>
86
144
  </div>
87
- <div id="content-${i}" class="content p-4 bg-gray-900 rounded-b-lg">
88
- <h4 class="font-bold mb-2">Evidence</h4>
89
- <pre>${JSON.stringify(v.evidence, null, 2)}</pre>
145
+ <div id="evidence-${i}" class="evidence-panel p-6 bg-black border-t border-gray-800">
146
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
147
+ <div>
148
+ <h4 class="text-xs font-bold uppercase text-gray-500 mb-3 tracking-widest">Discovery Evidence</h4>
149
+ <pre class="mono">${JSON.stringify(v.evidence, null, 2)}</pre>
150
+ </div>
151
+ <div>
152
+ <h4 class="text-xs font-bold uppercase text-gray-500 mb-3 tracking-widest">Technical Details</h4>
153
+ <div class="space-y-3 text-sm">
154
+ <p><span class="text-gray-500">CWE:</span> <span class="text-blue-400 mono">${v.cwe}</span></p>
155
+ <p><span class="text-gray-500">Confidence:</span> <span class="font-bold text-gray-300">${v.confidence}</span></p>
156
+ <p><span class="text-gray-500">Remediation:</span> <span class="text-gray-400 italic">Consult OMEN AI Protocol for detailed fix.</span></p>
157
+ </div>
158
+ </div>
159
+ </div>
90
160
  </div>
91
161
  </div>
92
162
  `).join('')}
@@ -95,33 +165,70 @@ export async function startUIServer() {
95
165
  </div>
96
166
 
97
167
  <!-- 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>${(report.attack_surface.tech_stack || []).join('\n')}</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_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">${(report.attack_surface.endpoints_discovered || []).join('\n')}</pre> </div>
168
+ <div id="surface" class="content-area">
169
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
170
+ <div class="lg:col-span-2 space-y-8">
171
+ <div class="card p-6 rounded-xl">
172
+ <h3 class="text-xl font-bold mb-4">Discovered Assets</h3>
173
+ <pre class="mono max-h-[600px]">${(report.attack_surface.endpoints || []).join('\n') || 'No endpoints discovered.'}</pre>
174
+ </div>
175
+ </div>
176
+ <div class="space-y-8">
177
+ <div class="card p-6 rounded-xl">
178
+ <h3 class="text-xl font-bold mb-4">Forms & Inputs</h3>
179
+ <pre class="mono">${JSON.stringify(report.attack_surface.forms || [], null, 2)}</pre>
180
+ </div>
181
+ <div class="card p-6 rounded-xl">
182
+ <h3 class="text-xl font-bold mb-4">Tech Fingerprint</h3>
183
+ <div class="flex flex-wrap gap-2">
184
+ ${(report.attack_surface.tech_stack || []).map(t => `<span class="px-3 py-1 bg-gray-800 rounded-full text-xs font-bold text-blue-400 border border-gray-700">${t}</span>`).join('') || '<span class="text-gray-500 italic">No stack identified.</span>'}
185
+ </div>
186
+ </div>
187
+ </div>
103
188
  </div>
104
189
  </div>
105
190
 
106
191
  <!-- 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>
192
+ <div id="history" class="content-area text-center py-20">
193
+ <div class="card p-10 rounded-2xl max-w-lg mx-auto border-dashed border-gray-700 bg-transparent">
194
+ <h2 class="text-2xl font-bold mb-4 text-gray-400">Scan Timeline</h2>
195
+ <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>
196
+ <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
197
  </div>
112
198
  </div>
113
199
 
200
+ <footer class="text-center text-gray-600 mt-16 border-t border-gray-900 pt-8 mb-10">
201
+ <p class="text-xs uppercase tracking-widest font-bold mb-2">OMEN Security Framework - v1.0.16</p>
202
+ <p class="text-[10px] text-gray-700 italic">"The eye that never sleeps, the code that never fails."</p>
203
+ </footer>
114
204
  </div>
205
+
115
206
  <script>
116
- function showTab(tabName) {
117
- document.querySelectorAll('.tab-content').forEach(tab => tab.classList.add('hidden'));
118
- document.getElementById(tabName).classList.remove('hidden');
119
- document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
120
- document.querySelector(`[onclick="showTab('${tabName}')"]`).classList.add('active');
121
- }
122
- function toggleCollapse(index) {
123
- const content = document.getElementById('content-' + index);
124
- content.style.display = content.style.display === 'block' ? 'none' : 'block';
207
+ // Robust Tab System
208
+ document.querySelectorAll('.tab-btn').forEach(btn => {
209
+ btn.addEventListener('click', () => {
210
+ const target = btn.getAttribute('data-target');
211
+
212
+ // Update buttons
213
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
214
+ btn.classList.add('active');
215
+
216
+ // Update areas
217
+ document.querySelectorAll('.content-area').forEach(area => {
218
+ area.classList.remove('active');
219
+ if (area.id === target) area.classList.add('active');
220
+ });
221
+ });
222
+ });
223
+
224
+ // Robust Evidence Toggle
225
+ function toggleEvidence(index) {
226
+ const panel = document.getElementById('evidence-' + index);
227
+ if (panel.style.display === 'block') {
228
+ panel.style.display = 'none';
229
+ } else {
230
+ panel.style.display = 'block';
231
+ }
125
232
  }
126
233
  </script>
127
234
  </body>
@@ -129,7 +236,13 @@ export async function startUIServer() {
129
236
  `;
130
237
  res.send(html);
131
238
  } catch (err) {
132
- res.status(500).send(`<h1>Error loading report</h1><p>Please run a scan first to generate omen-report.json. Error: ${err.message}</p>`);
239
+ res.status(500).send(`
240
+ <body style="background:#0a0a0c; color:#ff4d4d; font-family:sans-serif; padding:50px; text-align:center;">
241
+ <h1>Dashboard Failed to Load</h1>
242
+ <p style="color:#888;">Error details: ${err.message}</p>
243
+ <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>
244
+ </body>
245
+ `);
133
246
  }
134
247
  });
135
248
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omen-sec-cli",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
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.16 \n'));
13
13
  }
14
14
 
15
15
  export function showHelp() {