-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
323 lines (275 loc) · 12.4 KB
/
app.js
File metadata and controls
323 lines (275 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
'use strict';
// Cached snapshot used as a fallback so the page is never empty if the live
// feed is briefly unreachable. The live /api/kev call overrides this on load.
const SEED = [
{ cveID: 'CVE-2026-0257', vendorProject: 'Palo Alto Networks', product: 'PAN-OS', dateAdded: '2026-05-29', sev: 'critical', ransomware: false, desc: 'Authentication bypass in PAN-OS allowing an unauthorized VPN connection, bypassing security restrictions.' },
{ cveID: 'CVE-2026-8398', vendorProject: 'Disc Soft', product: 'Daemon Tools Lite', dateAdded: '2026-05-27', sev: null, ransomware: false, desc: 'Embedded malicious code shipped within the installer — a supply-chain compromise of the distributed binary.' },
{ cveID: 'CVE-2026-34926', vendorProject: 'Trend Micro', product: 'Apex One (On-Premise)', dateAdded: '2026-05-21', sev: 'high', ransomware: false, desc: 'Directory traversal in the on-premise console permitting access to files outside the intended path.' },
{ cveID: 'CVE-2025-34291', vendorProject: 'Langflow', product: 'Langflow', dateAdded: '2026-05-21', sev: 'high', ransomware: false, desc: 'Origin validation error in Langflow, an open-source LLM app builder — relevant to AI/ML supply-chain exposure.' },
{ cveID: 'CVE-2026-41091', vendorProject: 'Microsoft', product: 'Defender', dateAdded: '2026-05-20', sev: 'high', ransomware: false, desc: 'Elevation of privilege in Microsoft Defender. A local attacker can abuse Defender to reach SYSTEM-level control.' },
{ cveID: 'CVE-2026-45498', vendorProject: 'Microsoft', product: 'Defender', dateAdded: '2026-05-20', sev: 'medium', ransomware: false, desc: 'Denial of service in Microsoft Defender, added alongside the EoP flaw in the same batch.' },
{ cveID: 'CVE-2026-31431', vendorProject: 'Linux', product: 'Kernel', dateAdded: '2026-05-09', sev: 'high', ransomware: false, desc: 'Local privilege escalation (Copy Fail) — incorrect resource transfer between spheres lets an unprivileged user gain root. PoC widely available. Fixed in 6.18.22 / 6.19.12 / 7.0.' },
{ cveID: 'CVE-2026-20182', vendorProject: 'Cisco', product: 'Catalyst SD-WAN Controller', dateAdded: '2026-05-14', sev: 'critical', ransomware: false, desc: 'Authentication bypass in the Catalyst SD-WAN Controller; covered under Emergency Directive 26-03.' },
{ cveID: 'CVE-2024-57726', vendorProject: 'SimpleHelp', product: 'SimpleHelp', dateAdded: '2026-04-24', sev: 'critical', ransomware: false, desc: 'Missing authorization lets low-privileged technicians mint API keys with excess permissions, escalating to admin. Linked to DragonForce ransomware precursor activity.' },
{ cveID: 'CVE-2024-7399', vendorProject: 'Samsung', product: 'MagicINFO 9 Server', dateAdded: '2026-04-24', sev: 'high', ransomware: false, desc: 'Path traversal allowing arbitrary file write. Exploitation tied to Mirai botnet deployment.' },
{ cveID: 'CVE-2025-29635', vendorProject: 'D-Link', product: 'DIR-823X (EOL)', dateAdded: '2026-04-24', sev: 'high', ransomware: false, desc: 'Command injection on end-of-life DIR-823X routers via a crafted POST to /goform/set_prohibiting.' },
];
const $ = (id) => document.getElementById(id);
// ---- Safe DOM construction --------------------------------------------------
// Everything the feeds display (malware URLs, tags, threat names, IPs, country
// codes) is attacker-influenced data from third-party blocklists. We NEVER build
// markup from it. Every dynamic value goes in through `textContent`, so it is
// always treated as inert text and can't be parsed as HTML — no XSS surface,
// regardless of what a poisoned feed contains. The strict CSP (script-src 'self',
// no inline) is the second layer; this is the first.
function el(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text != null) node.textContent = text;
return node;
}
// Replace a status line with a single classed span of plain text.
function setNote(node, className, text) {
node.replaceChildren(el('span', className, text));
}
// Severity tiers. Colors live in style.css as `.sev-<tier>` classes (not inline)
// so the strict CSP `style-src 'self'` keeps blocking inline styles.
const SEV_TIERS = ['critical', 'high', 'medium', 'low'];
function tierFromScore(score) {
if (score >= 9) return 'critical';
if (score >= 7) return 'high';
if (score >= 4) return 'medium';
return 'low';
}
// Returns a badge element, or null when there's no score and no recognized
// severity word (render nothing).
function sevBadge(score, sev) {
let tier, label;
if (typeof score === 'number') {
tier = tierFromScore(score);
label = tier + ' ' + score.toFixed(1);
} else if (SEV_TIERS.includes(sev)) {
tier = sev;
label = sev;
} else {
return null;
}
return el('span', 'sev sev-' + tier, label);
}
// ---- KEV feed ---------------------------------------------------------------
let kevData = SEED.slice();
let live = false;
const cvssCache = {};
function setStatus() {
setNote($('kev-status'), 'muted', live
? `// ${kevData.length} entries · live cisa feed · newest first · click to expand`
: `// ${kevData.length} entries · cached snapshot · live feed loads from /api/kev`);
}
function renderKev() {
const q = $('k-filter').value.trim().toLowerCase();
let items = kevData;
if (q) {
items = kevData.filter(
(v) =>
(v.cveID || '').toLowerCase().includes(q) ||
(v.vendorProject || '').toLowerCase().includes(q) ||
(v.product || '').toLowerCase().includes(q)
);
}
items = items.slice(0, 80);
const list = $('kev-list');
if (!items.length) {
list.replaceChildren(el('div', 'note list-empty', 'no matches'));
return;
}
const frag = document.createDocumentFragment();
for (const v of items) {
const item = el('div', 'kev-item');
item.tabIndex = 0;
item.setAttribute('role', 'button');
item.setAttribute('aria-expanded', 'false');
const top = el('div', 'kev-top');
top.appendChild(el('span', 'kev-cve', v.cveID));
const badge = sevBadge(v.score, v.sev);
if (badge) top.appendChild(badge);
if (v.ransomware) top.appendChild(el('span', 'ransom', 'ransomware'));
item.appendChild(top);
item.appendChild(
el('div', 'kev-meta', `${v.vendorProject} · ${v.product} · added ${v.dateAdded}`)
);
const detail = el('div', 'kev-detail');
item.appendChild(detail);
const toggle = () => expandKev(item, v, detail);
item.addEventListener('click', toggle);
item.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); }
});
frag.appendChild(item);
}
list.replaceChildren(frag);
}
async function expandKev(item, v, d) {
if (d.classList.contains('open')) {
d.classList.remove('open');
item.setAttribute('aria-expanded', 'false');
return;
}
d.classList.add('open');
item.setAttribute('aria-expanded', 'true');
d.replaceChildren(el('div', null, v.desc || v.name || 'no description'));
if (v.dueDate) d.appendChild(el('div', 'lbl kev-line', 'due: ' + v.dueDate));
// If the snapshot already carries a severity word, show it without hitting NVD.
if (typeof v.score === 'number' || v.sev) return;
const slot = el('div', 'muted kev-line', 'fetching cvss…');
d.appendChild(slot);
if (v.cveID in cvssCache) {
showCvss(slot, cvssCache[v.cveID]);
return;
}
try {
const r = await fetch(`/api/lookup?source=cvss&cve=${encodeURIComponent(v.cveID)}`);
if (!r.ok) throw new Error(String(r.status));
const j = await r.json();
cvssCache[v.cveID] = j.score;
// The fetch may resolve after the user collapsed the row; don't write
// stale content into a closed detail panel.
if (!d.classList.contains('open')) return;
showCvss(slot, j.score);
} catch (e) {
slot.className = 'err kev-line';
slot.textContent = 'cvss unavailable';
}
}
function showCvss(slot, score) {
const wrap = el('div', 'kev-line');
wrap.appendChild(el('span', 'lbl', 'cvss: '));
wrap.appendChild(
typeof score === 'number' ? sevBadge(score, null) : el('span', 'muted', 'n/a')
);
slot.replaceWith(wrap);
}
async function loadKev() {
try {
const r = await fetch('/api/kev');
if (!r.ok) return;
const j = await r.json();
if (Array.isArray(j.vulnerabilities) && j.vulnerabilities.length) {
kevData = j.vulnerabilities; // already slimmed + sorted server-side
live = true;
setStatus();
renderKev();
}
} catch (e) {
// keep the seed snapshot
}
}
// ---- URLhaus online feed ----------------------------------------------------
// Live malware URLs actively serving, newest first, filterable by host/threat/tag.
let urlhausData = [];
function renderUrlhaus() {
const status = $('urlhaus-status');
const list = $('urlhaus-list');
if (!urlhausData.length) {
setNote(status, 'muted', '// loading live feed from /api/urlhaus…');
list.replaceChildren();
return;
}
const q = $('u-filter').value.trim().toLowerCase();
let items = urlhausData;
if (q) {
items = urlhausData.filter(
(u) =>
(u.host || '').toLowerCase().includes(q) ||
(u.threat || '').toLowerCase().includes(q) ||
(u.tags || []).some((t) => t.toLowerCase().includes(q))
);
}
setNote(status, 'muted', `// ${items.length} of ${urlhausData.length} online urls · newest first`);
const shown = items.slice(0, 80);
if (!shown.length) {
list.replaceChildren(el('div', 'note list-empty', 'no matches'));
return;
}
const frag = document.createDocumentFragment();
for (const u of shown) {
const item = el('div', 'feed-item');
const top = el('div', 'feed-top');
top.appendChild(el('span', 'feed-host', u.host || '—'));
if (u.threat) top.appendChild(el('span', 'chip warn', u.threat));
item.appendChild(top);
// The live malware URL is rendered as inert TEXT, never as a clickable
// <a href> — there is deliberately no way to navigate to it from here.
item.appendChild(el('div', 'feed-url', u.url));
let meta = `added ${u.dateAdded}`;
if (u.tags && u.tags.length) meta += ' · ' + u.tags.slice(0, 8).join(', ');
item.appendChild(el('div', 'feed-meta', meta));
frag.appendChild(item);
}
list.replaceChildren(frag);
}
async function loadUrlhaus() {
try {
const r = await fetch('/api/urlhaus');
if (!r.ok) throw new Error(String(r.status));
const j = await r.json();
urlhausData = Array.isArray(j.urls) ? j.urls : [];
renderUrlhaus();
} catch (e) {
setNote($('urlhaus-status'), 'err', '// urlhaus feed unavailable');
}
}
// ---- Feodo C2 feed ----------------------------------------------------------
// Active botnet command-and-control IPs with malware family and country.
let feodoData = [];
function renderFeodo() {
const status = $('feodo-status');
const list = $('feodo-list');
if (!feodoData.length) {
setNote(status, 'muted', '// loading live feed from /api/feodo…');
list.replaceChildren();
return;
}
setNote(status, 'muted', `// ${feodoData.length} active c2 servers · newest first`);
const frag = document.createDocumentFragment();
for (const c of feodoData.slice(0, 80)) {
const item = el('div', 'feed-item');
const top = el('div', 'feed-top');
top.appendChild(el('span', 'feed-host', c.ip + (c.port != null ? ':' + c.port : '')));
if (c.malware) top.appendChild(el('span', 'chip bad-chip', c.malware));
if (c.country) top.appendChild(el('span', 'chip', c.country));
item.appendChild(top);
let meta = `first seen ${c.firstSeen || '—'}`;
if (c.lastSeen) meta += ' · last online ' + c.lastSeen;
item.appendChild(el('div', 'feed-meta', meta));
frag.appendChild(item);
}
list.replaceChildren(frag);
}
async function loadFeodo() {
try {
const r = await fetch('/api/feodo');
if (!r.ok) throw new Error(String(r.status));
const j = await r.json();
feodoData = Array.isArray(j.c2) ? j.c2 : [];
renderFeodo();
} catch (e) {
setNote($('feodo-status'), 'err', '// feodo feed unavailable');
}
}
// ---- Wire up ----------------------------------------------------------------
function init() {
setStatus();
renderKev();
loadKev();
$('k-filter').addEventListener('input', renderKev);
renderUrlhaus();
loadUrlhaus();
$('u-filter').addEventListener('input', renderUrlhaus);
renderFeodo();
loadFeodo();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}