omen-sec-cli 1.0.16 → 1.0.18

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.
@@ -0,0 +1,43 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ const STATE_FILE = path.join(process.cwd(), '.omen', 'context.json');
5
+
6
+ export async function saveState(data) {
7
+ try {
8
+ await fs.mkdir(path.dirname(STATE_FILE), { recursive: true });
9
+ const currentState = await loadState();
10
+ const newState = { ...currentState, ...data, last_updated: new Date().toISOString() };
11
+ await fs.writeFile(STATE_FILE, JSON.stringify(newState, null, 2));
12
+ return newState;
13
+ } catch (err) {
14
+ console.error('Failed to save OMEN state:', err.message);
15
+ return null;
16
+ }
17
+ }
18
+
19
+ export async function loadState() {
20
+ try {
21
+ const data = await fs.readFile(STATE_FILE, 'utf-8');
22
+ return JSON.parse(data);
23
+ } catch (err) {
24
+ return {
25
+ schema_version: "1.0",
26
+ project: {},
27
+ discovery: {},
28
+ plan: {},
29
+ execution: {
30
+ static: [],
31
+ dynamic: [],
32
+ vulnerabilities: []
33
+ },
34
+ verification: {}
35
+ };
36
+ }
37
+ }
38
+
39
+ export async function clearState() {
40
+ try {
41
+ await fs.unlink(STATE_FILE);
42
+ } catch (err) {}
43
+ }
package/core/ui-server.js CHANGED
@@ -13,17 +13,25 @@ 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';
16
+ const historyDir = path.join(process.cwd(), '.omen', 'history');
17
+ let historyFiles = [];
18
+ try {
19
+ historyFiles = await fs.readdir(historyDir);
20
+ } catch (e) {}
21
+
22
+ const getSeverityClass = (severity = '') => {
23
+ const s = severity.toLowerCase();
24
+ if (s === 'critical') return 'text-red-500';
25
+ if (s === 'high') return 'text-orange-500';
26
+ if (s === 'medium') return 'text-yellow-500';
20
27
  return 'text-green-500';
21
28
  };
22
29
 
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';
30
+ const getCategoryBadge = (category = '') => {
31
+ const c = category.toLowerCase();
32
+ if (c === 'confirmed') return 'bg-red-900/50 text-red-400 border-red-800';
33
+ if (c === 'probable') return 'bg-orange-900/50 text-orange-400 border-orange-800';
34
+ if (c === 'hardening') return 'bg-blue-900/50 text-blue-400 border-blue-800';
27
35
  return 'bg-gray-800 text-gray-400 border-gray-700';
28
36
  };
29
37
 
@@ -68,6 +76,7 @@ export async function startUIServer() {
68
76
  <div class="flex border-b border-gray-800 mb-8 bg-[#111116] rounded-t-xl overflow-hidden">
69
77
  <div class="tab-btn active" data-target="dashboard">Dashboard</div>
70
78
  <div class="tab-btn" data-target="findings">Findings</div>
79
+ <div class="tab-btn" data-target="fixplan">Fix Plan</div>
71
80
  <div class="tab-btn" data-target="surface">Attack Surface</div>
72
81
  <div class="tab-btn" data-target="history">History</div>
73
82
  </div>
@@ -77,19 +86,19 @@ export async function startUIServer() {
77
86
  <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-10">
78
87
  <div class="card p-6 rounded-xl shadow-2xl">
79
88
  <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>
89
+ <p class="text-5xl font-black ${getSeverityClass(report.riskLevel)}">${report.score || 0}/100</p>
81
90
  </div>
82
91
  <div class="card p-6 rounded-xl shadow-2xl">
83
92
  <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>
93
+ <p class="text-5xl font-black ${getSeverityClass(report.riskLevel)}">${report.riskLevel || 'N/A'}</p>
85
94
  </div>
86
95
  <div class="card p-6 rounded-xl shadow-2xl">
87
96
  <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>
97
+ <p class="text-5xl font-black text-white">${(report.vulnerabilities || []).filter(v => v.category?.toLowerCase() === 'confirmed').length}</p>
89
98
  </div>
90
99
  <div class="card p-6 rounded-xl shadow-2xl">
91
100
  <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>
101
+ <p class="text-5xl font-black text-white">${(report.vulnerabilities || []).filter(v => v.category?.toLowerCase() === 'probable').length}</p>
93
102
  </div>
94
103
  </div>
95
104
 
@@ -99,31 +108,70 @@ export async function startUIServer() {
99
108
  <span class="w-2 h-2 bg-red-500 rounded-full mr-2"></span> Top Critical Findings
100
109
  </h2>
101
110
  <div class="space-y-4">
102
- ${report.vulnerabilities.filter(v => v.severity === 'Critical' || v.severity === 'High').slice(0, 5).map(v => `
111
+ ${(report.vulnerabilities || []).filter(v => v.severity?.toLowerCase() === 'critical' || v.severity?.toLowerCase() === 'high').slice(0, 5).map(v => `
103
112
  <div class="p-4 bg-[#1c1c22] rounded-lg border border-gray-800">
104
113
  <div class="flex justify-between items-start mb-2">
105
- <span class="font-bold ${getSeverityClass(v.severity)} text-sm">${v.severity}</span>
114
+ <span class="font-bold ${getSeverityClass(v.severity)} text-sm uppercase">${v.severity}</span>
106
115
  <span class="px-2 py-0.5 rounded border text-[10px] uppercase font-bold ${getCategoryBadge(v.category)}">${v.category}</span>
107
116
  </div>
108
- <p class="text-sm font-medium text-gray-300">${v.description}</p>
117
+ <p class="text-sm font-medium text-gray-300">${v.title || v.description}</p>
109
118
  </div>
110
119
  `).join('') || '<p class="text-gray-500 italic">No critical findings discovered.</p>'}
111
120
  </div>
112
121
  </div>
113
122
  <div class="card p-6 rounded-xl">
114
123
  <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
124
+ <span class="w-2 h-2 bg-blue-500 rounded-full mr-2"></span> Phase Intelligence
116
125
  </h2>
117
126
  <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>
127
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Stack Detected</span> <span class="text-gray-300 font-bold">${report.discovery?.stack || 'N/A'}</span> </div>
128
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Boot Strategy</span> <span class="mono text-gray-300">${report.discovery?.boot_strategy || 'None'}</span> </div>
129
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Execution Steps</span> <span class="text-gray-300">${(report.plan?.steps || []).length} steps</span> </div>
130
+ <div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Last Scan Status</span> <span class="text-green-500 font-bold uppercase">${report.execution?.status || 'N/A'}</span> </div>
122
131
  </div>
123
132
  </div>
124
133
  </div>
125
134
  </div>
126
135
 
136
+ <!-- Findings Tab (already good) -->
137
+
138
+ <!-- Fix Plan Tab -->
139
+ <div id="fixplan" class="content-area">
140
+ <div class="card p-6 rounded-xl mb-8 border-l-4 border-blue-500 bg-blue-900/10">
141
+ <h2 class="text-2xl font-black mb-2 tracking-tight">REMEDIATION CHECKLIST</h2>
142
+ <p class="text-gray-400">Step-by-step guide to secure your application. These findings are prioritized by risk level.</p>
143
+ </div>
144
+ <div class="space-y-6">
145
+ ${(report.vulnerabilities || []).sort((a,b) => {
146
+ const w = {critical:4, high:3, medium:2, low:1, info:0};
147
+ return (w[b.severity?.toLowerCase()] || 0) - (w[a.severity?.toLowerCase()] || 0);
148
+ }).map((v, i) => `
149
+ <div class="card p-6 rounded-xl border-l-4 ${v.severity?.toLowerCase() === 'critical' ? 'border-red-500' : 'border-gray-700'}">
150
+ <div class="flex justify-between items-start mb-4">
151
+ <div>
152
+ <h3 class="text-lg font-bold text-white mb-1">${v.title || v.description}</h3>
153
+ <span class="text-[10px] mono text-gray-500 uppercase tracking-widest">ID: ${v.id}</span>
154
+ </div>
155
+ <div class="text-right">
156
+ <span class="block text-xs font-black ${getSeverityClass(v.severity)} uppercase">${v.severity}</span>
157
+ <span class="block text-[10px] text-gray-500 mono">${v.cwe || 'CWE-N/A'}</span>
158
+ </div>
159
+ </div>
160
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
161
+ <div class="bg-black/50 p-4 rounded-lg border border-gray-800">
162
+ <h4 class="text-[10px] font-bold text-gray-500 uppercase mb-2">How to Fix</h4>
163
+ <p class="text-sm text-gray-300 leading-relaxed">${v.remediation || 'No remediation provided.'}</p>
164
+ </div>
165
+ <div class="bg-black/50 p-4 rounded-lg border border-gray-800">
166
+ <h4 class="text-[10px] font-bold text-gray-500 uppercase mb-2">Verification Command</h4>
167
+ <code class="block text-xs text-blue-400 mono mt-2">npx omen-sec-cli verify --id ${v.id}</code>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ `).join('') || '<p class="text-center py-20 text-gray-500 italic">No vulnerabilities to fix. You are secure!</p>'}
172
+ </div>
173
+ </div>
174
+
127
175
  <!-- Findings Tab -->
128
176
  <div id="findings" class="content-area">
129
177
  <div class="card rounded-xl overflow-hidden">
@@ -134,26 +182,30 @@ export async function startUIServer() {
134
182
  </div>
135
183
  </div>
136
184
  <div class="divide-y divide-gray-800">
137
- ${report.vulnerabilities.map((v, i) => `
185
+ ${(report.vulnerabilities || []).map((v, i) => `
138
186
  <div class="finding-row">
139
187
  <div class="p-4 flex items-center cursor-pointer gap-4" onclick="toggleEvidence(${i})">
140
188
  <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>
189
+ <div class="flex-1 text-sm font-semibold text-gray-300">${v.title || v.description}</div>
142
190
  <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>
191
+ <div class="text-[10px] mono text-gray-600">${v.kind || 'N/A'}</div>
144
192
  </div>
145
193
  <div id="evidence-${i}" class="evidence-panel p-6 bg-black border-t border-gray-800">
146
194
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
147
195
  <div>
148
196
  <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>
197
+ <pre class="mono">${JSON.stringify(v.evidence || {}, null, 2)}</pre>
150
198
  </div>
151
199
  <div>
152
200
  <h4 class="text-xs font-bold uppercase text-gray-500 mb-3 tracking-widest">Technical Details</h4>
153
201
  <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>
202
+ <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>
203
+ <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>
204
+ <p><span class="text-gray-500 font-bold uppercase text-[10px]">Description:</span> <span class="text-gray-300">${v.description || 'N/A'}</span></p>
205
+ <div class="mt-4 p-3 bg-blue-900/20 border border-blue-800 rounded">
206
+ <h5 class="text-[10px] font-bold text-blue-400 uppercase mb-1">Remediation</h5>
207
+ <p class="text-xs text-gray-300">${v.remediation || 'Consult OMEN AI Protocol for detailed fix.'}</p>
208
+ </div>
157
209
  </div>
158
210
  </div>
159
211
  </div>
@@ -170,18 +222,18 @@ export async function startUIServer() {
170
222
  <div class="lg:col-span-2 space-y-8">
171
223
  <div class="card p-6 rounded-xl">
172
224
  <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>
225
+ <pre class="mono max-h-[600px]">${(report.discovery?.entrypoints || []).concat(report.discovery?.critical_files || []).join('\n') || 'No assets discovered.'}</pre>
174
226
  </div>
175
227
  </div>
176
228
  <div class="space-y-8">
177
229
  <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>
230
+ <h3 class="text-xl font-bold mb-4">Phase Log</h3>
231
+ <pre class="mono text-[10px]">${(report.execution?.logs || []).join('\n') || 'No execution logs.'}</pre>
180
232
  </div>
181
233
  <div class="card p-6 rounded-xl">
182
234
  <h3 class="text-xl font-bold mb-4">Tech Fingerprint</h3>
183
235
  <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>'}
236
+ ${(report.attack_surface?.tech_stack || report.discovery?.stack ? [report.discovery.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
237
  </div>
186
238
  </div>
187
239
  </div>
@@ -189,16 +241,28 @@ export async function startUIServer() {
189
241
  </div>
190
242
 
191
243
  <!-- History Tab -->
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>
244
+ <div id="history" class="content-area">
245
+ <div class="card rounded-xl overflow-hidden">
246
+ <div class="p-6 border-b border-gray-800 bg-[#111116]">
247
+ <h2 class="text-xl font-bold">Scan History</h2>
248
+ <p class="text-sm text-gray-500">Historical snapshots saved in .omen/history/</p>
249
+ </div>
250
+ <div class="divide-y divide-gray-800">
251
+ ${historyFiles.reverse().map(file => `
252
+ <div class="p-4 flex justify-between items-center hover:bg-[#1c1c22]">
253
+ <div class="flex items-center gap-3">
254
+ <div class="w-2 h-2 bg-green-500 rounded-full"></div>
255
+ <span class="text-sm font-medium text-gray-300">${file}</span>
256
+ </div>
257
+ <span class="text-[10px] mono text-gray-600">${new Date(parseInt(file.split('-').pop()) || Date.now()).toLocaleString()}</span>
258
+ </div>
259
+ `).join('') || '<div class="p-20 text-center text-gray-500 italic">No historical scans found yet.</div>'}
260
+ </div>
197
261
  </div>
198
262
  </div>
199
263
 
200
264
  <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>
265
+ <p class="text-xs uppercase tracking-widest font-bold mb-2">OMEN Security Framework - v1.0.17</p>
202
266
  <p class="text-[10px] text-gray-700 italic">"The eye that never sleeps, the code that never fails."</p>
203
267
  </footer>
204
268
  </div>
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "omen-sec-cli",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "OMEN — AI Security Engine",
5
+ "engines": {
6
+ "node": ">=20.0.0"
7
+ },
5
8
  "main": "bin/index.js",
6
9
  "type": "module",
7
10
  "bin": {
@@ -15,7 +18,9 @@
15
18
  "axios": "^1.13.6",
16
19
  "chalk": "^5.3.0",
17
20
  "cheerio": "^1.1.0",
21
+ "commander": "^13.1.0",
18
22
  "dotenv": "^17.3.1",
23
+ "execa": "^9.6.1",
19
24
  "express": "^5.2.1",
20
25
  "glob": "^13.0.6",
21
26
  "js-yaml": "^4.1.1",
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.16 \n'));
12
+ console.log(chalk.gray(' Version: 1.0.17 \n'));
13
13
  }
14
14
 
15
15
  export function showHelp() {