diff --git a/README.md b/README.md index ad40c511d6..7c58b3cee8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@

Recently updated Proof-of-Concepts

-> 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 diff --git a/docs/assets/site.js b/docs/assets/site.js index 4c459f0716..ddf0a6dd37 100644 --- a/docs/assets/site.js +++ b/docs/assets/site.js @@ -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 = 'No PoCs found yet.'; - return; + if (entries.length === 0) { + tbody.innerHTML = 'No recent PoCs with stars yet.'; + return; + } + + tbody.innerHTML = entries.map(item => { + return ` + ${item.stars}⭐ + ${item.updated} + ${item.name} + ${truncate(item.desc)} + `; + }).join(''); + } catch (err) { + console.warn('Trending render failed', err); + tbody.innerHTML = 'Unable to load trending PoCs.'; } - - tbody.innerHTML = trending.map(item => { - const pocCount = item.poc ? item.poc.length : 0; - const safeDesc = truncate(item.desc, 160); - return ` - ${item.cve} - ${pocCount} - ${safeDesc} - `; - }).join(''); } document.addEventListener('DOMContentLoaded', () => { bindColumnFilters(); - filterTablesByPoc(); + filterTablesByData(); renderTrending(); }); })(); diff --git a/docs/epss/index.html b/docs/epss/index.html index be98f4619c..f9197cdf44 100644 --- a/docs/epss/index.html +++ b/docs/epss/index.html @@ -27,7 +27,7 @@
- +
@@ -112,4 +112,4 @@ - \ No newline at end of file + diff --git a/docs/index.html b/docs/index.html index b65f9cba17..ed1501e461 100644 --- a/docs/index.html +++ b/docs/index.html @@ -57,15 +57,15 @@

Trending PoCs

- Most linked PoCs across the catalog + Most starred PoCs in the past few days
CVEEPSSPercentilePoCsSummary
- + - +
CVEPoCsDescription
StarsUpdatedNameDescription
Loading trending PoCs…
Loading trending PoCs…
@@ -255,7 +255,7 @@ Sorted by score
- +
diff --git a/docs/logic.js b/docs/logic.js index b8c50d7f49..b99a05bc7c 100644 --- a/docs/logic.js +++ b/docs/logic.js @@ -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 = { diff --git a/scripts/build_joined.py b/scripts/build_joined.py index ee249618c7..c521655968 100644 --- a/scripts/build_joined.py +++ b/scripts/build_joined.py @@ -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, } ) diff --git a/scripts/utils.py b/scripts/utils.py index b225131602..198330caa6 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -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
CVEEPSSPercentilePoCsSummary