mirror of
https://github.com/0xMarcio/cve.git
synced 2026-05-16 21:03:29 +02:00
Filter to PoC-backed CVEs and fix trending PoCs
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
<h1 align="center">Recently updated Proof-of-Concepts</h1>
|
||||
|
||||
> Live API + site: `/api/v1/index.json`, `/api/v1/top/today.json`, and Pages generated via `python scripts/build_all.py` for the new GitHub PoC discovery + scoring pipeline.
|
||||
|
||||
|
||||
## 2025
|
||||
|
||||
### Latest 20 of 433 Repositories
|
||||
|
||||
+73
-37
@@ -1,6 +1,7 @@
|
||||
(function(){
|
||||
let datasetPromise = null;
|
||||
let pocSet = null;
|
||||
let descSet = null;
|
||||
|
||||
function fetchDataset() {
|
||||
if (datasetPromise) return datasetPromise;
|
||||
@@ -25,15 +26,19 @@
|
||||
return datasetPromise;
|
||||
}
|
||||
|
||||
async function ensurePocSet() {
|
||||
if (pocSet) return pocSet;
|
||||
async function ensureSets() {
|
||||
if (pocSet && descSet) return { pocSet, descSet };
|
||||
const dataset = await fetchDataset();
|
||||
pocSet = new Set(
|
||||
dataset
|
||||
.filter(item => Array.isArray(item.poc) && item.poc.length > 0)
|
||||
.map(item => (item.cve || '').toUpperCase())
|
||||
);
|
||||
return pocSet;
|
||||
pocSet = new Set();
|
||||
descSet = new Set();
|
||||
dataset.forEach(item => {
|
||||
const cve = (item.cve || '').toUpperCase();
|
||||
const desc = (item.desc || '').trim();
|
||||
const hasPoc = Array.isArray(item.poc) && item.poc.length > 0;
|
||||
if (hasPoc) pocSet.add(cve);
|
||||
if (desc) descSet.add(cve);
|
||||
});
|
||||
return { pocSet, descSet };
|
||||
}
|
||||
|
||||
function bindColumnFilters() {
|
||||
@@ -52,59 +57,90 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function filterTablesByPoc() {
|
||||
const set = await ensurePocSet();
|
||||
document.querySelectorAll('table[data-require-poc]').forEach(table => {
|
||||
async function filterTablesByData() {
|
||||
const { pocSet, descSet } = await ensureSets();
|
||||
document.querySelectorAll('table[data-require-poc], table[data-require-desc]').forEach(table => {
|
||||
for (const row of Array.from(table.querySelectorAll('tbody tr'))) {
|
||||
const link = row.querySelector('a');
|
||||
const idText = (link ? link.textContent : row.textContent || '').trim().toUpperCase();
|
||||
if (!set.has(idText)) {
|
||||
const needsPoc = table.hasAttribute('data-require-poc');
|
||||
const needsDesc = table.hasAttribute('data-require-desc');
|
||||
const hasPoc = pocSet.has(idText);
|
||||
const hasDesc = descSet.has(idText);
|
||||
if ((needsPoc && !hasPoc) || (needsDesc && !hasDesc)) {
|
||||
row.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function truncate(text, limit = 140) {
|
||||
function truncate(text, limit = 160) {
|
||||
if (!text) return '';
|
||||
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
||||
}
|
||||
|
||||
function parseRelativeDays(label) {
|
||||
if (!label) return Infinity;
|
||||
const lower = label.toLowerCase();
|
||||
if (lower.includes('hour') || lower.includes('minute') || lower.includes('just')) return 0;
|
||||
const match = lower.match(/(\d+)\s*day/);
|
||||
return match ? parseInt(match[1], 10) : Infinity;
|
||||
}
|
||||
|
||||
function parseTrendingMarkdown(text) {
|
||||
const rows = [];
|
||||
const regex = /^\|\s*(\d+)\s*⭐\s*\|\s*([^|]+)\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.*?)\|$/;
|
||||
text.split('\n').forEach(line => {
|
||||
const trimmed = line.trim();
|
||||
const m = regex.exec(trimmed);
|
||||
if (!m) return;
|
||||
const stars = parseInt(m[1], 10);
|
||||
const updated = m[2].trim();
|
||||
const name = m[3].trim();
|
||||
const url = m[4].trim();
|
||||
const desc = m[5].trim();
|
||||
const ageDays = parseRelativeDays(updated);
|
||||
rows.push({ stars, updated, name, url, desc, ageDays });
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function renderTrending() {
|
||||
const container = document.querySelector('[data-trending]');
|
||||
const tbody = document.getElementById('trending-body');
|
||||
if (!container || !tbody) return;
|
||||
|
||||
const data = await fetchDataset();
|
||||
const trending = data
|
||||
.filter(item => item && item.cve && Array.isArray(item.poc) && item.poc.length > 0)
|
||||
.map(item => ({ cve: item.cve, desc: item.desc || 'No description available.', poc: item.poc }))
|
||||
.sort((a, b) => {
|
||||
const delta = (b.poc?.length || 0) - (a.poc?.length || 0);
|
||||
if (delta !== 0) return delta;
|
||||
return (b.cve || '').localeCompare(a.cve || '');
|
||||
})
|
||||
.slice(0, 12);
|
||||
try {
|
||||
const res = await fetch('/README.md', { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error('failed to load README');
|
||||
const text = await res.text();
|
||||
const entries = parseTrendingMarkdown(text)
|
||||
.filter(item => item.ageDays <= 5)
|
||||
.sort((a, b) => b.stars - a.stars)
|
||||
.slice(0, 20);
|
||||
|
||||
if (trending.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="muted">No PoCs found yet.</td></tr>';
|
||||
return;
|
||||
if (entries.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="muted">No recent PoCs with stars yet.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = entries.map(item => {
|
||||
return `<tr>
|
||||
<td>${item.stars}⭐</td>
|
||||
<td>${item.updated}</td>
|
||||
<td><a href="${item.url}" target="_blank" rel="noreferrer">${item.name}</a></td>
|
||||
<td class="mono">${truncate(item.desc)}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
} catch (err) {
|
||||
console.warn('Trending render failed', err);
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="muted">Unable to load trending PoCs.</td></tr>';
|
||||
}
|
||||
|
||||
tbody.innerHTML = trending.map(item => {
|
||||
const pocCount = item.poc ? item.poc.length : 0;
|
||||
const safeDesc = truncate(item.desc, 160);
|
||||
return `<tr>
|
||||
<td class="cve-cell"><a href="/cve/?id=${item.cve}">${item.cve}</a></td>
|
||||
<td>${pocCount}</td>
|
||||
<td>${safeDesc}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
bindColumnFilters();
|
||||
filterTablesByPoc();
|
||||
filterTablesByData();
|
||||
renderTrending();
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
<input type="search" placeholder="Filter CVE" data-filter-table="epss-table" class="filter" />
|
||||
<div class="table-responsive">
|
||||
<table class="list" id="epss-table">
|
||||
<table class="list" id="epss-table" data-require-poc data-require-desc>
|
||||
<thead><tr><th>CVE</th><th>EPSS</th><th>Percentile</th><th>PoCs</th><th>Summary</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -112,4 +112,4 @@
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
+4
-4
@@ -57,15 +57,15 @@
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<h1>Trending PoCs</h1>
|
||||
<span class="muted">Most linked PoCs across the catalog</span>
|
||||
<span class="muted">Most starred PoCs in the past few days</span>
|
||||
</div>
|
||||
<div class="table-wrap" data-trending>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>CVE</th><th>PoCs</th><th>Description</th></tr>
|
||||
<tr><th>Stars</th><th>Updated</th><th>Name</th><th>Description</th></tr>
|
||||
</thead>
|
||||
<tbody id="trending-body">
|
||||
<tr><td colspan="3" class="muted">Loading trending PoCs…</td></tr>
|
||||
<tr><td colspan="4" class="muted">Loading trending PoCs…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -255,7 +255,7 @@
|
||||
<span class="muted">Sorted by score</span>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<table data-require-poc data-require-desc>
|
||||
<thead><tr><th>CVE</th><th>EPSS</th><th>Percentile</th><th>PoCs</th><th>Summary</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
||||
+7
-2
@@ -60,11 +60,16 @@ function prepareDataset(raw) {
|
||||
const base = entry.desc || '';
|
||||
return replaceStrings.reduce((desc, str) => desc.replace(str, ''), base);
|
||||
};
|
||||
return raw.map(entry => {
|
||||
return raw
|
||||
.filter(entry => {
|
||||
const desc = (entry.desc || '').trim();
|
||||
return desc && Array.isArray(entry.poc) && entry.poc.length > 0;
|
||||
})
|
||||
.map(entry => {
|
||||
const descCleaned = descKeyCleaned(entry);
|
||||
const searchText = `${entry.cve || ''} ${descCleaned}`.toLowerCase();
|
||||
return { ...entry, _searchText: searchText };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const controls = {
|
||||
|
||||
@@ -40,7 +40,10 @@ def enrich_kev(kev_items: List[Dict], epss_lookup: Dict[str, Dict], poc_index: D
|
||||
continue
|
||||
cve = cve.upper()
|
||||
epss_info = epss_lookup.get(cve, {})
|
||||
poc_count = len(poc_index.get(cve, {}).get("poc", []))
|
||||
poc_info = poc_index.get(cve)
|
||||
if not poc_info or not poc_info.get("poc"):
|
||||
continue
|
||||
poc_count = len(poc_info["poc"])
|
||||
enriched.append(
|
||||
{
|
||||
"cve": cve,
|
||||
@@ -92,12 +95,16 @@ def build_high_epss_not_in_kev(
|
||||
epss_score = row.get("epss") or 0.0
|
||||
if epss_score < threshold:
|
||||
continue
|
||||
poc_count = len(poc_index.get(cve, {}).get("poc", []))
|
||||
poc_info = poc_index.get(cve)
|
||||
if not poc_info or not poc_info.get("poc"):
|
||||
continue
|
||||
poc_count = len(poc_info["poc"])
|
||||
output.append(
|
||||
{
|
||||
"cve": cve,
|
||||
"epss": row.get("epss"),
|
||||
"percentile": row.get("percentile"),
|
||||
"summary": truncate_description(poc_info.get("desc", "")),
|
||||
"poc_count": poc_count,
|
||||
}
|
||||
)
|
||||
|
||||
+4
-1
@@ -107,10 +107,13 @@ def load_poc_index() -> Dict[str, Dict[str, object]]:
|
||||
cve = str(entry.get("cve", "")).upper()
|
||||
if not is_valid_cve(cve):
|
||||
continue
|
||||
desc = (entry.get("desc") or "").strip()
|
||||
poc_links = stable_unique(entry.get("poc", []) or [])
|
||||
poc_links = filter_links_by_blacklist(poc_links, blacklist)
|
||||
if not desc or not poc_links:
|
||||
continue
|
||||
mapping[cve] = {
|
||||
"desc": entry.get("desc", ""),
|
||||
"desc": desc,
|
||||
"poc": poc_links,
|
||||
}
|
||||
return mapping
|
||||
|
||||
Reference in New Issue
Block a user