Simplify site to search + trending

This commit is contained in:
0xMarcio
2025-12-18 07:13:08 +01:00
parent 8f76f94f78
commit 5c78e585a1
35 changed files with 286 additions and 4247 deletions

View File

@@ -1,69 +0,0 @@
name: Build pipeline + Pages
on:
schedule:
- cron: "15 5 * * *"
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Cache dependencies and API cache
uses: actions/cache@v4
with:
path: |
~/.cache/pip
data/cache
key: ${{ runner.os }}-cve-pipeline-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-cve-pipeline-
- name: Install requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Build pipeline outputs + site
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python scripts/build_all.py --days 7
- name: Validate JSON index
run: python -m json.tool docs/api/v1/index.json > /dev/null
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deploy
uses: actions/deploy-pages@v4

View File

@@ -1,41 +0,0 @@
name: Generate CVE JSON
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Change directory to docs and run CVE JSON generator script
run: |
cd /home/runner/work/cve/cve/docs
python generate_cve_list.py
- name: Check for changes and commit if necessary
run: |
cd /home/runner/work/cve/cve
git config --global user.name '0xMarcio'
git config --global user.email 'marc@codepwn.win'
git remote set-url origin https://github-actions[bot]:$GITHUB_TOKEN@github.com/0xMarcio/cve.git
if [ -n "$(git status --porcelain)" ]; then
git add .
git commit -m "Update CVE list $(date +'%Y-%m-%d %H:%M')"
git push origin main
else
echo "No changes to commit"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -3,7 +3,7 @@ name: Hot CVEs List
on:
workflow_dispatch:
schedule:
- cron: '30 */12 * * *'
- cron: '0 */6 * * *'
jobs:
ScheduledRun:

View File

@@ -7,15 +7,6 @@ on:
push:
branches:
- main
paths:
- 'scripts/**'
- 'templates/**'
- 'docs/assets/**'
- 'README.md'
- '.github/getTrending.py'
- '.github/workflows/hot_cves.yml'
- 'requirements.txt'
- '.github/workflows/site.yml'
permissions:
contents: read
@@ -43,11 +34,6 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Fetch KEV & EPSS
run: |
python scripts/fetch_kev.py
python scripts/fetch_epss.py
- name: Build site
run: python scripts/build_site.py --html-mode summary

View File

@@ -1,45 +0,0 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy cve.codepwn.win
on:
# Runs on pushes targeting the default branch and changes in the docs directory
push:
branches: ["main"]
paths:
- 'docs/**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload only the docs directory
path: 'docs'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Redirecting…</title>
<meta http-equiv="refresh" content="3;url=/" />
<meta http-equiv="refresh" content="2;url=/" />
<link rel="stylesheet" href="/style.css" />
</head>
<body class="color-no-search">
@@ -12,27 +12,8 @@
<section class="hero">
<p class="eyebrow">Not found</p>
<h1>Redirecting…</h1>
<p class="lede" id="msg">Trying to find the right page.</p>
<p class="lede">Page not found. Taking you home.</p>
</section>
</main>
<script>
(function() {
const path = window.location.pathname;
const cveMatch = path.match(/\\/cve\\/((CVE-\\d{4}-\\d+))\\.html/i);
if (cveMatch) {
const target = `/cve/?id=${cveMatch[1].toUpperCase()}`;
document.getElementById('msg').textContent = `Sending you to ${target}`;
window.location.replace(target);
return;
}
const navMatch = path.match(/^\\/(kev|epss|diffs)$/i);
if (navMatch) {
window.location.replace(`/${navMatch[1].toLowerCase()}/`);
return;
}
document.getElementById('msg').textContent = 'Page not found. Taking you home…';
setTimeout(() => window.location.replace('/'), 1500);
})();
</script>
</body>
</html>

View File

@@ -1,54 +0,0 @@
(function() {
const qs = (sel) => document.querySelector(sel);
const initIndexSearch = () => {
const input = document.querySelector("[data-index-search]");
if (!input) return;
const targetSel = input.getAttribute("data-target");
const target = targetSel ? qs(targetSel) : null;
const indexUrl = input.getAttribute("data-index-url");
if (!target || !indexUrl) return;
let cached = [];
fetch(indexUrl)
.then((resp) => resp.json())
.then((data) => { cached = data.items || []; })
.catch(() => { target.innerHTML = "<p class='muted small'>Index unavailable.</p>"; });
const render = (term) => {
if (!cached.length) return;
const value = term.trim().toLowerCase();
const results = cached.filter((row) => {
if (!value) return false;
return row.cve_id.toLowerCase().includes(value) ||
(row.top_languages || []).join(" ").toLowerCase().includes(value) ||
String(row.max_score || "").includes(value);
}).slice(0, 40);
if (!results.length) {
target.innerHTML = "<p class='muted small'>No matches yet.</p>";
return;
}
target.innerHTML = results.map((row) => {
const langs = (row.top_languages || []).map((lang) => `<span class="pill tiny">${lang}</span>`).join(" ");
return `<article class="card">
<div class="card-title"><a href="/cve/${row.cve_id}.html">${row.cve_id}</a></div>
<div class="meta-row">
<span class="pill tier-high">${row.high_confidence} high</span>
<span class="pill tier-medium">${row.medium_confidence} med</span>
<span class="pill">${row.poc_count} PoCs</span>
</div>
<div class="muted small">Max score ${row.max_score || 0}</div>
<div class="pill-row">${langs}</div>
</article>`;
}).join("");
};
input.addEventListener("input", (e) => render(e.target.value));
};
document.addEventListener("DOMContentLoaded", () => {
initIndexSearch();
});
})();

View File

@@ -1,167 +0,0 @@
(function () {
const params = new URLSearchParams(window.location.search);
const rawId = params.get("id") || params.get("cve");
const cveId = rawId ? rawId.toUpperCase() : null;
const titleEl = document.getElementById("cve-title");
const summaryEl = document.getElementById("cve-summary");
const metaEl = document.getElementById("cve-meta");
const factsEl = document.getElementById("cve-facts");
const pocRowsEl = document.getElementById("cve-poc-rows");
const kevRowsEl = document.getElementById("kev-rows");
const detailSection = document.getElementById("cve-details");
const notFoundSection = document.getElementById("not-found");
function setLoading(message) {
titleEl.textContent = cveId || "CVE details";
summaryEl.textContent = message;
}
function getDescriptionText(data) {
const desc = (data?.description || "").trim();
if (desc) return desc;
const kevDesc = (data?.kev?.short_description || "").trim();
if (kevDesc) return kevDesc;
return "No description available.";
}
function hasKevData(kev) {
if (!kev || typeof kev !== "object") return false;
return Boolean(
(kev.short_description && kev.short_description.trim()) ||
kev.date_added ||
kev.due_date ||
kev.required_action ||
kev.notes
);
}
function renderFacts(data) {
const pocCount = data.poc_count ?? (Array.isArray(data.poc_links) ? data.poc_links.length : Array.isArray(data.poc) ? data.poc.length : undefined);
const items = [];
const vendorValue = data.vendor || "Unknown vendor";
const productValue = data.product || "Unknown product";
const epssValue = typeof data.epss === "number" ? data.epss.toFixed(3) : "n/a";
const percentileValue = typeof data.percentile === "number" ? `${Math.round(data.percentile * 100)}th` : "n/a";
const pocValue = pocCount ?? 0;
items.push({ label: "Vendor", value: vendorValue });
items.push({ label: "Product", value: productValue });
items.push({ label: "EPSS", value: epssValue });
items.push({ label: "Percentile", value: percentileValue });
items.push({ label: "PoCs", value: pocValue });
if (hasKevData(data.kev)) items.push({ label: "KEV status", value: data.kev.date_added ? `Added ${data.kev.date_added}` : "Listed" });
if (items.length === 0) {
factsEl.innerHTML = `<div class="stat"><strong>—</strong><span>No overview data yet.</span></div>`;
return;
}
factsEl.innerHTML = items
.map((item) => `<div class="stat"><strong>${item.value}</strong><span>${item.label}</span></div>`)
.join("");
}
function renderPocs(links) {
pocRowsEl.innerHTML = "";
if (!links || links.length === 0) {
pocRowsEl.innerHTML = '<tr><td class="muted">No PoC links available.</td></tr>';
return;
}
pocRowsEl.innerHTML = links
.map((link) => `<tr><td><a href="${link}" target="_blank" rel="noreferrer">${link}</a></td></tr>`)
.join("");
}
function renderMeta(data) {
const pills = [];
if (data.vendor) pills.push(`Vendor: ${data.vendor}`);
if (data.product) pills.push(`Product: ${data.product}`);
if (hasKevData(data.kev)) pills.push("On KEV list");
metaEl.innerHTML = pills.map((text) => `<span class="pill">${text}</span>`).join("");
}
function renderKev(kev) {
if (!hasKevData(kev)) {
document.getElementById("kev-section").style.display = "none";
return;
}
const rows = [];
if (kev.short_description) rows.push(["Summary", kev.short_description]);
if (kev.date_added) rows.push(["Date added", kev.date_added]);
if (kev.due_date) rows.push(["Due", kev.due_date]);
if (kev.required_action) rows.push(["Required action", kev.required_action]);
if (kev.notes) rows.push(["Notes", kev.notes]);
kevRowsEl.innerHTML = rows.map(([k, v]) => `<tr><th>${k}</th><td>${v}</td></tr>`).join("");
document.getElementById("kev-section").style.display = "";
}
async function fetchCveFromApi(id) {
const res = await fetch(`/api/v1/cve/${id}.json`, { cache: "no-store" });
if (!res.ok) throw new Error("notfound");
return res.json();
}
async function fetchFromList(id) {
const res = await fetch("/CVE_list.json", { cache: "no-store" });
if (!res.ok) throw new Error("fallback-missing");
const data = await res.json();
const match = (data || []).find((row) => (row.cve || "").toUpperCase() === id);
if (!match) throw new Error("fallback-notfound");
return {
cve: id,
description: match.desc,
poc_links: match.poc || [],
poc_count: (match.poc || []).length,
};
}
async function load() {
if (!cveId) {
setLoading("Provide ?id=CVE-YYYY-#### in the URL to view details.");
notFoundSection.style.display = "";
return;
}
setLoading("Loading CVE details…");
try {
const data = await fetchCveFromApi(cveId);
titleEl.textContent = data.cve || cveId;
const desc = getDescriptionText(data);
summaryEl.textContent = desc;
renderFacts(data);
renderPocs(data.poc_links || data.poc || []);
renderKev(data.kev);
renderMeta(data);
detailSection.style.display = "";
notFoundSection.style.display = "none";
return;
} catch (err) {
console.warn("API lookup failed, trying CVE_list.json", err);
}
try {
const fallback = await fetchFromList(cveId);
titleEl.textContent = fallback.cve;
const desc = getDescriptionText(fallback);
summaryEl.textContent = desc;
renderFacts(fallback);
renderPocs(fallback.poc_links || fallback.poc || []);
renderKev(null);
renderMeta(fallback);
detailSection.style.display = "";
notFoundSection.style.display = "none";
} catch (err) {
console.warn("CVE_list lookup failed", err);
notFoundSection.style.display = "";
detailSection.style.display = "none";
metaEl.innerHTML = "";
titleEl.textContent = cveId;
summaryEl.textContent = "No data found for this CVE.";
}
}
document.addEventListener("DOMContentLoaded", load);
})();

View File

@@ -1,79 +0,0 @@
(function () {
function cardTemplate(item) {
return `
<article class="card">
<div class="card-title"><a href="/cve/?id=${item.cve}">${item.cve}</a></div>
<div class="card-meta">EPSS ${item.epss !== null && item.epss !== undefined ? item.epss.toFixed(3) : "0.000"}${item.percentile !== null && item.percentile !== undefined ? Math.round(item.percentile * 100) + "th pct" : ""}</div>
<p>${item.summary || "No description."}</p>
${item.vendor ? `<div class="badge">${item.vendor}</div>` : ""}
${item.product ? `<div class="badge">${item.product}</div>` : ""}
</article>
`;
}
function renderCards(gridId, items) {
const el = document.getElementById(gridId);
if (!el) return;
if (!items || items.length === 0) {
el.innerHTML = '<p class="muted">No data available.</p>';
return;
}
el.innerHTML = items.map(cardTemplate).join("");
}
function renderDiffTable(diff) {
const tbody = document.getElementById("diff-table-body");
if (!tbody) return;
const kevCount = (diff.new_kev_entries || []).length;
const kevExamples = (diff.new_kev_entries || []).slice(0, 5).map((row) => `<a href="/cve/?id=${row.cve}">${row.cve}</a>`).join(", ") || "None";
const epssCount = (diff.new_high_epss || []).length;
const epssExamples = (diff.new_high_epss || []).slice(0, 5).map((row) => `<a href="/cve/?id=${row.cve}">${row.cve}</a>`).join(", ") || "None";
const moverCount = (diff.epss_movers || []).length;
const moverExamples = (diff.epss_movers || []).slice(0, 5).map((row) => `<a href="/cve/?id=${row.cve}">${row.cve}</a> (${row.delta.toFixed(3)})`).join(", ") || "None";
tbody.innerHTML = `
<tr>
<td>New KEV entries</td>
<td>${kevCount}</td>
<td>${kevExamples}</td>
</tr>
<tr>
<td>New high EPSS</td>
<td>${epssCount}</td>
<td>${epssExamples}</td>
</tr>
<tr>
<td>Top EPSS movers</td>
<td>${moverCount}</td>
<td>${moverExamples}</td>
</tr>
`;
}
async function loadHome() {
try {
const res = await fetch("/api/v1/joined_top.json", { cache: "no-store" });
if (res.ok) {
const data = await res.json();
renderCards("kev-grid", (data.kev_top || []).slice(0, 15));
renderCards("epss-grid", (data.high_epss || []).slice(0, 15));
}
} catch (err) {
console.warn("Failed to load joined_top.json", err);
}
try {
const res = await fetch("/api/v1/diff/latest.json", { cache: "no-store" });
if (res.ok) {
const diff = await res.json();
renderDiffTable(diff);
}
} catch (err) {
console.warn("Failed to load diff", err);
}
}
document.addEventListener("DOMContentLoaded", loadHome);
})();

View File

@@ -1,20 +0,0 @@
(function () {
function bindColumnFilters() {
const filterInputs = document.querySelectorAll("[data-filter-table]");
filterInputs.forEach((input) => {
const table = document.getElementById(input.dataset.filterTable);
if (!table) return;
input.addEventListener("input", () => {
const term = input.value.trim().toLowerCase();
for (const row of table.querySelectorAll("tbody tr")) {
const text = row.innerText.toLowerCase();
row.style.display = text.includes(term) ? "" : "none";
}
});
});
}
document.addEventListener("DOMContentLoaded", () => {
bindColumnFilters();
});
})();

View File

@@ -1,97 +0,0 @@
:root {
--bg: #05070d;
--panel: #0d1020;
--panel-2: #11162b;
--text: #f3f4ff;
--muted: #8fa2c8;
--accent: #7ef1d3;
--accent-2: #5bc0eb;
--warn: #ffb86c;
--success: #6ef2a6;
--border: #1f2742;
--shadow: 0 18px 45px rgba(0,0,0,0.35);
font-family: "Space Grotesk", "Inter", "Helvetica Neue", system-ui, sans-serif;
line-height: 1.55;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: radial-gradient(circle at 20% 20%, rgba(91,192,235,0.08), transparent 25%), radial-gradient(circle at 80% 0%, rgba(126,241,211,0.08), transparent 23%), var(--bg);
color: var(--text);
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code { background: rgba(255,255,255,0.04); padding: 2px 6px; border-radius: 6px; color: var(--accent-2); }
.wrap { width: min(1200px, 94vw); margin: 0 auto; padding: 1.5rem 0; }
.topbar { position: sticky; top: 0; z-index: 10; background: rgba(13,16,32,0.85); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); }
.topbar .wrap { display: flex; justify-content: space-between; align-items: center; padding: 1rem 0; }
.brand a { font-weight: 700; letter-spacing: 0.5px; color: var(--text); }
.brand .dot { color: var(--accent); margin-right: 4px; }
nav a { margin-left: 1rem; color: var(--muted); font-weight: 600; }
nav a:hover { color: var(--accent); }
h1, h2, h3, h4 { margin: 0 0 0.5rem; line-height: 1.25; }
p { margin: 0 0 0.75rem; }
.muted { color: var(--muted); }
.small { font-size: 0.9rem; }
.eyebrow { text-transform: uppercase; letter-spacing: 0.15em; font-size: 0.8rem; color: var(--accent); margin-bottom: 0.35rem; }
.lede { color: var(--muted); max-width: 60ch; }
.hero { display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: center; padding: 1rem 0 2rem; }
.hero-panel { background: var(--panel); border: 1px solid var(--border); border-radius: 16px; padding: 1rem; box-shadow: var(--shadow); display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }
.stat .label { color: var(--muted); font-size: 0.9rem; }
.stat .value { font-size: 1.9rem; font-weight: 700; }
.cta-row { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-top: 1rem; }
.btn { background: linear-gradient(90deg, var(--accent), var(--accent-2)); color: #041019; padding: 0.75rem 1rem; border-radius: 12px; font-weight: 700; border: none; display: inline-block; }
.btn.ghost { background: transparent; color: var(--text); border: 1px solid var(--border); }
.text-link { color: var(--accent); font-weight: 600; }
.section-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 0.75rem; }
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 14px; padding: 1rem; box-shadow: var(--shadow); }
.card-title { font-weight: 700; margin-bottom: 0.4rem; }
.meta-row { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; margin-bottom: 0.35rem; }
.pill { display: inline-flex; align-items: center; gap: 4px; padding: 0.25rem 0.6rem; border-radius: 999px; background: rgba(255,255,255,0.04); border: 1px solid var(--border); color: var(--text); font-size: 0.85rem; }
.pill.ghost { background: transparent; color: var(--muted); }
.pill.warn { border-color: var(--warn); color: var(--warn); }
.pill.tier-high { border-color: var(--success); color: var(--success); }
.pill.tier-medium { border-color: var(--accent-2); color: var(--accent-2); }
.pill.tier-low { border-color: var(--muted); color: var(--muted); }
.pill.tiny { font-size: 0.75rem; padding: 0.15rem 0.4rem; }
.input { width: 100%; padding: 0.75rem; border-radius: 12px; border: 1px solid var(--border); background: var(--panel-2); color: var(--text); margin: 0.5rem 0 1rem; }
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 14px; box-shadow: var(--shadow); background: var(--panel); }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 0.85rem 1rem; border-bottom: 1px solid var(--border); text-align: left; }
th { background: #0f1326; color: var(--muted); font-weight: 600; letter-spacing: 0.02em; }
tr:last-child td { border-bottom: none; }
.matches ul { list-style: none; padding: 0; margin: 0.35rem 0 0; }
.matches li { margin-bottom: 0.25rem; color: var(--muted); }
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
.list { list-style: none; padding: 0; margin: 0.35rem 0; }
.list li { padding: 0.4rem 0; border-bottom: 1px solid var(--border); }
.list li:last-child { border-bottom: none; }
.pill-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.8rem 0; }
.footer { border-top: 1px solid var(--border); margin-top: 2rem; }
.footer-inner { display: flex; flex-wrap: wrap; gap: 1rem; padding: 1rem 0; color: var(--muted); }
@media (max-width: 840px) {
.hero { grid-template-columns: 1fr; }
nav { display: none; }
}
@media (max-width: 620px) {
.card-grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
th, td { padding: 0.65rem; }
}

View File

@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CVE Details - CVE PoC Hub</title>
<link rel="stylesheet" href="/style.css" />
<script defer src="/assets/cve.js"></script>
</head>
<body>
<header class="site-header">
<div class="wrap">
<div class="brand"><a href="/">CVE PoC Hub</a></div>
<nav>
<a href="/search/">PoC Search</a>
<a href="/kev/">KEV</a>
<a href="/epss/">EPSS</a>
</nav>
</div>
</header>
<main class="wrap" id="cve-page">
<section class="hero">
<p class="eyebrow">CVE detail</p>
<h1 id="cve-title">Loading…</h1>
<p class="lede" id="cve-summary">Fetching CVE data.</p>
<div class="pill-row tight" id="cve-meta"></div>
</section>
<section class="section" id="cve-details" style="display:none;">
<h2>Overview</h2>
<div class="subtle-grid" id="cve-facts"></div>
<h2>PoC Links</h2>
<div id="cve-pocs" class="table-responsive">
<table class="list">
<thead><tr><th>Link</th></tr></thead>
<tbody id="cve-poc-rows"></tbody>
</table>
</div>
<div id="kev-section" style="display:none;">
<h2>KEV details</h2>
<div class="table-responsive">
<table class="list">
<tbody id="kev-rows"></tbody>
</table>
</div>
</div>
</section>
<section class="section" id="not-found" style="display:none;">
<h2>Not found</h2>
<p class="muted">We could not find this CVE in the API or CVE_list.json. Check the identifier and try again.</p>
</section>
</main>
<footer class="site-footer">
<div class="wrap">
<span>Fast CVE triage without the noise.</span>
<span><a href="https://github.com/0xMarcio/cve">GitHub repo</a></span>
</div>
</footer>
</body>
</html>

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CVE PoC Hub</title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/style.css" />
<script defer src="/assets/site.js"></script>
</head>
<body class="">
<header class="site-header">
<div class="wrap">
<div class="brand"><a href="/">CVE PoC Hub</a></div>
<nav>
<a href="/search/">PoC Search</a>
<a href="/kev/">KEV</a>
<a href="/epss/">EPSS</a>
</nav>
</div>
</header>
<main class="wrap">
<section class="section">
<div class="section-header">
<h1>New KEV entries</h1>
<span class="muted">Only the recent additions</span>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>CVE</th><th>Vendor</th><th>Product</th><th>EPSS</th><th>Percentile</th><th>Date Added</th><th>Due</th></tr></thead>
<tbody>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-6218">CVE-2025-6218</a></td>
<td>RARLAB</td>
<td>WinRAR</td>
<td>0.000</td>
<td> 0th</td>
<td>2025-12-09</td>
<td>2025-12-30</td>
</tr>
</tbody>
</table>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap">
<span>Fast CVE triage without the noise.</span>
<span><a href="https://github.com/0xMarcio/cve">GitHub repo</a></span>
</div>
</footer>
</body>
</html>

View File

@@ -1,87 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CVE PoC Hub</title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/style.css" />
<script defer src="/assets/site.js"></script>
</head>
<body class="">
<header class="site-header">
<div class="wrap">
<div class="brand"><a href="/">CVE PoC Hub</a></div>
<nav>
<a href="/search/">PoC Search</a>
<a href="/kev/">KEV</a>
<a href="/epss/">EPSS</a>
</nav>
</div>
</header>
<main class="wrap">
<section class="section">
<div class="section-header">
<h1>EPSS highlights</h1>
<span class="muted">High-probability CVEs that are not in KEV.</span>
</div>
<input type="search" placeholder="Filter CVE" data-filter-table="epss-table" class="filter" />
<div class="table-responsive">
<table class="list" id="epss-table">
<thead><tr><th>CVE</th><th>EPSS</th><th>Percentile</th><th>PoCs</th><th>Summary</th></tr></thead>
<tbody>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8943">CVE-2025-8943</a></td>
<td>0.658</td>
<td>98th</td>
<td>1</td>
<td class="mono">The Custom MCPs feature is designed to execute OS commands, for instance, using tools like `npx` to spin up local MCP Servers. However, Flowise&#39;s inherent authentication and authorization model is minimal and lacks ro...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8518">CVE-2025-8518</a></td>
<td>0.339</td>
<td>97th</td>
<td>1</td>
<td class="mono">A vulnerability was found in givanz Vvveb 1.0.5. It has been rated as critical. Affected by this issue is the function Save of the file admin/controller/editor/code.php of the component Code Editor. The manipulation l...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8730">CVE-2025-8730</a></td>
<td>0.119</td>
<td>93th</td>
<td>2</td>
<td class="mono">A vulnerability was found in Belkin F9K1009 and F9K1010 2.00.04/2.00.09 and classified as critical. Affected by this issue is some unknown functionality of the component Web Interface. The manipulation leads to hard-c...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-7795">CVE-2025-7795</a></td>
<td>0.096</td>
<td>93th</td>
<td>3</td>
<td class="mono">A vulnerability, which was classified as critical, has been found in Tenda FH451 1.0.0.9. Affected by this issue is the function fromP2pListFilter of the file /goform/P2pListFilter. The manipulation of the argument pa...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-9090">CVE-2025-9090</a></td>
<td>0.092</td>
<td>92th</td>
<td>4</td>
<td class="mono">A vulnerability was identified in Tenda AC20 16.03.08.12. Affected is the function websFormDefine of the file /goform/telnet of the component Telnet Service. The manipulation leads to command injection. It is possible...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8085">CVE-2025-8085</a></td>
<td>0.078</td>
<td>92th</td>
<td>1</td>
<td class="mono">The Ditty WordPress plugin before 3.1.58 lacks authorization and authentication for requests to its displayItems endpoint, allowing unauthenticated visitors to make requests to arbitrary URLs.</td>
</tr>
</tbody>
</table>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap">
<span>Fast CVE triage without the noise.</span>
<span><a href="https://github.com/0xMarcio/cve">GitHub repo</a></span>
</div>
</footer>
</body>
</html>

View File

@@ -6,249 +6,52 @@
<title>CVE PoC Hub</title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/style.css" />
<script defer src="/assets/site.js"></script>
</head>
<body class="color-no-search">
<header class="site-header">
<div class="wrap">
<div class="brand"><a href="/">CVE PoC Hub</a></div>
<nav>
<a href="/search/">PoC Search</a>
<a href="/kev/">KEV</a>
<a href="/epss/">EPSS</a>
</nav>
</div>
</header>
<main class="wrap">
<section class="hero hero-signal" data-search-root>
<div class="hero-meta">
<h1>CVE PoC Hub</h1>
<p class="lede">Search PoCs, KEV, and EPSS quickly—no filler.</p>
</div>
<form class="searchForm" action="#">
<input type="text" class="search" placeholder="Search CVE, vendor, product, or keyword" autocomplete="off">
</form>
<div class="stat-row">
<div class="stat"><strong>264</strong><span>KEV entries tracked</span></div>
<div class="stat"><strong>6</strong><span>High-EPSS not in KEV</span></div>
<div class="stat"><strong>1</strong><span>New KEV in last 30 days</span></div>
</div>
<div class="search-results" data-results style="display:none">
<div class="header">
<h2>Results</h2>
<span class="muted">Filter with negative terms (e.g., -windows)</span>
</div>
<div class="noResults">No results yet.</div>
<div class="results-table hide">
<table class="results">
<thead>
<tr>
<td width="18%">CVE</td>
<td>Description / PoC links</td>
</tr>
</thead>
<tbody class="results"></tbody>
</table>
</div>
</div>
</section>
<section class="hero" data-search-root>
<h1>Find exploit writeups and PoCs instantly</h1>
<p class="lede">Search by CVE id, vendor, product, or keyword.</p>
<div class="pill-row tight">
<span class="pill">Linked PoCs</span>
<span class="pill">MITRE CVE pages</span>
<span class="pill">Vendor / product filters</span>
<span class="pill">Supports negative terms (e.g. <strong>-windows</strong>)</span>
</div>
<form class="searchForm" action="#">
<input type="text" class="search" placeholder="Search CVE, vendor, product, or keyword" autocomplete="off">
</form>
<div class="search-results" data-results style="display:none">
<div class="header">
<h2>Results</h2>
</div>
<div class="noResults">No results yet.</div>
<div class="results-table hide">
<table class="results">
<thead>
<tr>
<td width="18%">CVE</td>
<td>Description / PoC links</td>
</tr>
</thead>
<tbody class="results"></tbody>
</table>
</div>
</div>
</section>
<section class="section">
<div class="section-header">
<h1>Trending PoCs</h1>
<span class="muted">Current year, updated in the last 4 days</span>
</div>
<div class="table-wrap" data-trending>
<table>
<thead><tr><th>Stars</th><th>Updated</th><th>Name</th><th>Description</th></tr></thead>
<tbody id="trending-body">
<tr>
<td>1</td>
<td>55 minutes ago</td>
<td><a href="https://github.com/siddu7575/CVE-2025-61882-CVE-2025-61884" target="_blank">CVE-2025-61882-CVE-2025-61884</a></td>
<td class="mono">🔍 Detect vulnerabilities CVE-2025-61882 and CVE-2025-61884 in Oracle E-Business Suite to help secure your systems from potential remote code execution threats.</td>
</tr>
<tr>
<td>1</td>
<td>1 hour ago</td>
<td><a href="https://github.com/jm7knz/CVE-2025-54253-Exploit-Demo" target="_blank">CVE-2025-54253-Exploit-Demo</a></td>
<td class="mono">🐙 CVE-2025-54253 exploit demo for Adobe AEM Forms on JEE: OGNL injection to RCE with PoC, Python 3.10 exploit code, reproducer and mitigation guidance.</td>
</tr>
<tr>
<td>1</td>
<td>1 hour ago</td>
<td><a href="https://github.com/hophtien/CVE-2025-54424" target="_blank">CVE-2025-54424</a></td>
<td class="mono">CVE-2025-54424: 1Panel TLS client cert bypass enables RCE via forged CN &#39;panel_client&#39; using a bundled scanning and exploitation tool. Affected: &lt;= v2.0.5. 🔐</td>
</tr>
<tr>
<td>360</td>
<td>2 hours ago</td>
<td><a href="https://github.com/Malayke/Next.js-RSC-RCE-Scanner-CVE-2025-66478" target="_blank">Next.js-RSC-RCE-Scanner-CVE-2025-66478</a></td>
<td class="mono">A command-line scanner for batch detection of Next.js application versions and determining if they are affected by CVE-2025-66478 vulnerability.</td>
</tr>
<tr>
<td>1</td>
<td>2 hours ago</td>
<td><a href="https://github.com/ThemeHackers/CVE-2025-13780" target="_blank">CVE-2025-13780</a></td>
<td class="mono">A comprehensive vulnerability scanner for CVE-2025-13780, a Remote Code Execution (RCE) vulnerability in pgAdmin 4 versions ≤ 8.14.</td>
</tr>
<tr>
<td>2</td>
<td>10 hours ago</td>
<td><a href="https://github.com/Chrxstxqn/CVE-2025-6218-WinRAR-RCE-POC" target="_blank">CVE-2025-6218-WinRAR-RCE-POC</a></td>
<td class="mono">Comprehensive analysis and proof-of-concept for CVE-2025-6218 - WinRAR path traversal RCE vulnerability affecting versions 7.11 and earlier</td>
</tr>
<tr>
<td>1</td>
<td>11 hours ago</td>
<td><a href="https://github.com/M4rgs/CVE-2025-55182-React2Shell-Exploit" target="_blank">CVE-2025-55182-React2Shell-Exploit</a></td>
<td class="mono">A proof-of-concept tool for demonstrating the critical React2Shell vulnerability</td>
</tr>
<tr>
<td>4</td>
<td>13 hours ago</td>
<td><a href="https://github.com/wangxso/CVE-2025-66478-POC" target="_blank">CVE-2025-66478-POC</a></td>
<td class="mono">CVE-2025-66478 Proof of Concept</td>
</tr>
<tr>
<td>4</td>
<td>22 hours ago</td>
<td><a href="https://github.com/bbaboha/CVE-2025-65318-and-CVE-2025-65319" target="_blank">CVE-2025-65318-and-CVE-2025-65319</a></td>
<td class="mono">Insecure attachment handling when using Canary Mail or Blue mail</td>
</tr>
<tr>
<td>78</td>
<td>1 day ago</td>
<td><a href="https://github.com/Ashwesker/Blackash-CVE-2025-55182" target="_blank">Blackash-CVE-2025-55182</a></td>
<td class="mono">CVE-2025-55182</td>
</tr>
<tr>
<td>17</td>
<td>1 day ago</td>
<td><a href="https://github.com/ThemeHackers/CVE-2025-55182" target="_blank">CVE-2025-55182</a></td>
<td class="mono">a critical Remote Code Execution (RCE) vulnerability in React Server Components (RSC). It also includes a realistic &#34;Lab Environment&#34; to safely test and understand the vulnerability.</td>
</tr>
<tr>
<td>3</td>
<td>1 day ago</td>
<td><a href="https://github.com/ThemeHackers/CVE-2025-54100" target="_blank">CVE-2025-54100</a></td>
<td class="mono">CVE-2025-54100 (CVSS 7.8 High) is a command injection vulnerability in the Invoke-WebRequest cmdlet of Windows PowerShell 5.1. It arises from improper neutralization of special elements during the automatic parsing of Web responses.</td>
</tr>
<tr>
<td>2</td>
<td>1 day ago</td>
<td><a href="https://github.com/itres-labs/CVE-2025-31702" target="_blank">CVE-2025-31702</a></td>
<td class="mono">Repository with tools, exploits, and material associated with the analysis and discovery process of CVE-2025-31702 and other related security issues.</td>
</tr>
<tr>
<td>1</td>
<td>1 day ago</td>
<td><a href="https://github.com/LucasPDiniz/CVE-2025-55182" target="_blank">CVE-2025-55182</a></td>
<td class="mono">React2Shell Vulnerability</td>
</tr>
<tr>
<td>1</td>
<td>1 day ago</td>
<td><a href="https://github.com/Ashwesker/Blackash-CVE-2025-13780" target="_blank">Blackash-CVE-2025-13780</a></td>
<td class="mono">CVE-2025-13780</td>
</tr>
<tr>
<td>2</td>
<td>2 days ago</td>
<td><a href="https://github.com/subhdotsol/CVE-2025-55182" target="_blank">CVE-2025-55182</a></td>
<td class="mono">This project provides a fully functional demonstration of CVE-2025-55182 (React2Shell) - a critical Remote Code Execution vulnerability in React Server Components and Next.js.</td>
</tr>
<tr>
<td>1</td>
<td>2 days ago</td>
<td><a href="https://github.com/Security-Phoenix-demo/react2shell-scanner-CVE-2025-55182" target="_blank">react2shell-scanner-CVE-2025-55182</a></td>
<td class="mono">React2shell-web-scanner</td>
</tr>
<tr>
<td>1</td>
<td>2 days ago</td>
<td><a href="https://github.com/l0n3m4n/CVE-2025-55182-Waf" target="_blank">CVE-2025-55182-Waf</a></td>
<td class="mono">CVE-2025-55182 RCE vulnerability in Next.js/React RSC servers (exploit and scanner)</td>
</tr>
<tr>
<td>1</td>
<td>2 days ago</td>
<td><a href="https://github.com/rix4uni/CVE-2025-55182" target="_blank">CVE-2025-55182</a></td>
<td class="mono">A command-line tool for detecting CVE-2025-55182 and CVE-2025-66478 in Next.js applications using React Server Components.</td>
</tr>
<tr>
<td>1</td>
<td>2 days ago</td>
<td><a href="https://github.com/fsoc-ghost-0x/CVE-2025-9074_DAEMON_KILLER" target="_blank">CVE-2025-9074_DAEMON_KILLER</a></td>
<td class="mono">The Ultimate DAEMON_KILLER. Control is an illusion. This Exploit forces CVE-2025-9074 to break the Docker cage. Advanced Container Escape &amp; Root Escalation toolkit. Verify the vulnerability, take the host, destroy the logs. &gt; We Are Fsociety_</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="section">
<div class="section-header">
<h1>High EPSS not in KEV</h1>
<span class="muted">Sorted by score</span>
</div>
<div class="table-wrap">
<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>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8943">CVE-2025-8943</a></td>
<td>0.658</td>
<td>98th</td>
<td>1</td>
<td class="mono">The Custom MCPs feature is designed to execute OS commands, for instance, using tools like `npx` to spin up local MCP Servers. However, Flowise&#39;s inherent authentication and authorization model is minimal and lacks ro...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8518">CVE-2025-8518</a></td>
<td>0.339</td>
<td>97th</td>
<td>1</td>
<td class="mono">A vulnerability was found in givanz Vvveb 1.0.5. It has been rated as critical. Affected by this issue is the function Save of the file admin/controller/editor/code.php of the component Code Editor. The manipulation l...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8730">CVE-2025-8730</a></td>
<td>0.119</td>
<td>93th</td>
<td>2</td>
<td class="mono">A vulnerability was found in Belkin F9K1009 and F9K1010 2.00.04/2.00.09 and classified as critical. Affected by this issue is some unknown functionality of the component Web Interface. The manipulation leads to hard-c...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-7795">CVE-2025-7795</a></td>
<td>0.096</td>
<td>93th</td>
<td>3</td>
<td class="mono">A vulnerability, which was classified as critical, has been found in Tenda FH451 1.0.0.9. Affected by this issue is the function fromP2pListFilter of the file /goform/P2pListFilter. The manipulation of the argument pa...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-9090">CVE-2025-9090</a></td>
<td>0.092</td>
<td>92th</td>
<td>4</td>
<td class="mono">A vulnerability was identified in Tenda AC20 16.03.08.12. Affected is the function websFormDefine of the file /goform/telnet of the component Telnet Service. The manipulation leads to command injection. It is possible...</td>
</tr>
<tr>
<td class="cve-cell"><a href="/cve/?id=CVE-2025-8085">CVE-2025-8085</a></td>
<td>0.078</td>
<td>92th</td>
<td>1</td>
<td class="mono">The Ditty WordPress plugin before 3.1.58 lacks authorization and authentication for requests to its displayItems endpoint, allowing unauthenticated visitors to make requests to arbitrary URLs.</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="section" data-trending-section>
<h2>Latest PoC examples</h2>
<div class="table-wrap" data-trending>
<table>
<thead><tr><th>Stars</th><th>Updated</th><th>Name</th><th>Description</th></tr></thead>
<tbody id="trending-body">
<tr><td colspan="4">No recent PoCs.</td></tr>
</tbody>
</table>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap">
<span>Fast CVE triage without the noise.</span>
<span><a href="https://github.com/0xMarcio/cve">GitHub repo</a></span>
</div>
</footer>
<script src="/logic.js"></script>
<script src="/logic.js"></script>
</body>
</html>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,14 @@ function getSearchRoot() {
return document.querySelector('[data-search-root]');
}
function getTrendingSection() {
return document.querySelector('[data-trending-section]');
}
function getTrendingBody() {
return document.getElementById('trending-body');
}
function escapeHTML(str) {
return str.replace(/[&<>"']/g, match => ({
'&': '&amp;',
@@ -51,7 +59,7 @@ function toggleDropdown(button) {
window.toggleDropdown = toggleDropdown;
function getCveLink(cveId) {
return `<a href="/cve/?id=${cveId}"><b>${cveId}</b></a>`;
return `<a href="https://nvd.nist.gov/vuln/detail/${cveId}" target="_blank"><b>${cveId}</b></a>`;
}
function prepareDataset(raw) {
@@ -141,6 +149,8 @@ window.controls = controls;
document.addEventListener('DOMContentLoaded', () => {
const root = getSearchRoot();
const trendingSection = getTrendingSection();
const trendingBody = getTrendingBody();
if (!root) return;
const results = root.querySelector('[data-results]');
@@ -160,11 +170,46 @@ document.addEventListener('DOMContentLoaded', () => {
let currentSet = [];
let debounceTimer;
function renderTrending(items) {
if (!trendingBody) return;
if (!items || items.length === 0) {
trendingBody.innerHTML = '<tr><td colspan="4">No recent PoCs.</td></tr>';
return;
}
const rows = items.slice(0, 20).map(item => {
const stars = item.stars ?? '';
const updated = escapeHTML(item.updated || '');
const name = escapeHTML(item.name || '');
const url = item.url || '#';
const desc = escapeHTML(item.desc || '');
return `<tr><td>${stars}</td><td>${updated}</td><td><a href="${url}" target="_blank">${name}</a></td><td class="mono">${desc}</td></tr>`;
}).join('');
trendingBody.innerHTML = rows;
}
async function loadTrending() {
if (!trendingBody) return;
try {
const res = await fetch('/trending_poc.json', { cache: 'no-store' });
if (!res.ok) {
throw new Error(`Failed to load trending (${res.status})`);
}
const data = await res.json();
const items = Array.isArray(data) ? data : (data.items || []);
renderTrending(items);
} catch (err) {
console.warn(err.message);
}
}
function doSearch(event) {
const val = searchValue.value.trim();
if (val !== '') {
controls.displayResults(results, resultsTableHideable);
if (trendingSection) {
trendingSection.style.display = 'none';
}
currentSet = window.controls.doSearch(val, window.dataset || []);
if (currentSet.length < totalLimit) {
@@ -176,6 +221,9 @@ document.addEventListener('DOMContentLoaded', () => {
controls.hideResults(results, resultsTableHideable);
window.controls.setColor(colorUpdate, 'no-search');
noResults.style.display = 'none';
if (trendingSection) {
trendingSection.style.display = '';
}
}
if (event.type === 'submit') {
@@ -219,4 +267,6 @@ document.addEventListener('DOMContentLoaded', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => doSearch(event), 200);
});
loadTrending();
});

View File

@@ -1,66 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Search utility for CVE PoCs">
<link rel="stylesheet" href="/style.css">
<title>CVE PoC Hub - Search</title>
</head>
<body class="color-no-search">
<header class="site-header">
<div class="wrap">
<div class="brand"><a href="/">CVE PoC Hub</a></div>
<nav>
<a href="/search/">PoC Search</a>
<a href="/kev/">KEV</a>
<a href="/epss/">EPSS</a>
</nav>
</div>
</header>
<main class="wrap">
<section class="hero" data-search-root>
<h1>Find exploit writeups and PoCs instantly</h1>
<p class="lede">Search by CVE id, vendor, product, or keyword.</p>
<div class="pill-row tight">
<span class="pill">Linked PoCs</span>
<span class="pill">MITRE CVE pages</span>
<span class="pill">Vendor / product filters</span>
<span class="pill">Supports negative terms (e.g. <strong>-windows</strong>)</span>
</div>
<form class="searchForm" action="#">
<input type="text" class="search" placeholder="Search CVE, vendor, product, or keyword" autocomplete="off">
</form>
<div class="search-results" data-results style="display:none">
<div class="header">
<h2>Results</h2>
<span class="muted">Up to 10k rows</span>
</div>
<div class="noResults">No results yet.</div>
<div class="results-table hide">
<table class="results">
<thead>
<tr>
<td width="18%">
CVE
</td>
<td>
Description / PoC links
</td>
</tr>
</thead>
<tbody class="results"></tbody>
</table>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap">
<span>© 0xMarcio 2025</span>
<span>Found a bug? <a href="https://github.com/0xMarcio/cve/issues" target="_blank">File it on GitHub</a></span>
</div>
</footer>
<script src="/logic.js"></script>
</body>
</html>

4
docs/trending_poc.json Normal file
View File

@@ -0,0 +1,4 @@
{
"generated": "2025-12-18T00:00:00Z",
"items": []
}

View File

@@ -7,7 +7,7 @@ pip install -r requirements.txt
python scripts/fetch_kev.py
python scripts/fetch_epss.py
python scripts/build_site.py
python scripts/build_all.py # new PoC discovery + scoring pipeline
python scripts/build_all.py
```
Outputs land in `docs/` and JSON under `docs/api/v1/`. Snapshots live in `docs/api/v1/snapshots/` (last 14 days) and diffs under `docs/api/v1/diffs/`.

View File

@@ -19,7 +19,6 @@ from pipeline_outputs import (
write_top,
)
from poc_pipeline import PoCPipeline, build_scope, persist_evidence
from site_renderer import SiteRenderer
from utils import API_DIR, DOCS_DIR, load_json
@@ -106,10 +105,7 @@ def main(argv: List[str] | None = None) -> int:
prune_old_snapshots()
prune_old_diffs()
renderer = SiteRenderer(results=results, index_payload=index_payload, top_payload=top_payload, diff_payload=diff_payload)
renderer.build()
print(f"Generated site under {DOCS_DIR}")
print(f"Wrote pipeline outputs under {DOCS_DIR}")
print(f"Wrote latest snapshot to {snapshot_path}")
return 0

View File

@@ -1,31 +1,22 @@
from __future__ import annotations
import argparse
from datetime import timedelta
from pathlib import Path
import json
import re
from typing import Dict, Tuple
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional
from urllib.parse import urlparse
from jinja2 import Environment, FileSystemLoader, select_autoescape
from utils import (
API_DIR,
DOCS_DIR,
TEMPLATES_DIR,
ensure_dirs,
load_json,
load_poc_index,
parse_trending_from_readme,
save_json,
)
from utils import DOCS_DIR, TEMPLATES_DIR, ensure_dirs, load_blacklist, parse_trending_from_readme, is_blacklisted_repo
from build_joined import build_joined, write_api_outputs
from build_diffs import build_diff, prune_snapshots
KEV_DATA = DOCS_DIR.parent / "data" / "kev.json"
EPSS_DATA = DOCS_DIR.parent / "data" / "epss.json"
README_PATH = DOCS_DIR.parent / "README.md"
TRENDING_WINDOW = timedelta(days=4)
ROOT = DOCS_DIR.parent
README_PATH = ROOT / "README.md"
CVE_OUTPUT = DOCS_DIR / "CVE_list.json"
REMOVED_OUTPUT = DOCS_DIR / "CVE_blacklist_removed.json"
TRENDING_OUTPUT = DOCS_DIR / "trending_poc.json"
def build_env() -> Environment:
@@ -42,160 +33,200 @@ def render(env: Environment, template_name: str, context: Dict, output_path: Pat
output_path.write_text(html, encoding="utf-8")
def load_joined() -> Dict:
kev = load_json(KEV_DATA, default={})
epss = load_json(EPSS_DATA, default={})
poc_index = load_poc_index()
payload = build_joined(kev, epss, poc_index)
write_api_outputs(payload)
return payload
def normalise_block(text: str) -> str:
text = text.replace("\r\n", "\n")
text = re.sub(r"\n{2,}", "\n", text.strip())
lines = [line.lstrip("- ").rstrip() for line in text.split("\n")]
return "\n".join(line for line in lines if line)
def write_snapshot(joined: Dict) -> Path:
snapshot_path = API_DIR / "snapshots" / f"{joined['generated']}.json"
ensure_dirs(snapshot_path.parent)
save_json(snapshot_path, joined)
return snapshot_path
def parse_sections(content: str) -> Dict[str, str]:
sections: Dict[str, str] = {}
current_header: Optional[str] = None
buffer: List[str] = []
for line in content.splitlines():
header = line.strip()
if header.startswith("### ") or header.startswith("#### "):
if current_header is not None:
sections[current_header] = "\n".join(buffer).strip()
current_header = header
buffer = []
else:
buffer.append(line)
if current_header is not None:
sections[current_header] = "\n".join(buffer).strip()
return sections
def _parse_year(row: dict) -> int | None:
def repo_from_url(url: str) -> str:
try:
return int(row.get("year"))
except (TypeError, ValueError):
return None
parsed = urlparse(url)
host = (parsed.netloc or "").lower()
if host and "github" not in host:
return ""
path = parsed.path or url
except Exception:
path = url
parts = path.strip("/").split("/")
if len(parts) >= 2:
return parts[1].lower()
return (parts[-1] if parts else "").lower()
def _age_from_label(label: str) -> timedelta | None:
text = (label or "").strip().lower()
if text == "just now":
return timedelta()
match = re.match(r"(?P<value>\d+)\s+(?P<unit>minute|minutes|hour|hours|day|days)\s+ago", text)
if not match:
return None
value = int(match.group("value"))
unit = match.group("unit")
if unit.startswith("minute"):
return timedelta(minutes=value)
if unit.startswith("hour"):
return timedelta(hours=value)
return timedelta(days=value)
def _is_current_year_name(name: str, year: int) -> bool:
return bool(re.search(rf"cve-{year}-\d+", name or "", re.IGNORECASE))
def select_trending(readme_rows: list[dict]) -> list[dict]:
"""Pick up to 20 entries from the newest year table, filtered to last 4 days, with descriptions, matching the current year."""
if not readme_rows:
return []
years = [yr for yr in (_parse_year(row) for row in readme_rows) if yr is not None]
if not years:
return []
latest_year = max(years)
filtered: list[tuple[dict, timedelta]] = []
for row in readme_rows:
if _parse_year(row) != latest_year:
def is_blacklisted(url: str, blacklist: List[str]) -> bool:
repo = repo_from_url(url)
if not repo:
return False
for entry in blacklist:
slug = entry.lower()
if not slug:
continue
if not _is_current_year_name(row.get("name", ""), latest_year):
if slug.endswith("*"):
if repo.startswith(slug[:-1]):
return True
elif repo == slug:
return True
return False
def collect_links(block: str, *, blacklist: Optional[List[str]] = None, removed: Optional[List[str]] = None) -> List[str]:
links: List[str] = []
blacklist = blacklist or []
if removed is None:
removed = []
for raw in block.splitlines():
entry = raw.strip()
if not entry or "No PoCs" in entry:
continue
if not (row.get("desc") or "").strip():
if entry.startswith("- "):
entry = entry[2:].strip()
if not entry:
continue
age = _age_from_label(row.get("updated", ""))
if age is None or age > TRENDING_WINDOW:
if is_blacklisted(entry, blacklist):
removed.append(entry)
continue
filtered.append((row, age))
# Sort by freshness then stars
filtered.sort(key=lambda pair: (pair[1], -int(pair[0].get("stars") or 0)))
selected: list[dict] = []
for row, _age in filtered[:20]:
try:
stars = int(row.get("stars") or 0)
except (TypeError, ValueError):
stars = 0
selected.append(
{
"stars": stars,
"updated": (row.get("updated") or "").strip(),
"name": (row.get("name") or "").strip(),
"url": (row.get("url") or "").strip(),
"desc": (row.get("desc") or "").strip(),
"year": latest_year,
}
)
return selected
if entry not in links:
links.append(entry)
return links
def build_pages(env: Environment, data: Dict, diff: Dict | None = None, html_mode: str = "summary") -> None:
joined = data["joined"]
details = data["details"]
vendors = data["vendors"]
def build_cve_list(blacklist: List[str]) -> Dict[str, object]:
cve_entries = []
removed_by_cve: Dict[str, List[str]] = {}
removed_seen: set[str] = set()
trending_raw = parse_trending_from_readme(README_PATH)
trending = select_trending(trending_raw)
recent_kev = (diff or {}).get("new_kev_entries") or []
metrics = {
"kev_total": len(data["kev_enriched"]),
"high_epss_count": len(joined["high_epss"]),
"recent_kev_count": len(recent_kev),
for md_path in sorted(ROOT.glob("[12][0-9][0-9][0-9]/CVE-*.md")):
content = md_path.read_text(encoding="utf-8")
sections = parse_sections(content)
description = normalise_block(sections.get("### Description", ""))
removed_links: List[str] = []
references = collect_links(sections.get("#### Reference", ""), blacklist=blacklist, removed=removed_links)
github_links = collect_links(sections.get("#### Github", ""), blacklist=blacklist, removed=removed_links)
poc_entries: List[str] = []
seen = set()
for link in references + github_links:
if link not in seen:
poc_entries.append(link)
seen.add(link)
cve_id = md_path.stem
if removed_links:
removed_by_cve[cve_id] = sorted(set(removed_links))
removed_seen.update(removed_links)
if not poc_entries:
continue
cve_entries.append({
"cve": cve_id,
"desc": description,
"poc": poc_entries,
})
return {
"entries": cve_entries,
"removed": {
"removed": sorted(removed_seen),
"by_cve": removed_by_cve,
},
}
if html_mode in {"summary", "all"}:
common_ctx = {"generated": joined["generated"], "metrics": metrics, "recent_kev": recent_kev}
render(
env,
"index.html",
{**common_ctx, "data": joined, "trending": trending, "diff": diff or {}},
DOCS_DIR / "index.html",
)
render(env, "kev.html", {**common_ctx, "kev": data["kev_enriched"]}, DOCS_DIR / "kev" / "index.html")
render(env, "epss.html", {**common_ctx, "epss": joined["high_epss"]}, DOCS_DIR / "epss" / "index.html")
render(env, "diffs.html", {**common_ctx, "diff": diff or {}}, DOCS_DIR / "diffs" / "index.html")
if html_mode == "all":
common_ctx = {"generated": joined["generated"]}
for cve, detail in details.items():
render(env, "cve.html", {**common_ctx, "cve": detail}, DOCS_DIR / "cve" / f"{cve}.html")
def build_trending(blacklist: List[str]) -> List[Dict[str, object]]:
rows = parse_trending_from_readme(README_PATH)
if not rows:
return []
for slug, vendor in vendors.items():
cve_details = [details[cve] for cve in vendor["cves"] if cve in details]
render(env, "vendor.html", {**common_ctx, "vendor": vendor, "cves": cve_details}, DOCS_DIR / "vendors" / f"{slug}.html")
by_year: Dict[int, List[Dict[str, object]]] = {}
for row in rows:
year_text = row.get("year") or ""
if not str(year_text).isdigit():
continue
year = int(year_text)
url = (row.get("url") or "").strip()
if url and is_blacklisted_repo(url, blacklist):
continue
stars_text = str(row.get("stars") or "").strip()
stars = int(re.sub(r"\D", "", stars_text) or 0)
item = {
"year": year,
"stars": stars,
"updated": (row.get("updated") or "").strip(),
"name": (row.get("name") or "").strip(),
"url": url,
"desc": (row.get("desc") or "").strip(),
}
by_year.setdefault(year, []).append(item)
if not by_year:
return []
current_year = datetime.now(timezone.utc).year
target_year = current_year if current_year in by_year else max(by_year)
return by_year.get(target_year, [])
def write_json(path: Path, data, *, indent: Optional[int] = None) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as handle:
json.dump(data, handle, ensure_ascii=False, indent=indent)
def main() -> int:
parser = argparse.ArgumentParser(description="Build static site and JSON")
parser = argparse.ArgumentParser(description="Build CVE PoC site")
parser.add_argument(
"--html-mode",
choices=["none", "summary", "all"],
default="none",
help="Render no HTML, summary pages only, or all pages including per-CVE.",
default="summary",
help="Render HTML or skip it.",
)
args = parser.parse_args()
ensure_dirs(DOCS_DIR, DOCS_DIR / "kev", DOCS_DIR / "epss", DOCS_DIR / "diffs")
ensure_dirs(DOCS_DIR)
blacklist = load_blacklist()
data = load_joined()
# snapshot + diff before rendering so dashboard can show it
snapshot_path = write_snapshot(data["joined"])
snapshots = sorted((API_DIR / "snapshots").glob("*.json"))
diff, target = build_diff(
snapshots,
kev_full=data["kev_enriched"],
threshold=0.05,
max_movers=50,
recent_days=30,
cve_payload = build_cve_list(blacklist)
write_json(CVE_OUTPUT, cve_payload["entries"])
write_json(REMOVED_OUTPUT, cve_payload["removed"], indent=2)
trending_items = build_trending(blacklist)
write_json(
TRENDING_OUTPUT,
{
"generated": datetime.now(timezone.utc).isoformat(),
"items": trending_items,
},
indent=2,
)
prune_snapshots(snapshots, lookback_days=14)
if args.html_mode != "none":
env = build_env()
build_pages(env, data, diff, html_mode=args.html_mode)
render(env, "index.html", {"trending": trending_items}, DOCS_DIR / "index.html")
# build daily diff after snapshot is written
print("Site generated under docs/")
return 0

View File

@@ -1,99 +0,0 @@
from __future__ import annotations
from pathlib import Path
from typing import Dict, List
from jinja2 import Environment, FileSystemLoader, select_autoescape
from utils import DOCS_DIR, TEMPLATES_DIR, ensure_dirs
def build_env() -> Environment:
loader = FileSystemLoader(str(TEMPLATES_DIR))
env = Environment(loader=loader, autoescape=select_autoescape(["html", "xml"]))
env.trim_blocks = True
env.lstrip_blocks = True
return env
class SiteRenderer:
def __init__(
self,
*,
results: List[Dict],
index_payload: Dict,
top_payload: Dict,
diff_payload: Dict | None = None,
) -> None:
self.results = []
for result in results:
visible = [p for p in result.get("pocs", []) if p.get("confidence_tier") in {"high", "medium"}]
if not visible:
visible = result.get("pocs", [])
self.results.append({**result, "visible_pocs": visible})
self.index_payload = index_payload
self.top_payload = top_payload
self.diff_payload = diff_payload or {}
self.env = build_env()
ensure_dirs(
DOCS_DIR,
DOCS_DIR / "pocs",
DOCS_DIR / "cve",
DOCS_DIR / "diffs",
DOCS_DIR / "assets",
)
def render(self, template_name: str, context: Dict, target: Path) -> None:
html = self.env.get_template(template_name).render(**context)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(html, encoding="utf-8")
def build(self) -> None:
generated = self.index_payload.get("generated")
summary = {
"generated": generated,
"total_cves": len(self.index_payload.get("items", [])),
"total_pocs": sum(item.get("poc_count", 0) for item in self.index_payload.get("items", [])),
"high_total": sum(item.get("high_confidence", 0) for item in self.index_payload.get("items", [])),
"medium_total": sum(item.get("medium_confidence", 0) for item in self.index_payload.get("items", [])),
}
self.render(
"pipeline_index.html",
{
"summary": summary,
"top": self.top_payload.get("items", [])[:25],
"diff": self.diff_payload or {},
},
DOCS_DIR / "index.html",
)
self.render(
"pipeline_pocs.html",
{
"generated": generated,
"index": self.index_payload.get("items", []),
"top": self.top_payload.get("items", [])[:100],
},
DOCS_DIR / "pocs" / "index.html",
)
for result in self.results:
self.render(
"pipeline_cve.html",
{"cve": result, "generated": generated},
DOCS_DIR / "cve" / f"{result['cve_id']}.html",
)
if self.diff_payload:
diff_date = self.diff_payload.get("generated")
self.render(
"pipeline_diff.html",
{"diff": self.diff_payload, "generated": generated},
DOCS_DIR / "diffs" / "index.html",
)
if diff_date:
self.render(
"pipeline_diff.html",
{"diff": self.diff_payload, "generated": generated},
DOCS_DIR / "diffs" / f"{diff_date}.html",
)

View File

@@ -6,28 +6,11 @@
<title>{{ title or 'CVE PoC Hub' }}</title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/style.css" />
<script defer src="/assets/site.js"></script>
</head>
<body class="{{ body_class or '' }}">
<header class="site-header">
<div class="wrap">
<div class="brand"><a href="/">CVE PoC Hub</a></div>
<nav>
<a href="/search/">PoC Search</a>
<a href="/kev/">KEV</a>
<a href="/epss/">EPSS</a>
</nav>
</div>
</header>
<main class="wrap">
{% block content %}{% endblock %}
</main>
<footer class="site-footer">
<div class="wrap">
<span>Fast CVE triage without the noise.</span>
<span><a href="https://github.com/0xMarcio/cve">GitHub repo</a></span>
</div>
</footer>
{% block extra_scripts %}{% endblock %}
</body>
</html>

View File

@@ -1,29 +0,0 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ cve.cve }}</h1>
<p class="lead">{{ cve.description or 'No description available.' }}</p>
<div class="pill-row">
{% if cve.kev %}<span class="pill pill-warn">In KEV</span>{% else %}<span class="pill">Not in KEV</span>{% endif %}
{% if cve.epss is not none %}<span class="pill">EPSS {{ '%.3f'|format(cve.epss) }} ({{ '%2.0f'|format((cve.percentile or 0)*100) }}th)</span>{% endif %}
{% if cve.kev and cve.kev.due_date %}<span class="pill">Due {{ cve.kev.due_date }}</span>{% endif %}
</div>
{% if cve.poc_links %}
<h2>Proof of Concepts</h2>
<ul>
{% for link in cve.poc_links %}
<li><a href="{{ link }}" target="_blank" rel="noopener">{{ link }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% if cve.kev %}
<h2>KEV Details</h2>
<ul>
<li><strong>Date added:</strong> {{ cve.kev.date_added }}</li>
{% if cve.kev.due_date %}<li><strong>Due date:</strong> {{ cve.kev.due_date }}</li>{% endif %}
<li><strong>Required action:</strong> {{ cve.kev.required_action }}</li>
{% if cve.kev.notes %}<li><strong>Notes:</strong> {{ cve.kev.notes }}</li>{% endif %}
</ul>
{% endif %}
{% endblock %}

View File

@@ -1,27 +0,0 @@
{% extends "base.html" %}
{% block content %}
<section class="section">
<div class="section-header">
<h1>New KEV entries</h1>
<span class="muted">Only the recent additions</span>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>CVE</th><th>Vendor</th><th>Product</th><th>EPSS</th><th>Percentile</th><th>Date Added</th><th>Due</th></tr></thead>
<tbody>
{% for row in recent_kev %}
<tr>
<td class="cve-cell"><a href="/cve/?id={{ row.cve }}">{{ row.cve }}</a></td>
<td>{{ row.vendor }}</td>
<td>{{ row.product }}</td>
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
<td>{{ '%2.0f'|format((row.percentile or 0)*100) }}th</td>
<td>{{ row.date_added }}</td>
<td>{{ row.due_date or '—' }}</td>
</tr>
{% else %}<tr><td colspan="7">No fresh KEV entries in the last 30 days.</td></tr>{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@@ -1,26 +0,0 @@
{% extends "base.html" %}
{% block content %}
<section class="section">
<div class="section-header">
<h1>EPSS highlights</h1>
<span class="muted">High-probability CVEs that are not in KEV.</span>
</div>
<input type="search" placeholder="Filter CVE" data-filter-table="epss-table" class="filter" />
<div class="table-responsive">
<table class="list" id="epss-table">
<thead><tr><th>CVE</th><th>EPSS</th><th>Percentile</th><th>PoCs</th><th>Summary</th></tr></thead>
<tbody>
{% for row in epss %}
<tr>
<td class="cve-cell"><a href="/cve/?id={{ row.cve }}">{{ row.cve }}</a></td>
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
<td>{{ '%2.0f'|format((row.percentile or 0)*100) }}th</td>
<td>{{ row.poc_count }}</td>
<td class="mono">{{ row.summary or 'No public description yet.' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@@ -1,15 +1,14 @@
{% extends "base.html" %}
{% set body_class = "color-no-search" %}
{% block content %}
<section class="hero hero-signal" data-search-root>
<div class="hero-meta">
<p class="lede">Search by CVE id, vendor, product, or keyword.</p>
<div class="pill-row tight">
<span class="pill">Linked PoCs</span>
<span class="pill">MITRE CVE pages</span>
<span class="pill">Vendor / product filters</span>
<span class="pill">Supports negative terms (e.g. <strong>-windows</strong>)</span>
</div>
<section class="hero" data-search-root>
<h1>Find exploit writeups and PoCs instantly</h1>
<p class="lede">Search by CVE id, vendor, product, or keyword.</p>
<div class="pill-row tight">
<span class="pill">Linked PoCs</span>
<span class="pill">MITRE CVE pages</span>
<span class="pill">Vendor / product filters</span>
<span class="pill">Supports negative terms (e.g. <strong>-windows</strong>)</span>
</div>
<form class="searchForm" action="#">
<input type="text" class="search" placeholder="Search CVE, vendor, product, or keyword" autocomplete="off">
@@ -17,7 +16,6 @@
<div class="search-results" data-results style="display:none">
<div class="header">
<h2>Results</h2>
<span class="muted">Filter with negative terms (e.g., -windows)</span>
</div>
<div class="noResults">No results yet.</div>
<div class="results-table hide">
@@ -34,11 +32,8 @@
</div>
</section>
<section class="section">
<div class="section-header">
<h1>Trending PoCs</h1>
<span class="muted">Current year, updated in the last 4 days</span>
</div>
<section class="section" data-trending-section>
<h2>Latest PoC examples</h2>
<div class="table-wrap" data-trending>
<table>
<thead><tr><th>Stars</th><th>Updated</th><th>Name</th><th>Description</th></tr></thead>
@@ -51,32 +46,7 @@
<td class="mono">{{ row.desc }}</td>
</tr>
{% else %}
<tr><td colspan="4" class="muted">No recent PoCs.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<section class="section">
<div class="section-header">
<h1>High EPSS not in KEV</h1>
<span class="muted">Sorted by score</span>
</div>
<div class="table-wrap">
<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>
{% for row in data.high_epss %}
<tr>
<td class="cve-cell"><a href="/cve/?id={{ row.cve }}">{{ row.cve }}</a></td>
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
<td>{{ '%2.0f'|format((row.percentile or 0)*100) }}th</td>
<td>{{ row.poc_count }}</td>
<td class="mono">{{ row.summary or 'No public description yet.' }}</td>
</tr>
{% else %}
<tr><td colspan="5">No high-EPSS items outside KEV today.</td></tr>
<tr><td colspan="4">No recent PoCs.</td></tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,30 +0,0 @@
{% extends "base.html" %}
{% block content %}
<section class="section">
<div class="section-header">
<h1>KEV catalog</h1>
<span class="muted">Filter by CVE, vendor, or product.</span>
</div>
<input type="search" placeholder="Filter CVE, vendor, product" data-filter-table="kev-table" class="filter" />
<div class="table-responsive">
<table class="list" id="kev-table">
<thead>
<tr><th>CVE</th><th>Vendor</th><th>Product</th><th>EPSS</th><th>Percentile</th><th>Date Added</th><th>Due</th></tr>
</thead>
<tbody>
{% for row in kev %}
<tr>
<td class="cve-cell"><a href="/cve/?id={{ row.cve }}">{{ row.cve }}</a></td>
<td>{{ row.vendor }}</td>
<td>{{ row.product }}</td>
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
<td>{{ '%2.0f'|format((row.percentile or 0)*100) }}th</td>
<td>{{ row.date_added }}</td>
<td>{{ row.due_date or '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@@ -1,36 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}CVE PoC Hub{% endblock %}</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header class="topbar">
<div class="wrap">
<div class="brand">
<a href="/"><span class="dot"></span> CVE PoC Hub</a>
</div>
<nav>
<a href="/search/">PoC Search</a>
<a href="/pocs/">Explorer</a>
<a href="/diffs/">New KEV</a>
<a href="/epss/">EPSS</a>
<a href="/kev/">KEV</a>
<a href="https://github.com/0xMarcio/cve" target="_blank" rel="noreferrer">GitHub</a>
</nav>
</div>
</header>
<main class="wrap">
{% block content %}{% endblock %}
</main>
<footer class="footer">
<div class="wrap footer-inner">
<div>PoC explorer with clean signals only.</div>
<div class="muted">API: <code>/api/v1/</code></div>
</div>
</footer>
<script src="/assets/app.js"></script>
</body>
</html>

View File

@@ -1,46 +0,0 @@
{% extends "pipeline_base.html" %}
{% block title %}{{ cve.cve_id }} PoCs{% endblock %}
{% block content %}
<section>
<div class="section-header">
<div>
<p class="eyebrow">CVE record</p>
<h1>{{ cve.cve_id }}</h1>
<p class="muted small">Last updated {{ cve.last_updated }}</p>
</div>
<a class="text-link" href="/api/v1/cve/{{ cve.cve_id }}.json">JSON</a>
</div>
<div class="card-grid">
{% for poc in cve.visible_pocs %}
<article class="card">
<div class="card-title"><a href="{{ poc.repo_url }}" target="_blank" rel="noreferrer">{{ poc.repo_full_name }}</a></div>
<div class="meta-row">
<span class="pill tier-{{ poc.confidence_tier }}">{{ poc.confidence_tier|capitalize }} ({{ poc.confidence_score|round(1) }})</span>
{% if poc.primary_language %}<span class="pill">{{ poc.primary_language }}</span>{% endif %}
{% if poc.stars %}<span class="pill">{{ poc.stars }}★</span>{% endif %}
{% if poc.is_fork %}<span class="pill ghost">Fork</span>{% endif %}
</div>
<div class="muted small">
{% if poc.pushed_at %}Updated {{ poc.pushed_at }} · {% endif %}
{% if poc.archived %}<span class="pill warn">Archived</span>{% endif %}
{% if poc.parent_repo_url %}Parent: <a href="{{ poc.parent_repo_url }}" target="_blank" rel="noreferrer">{{ poc.parent_repo_url }}</a>{% endif %}
</div>
<div class="pill-row">
{% for topic in poc.topics %}<span class="pill ghost">{{ topic }}</span>{% endfor %}
</div>
<div class="matches">
<div class="muted small">Matches</div>
<ul>
{% for match in poc.matches %}
<li><span class="pill tiny">{{ match.match_type }}</span> {{ match.path }}</li>
{% endfor %}
</ul>
</div>
</article>
{% else %}
<p class="muted">No PoCs found yet for {{ cve.cve_id }}.</p>
{% endfor %}
</div>
</section>
{% endblock %}

View File

@@ -1,72 +0,0 @@
{% extends "pipeline_base.html" %}
{% block title %}Diff {{ diff.generated or generated }}{% endblock %}
{% block content %}
<section>
<div class="section-header">
<div>
<p class="eyebrow">Daily delta</p>
<h1>Diff for {{ diff.generated }}</h1>
</div>
<a class="text-link" href="/api/v1/diffs/{{ diff.generated }}.json">JSON</a>
</div>
<div class="grid-2">
<div>
<h3>New high-confidence PoCs</h3>
<ul class="list">
{% for item in diff.new_high_conf_pocs %}
<li>
<span class="pill">+ High</span>
<a href="/cve/{{ item.cve_id }}.html">{{ item.cve_id }}</a>
<a href="https://github.com/{{ item.repo_full_name }}" target="_blank" rel="noreferrer">{{ item.repo_full_name }}</a>
</li>
{% else %}
<li class="muted">No new high-confidence entries.</li>
{% endfor %}
</ul>
</div>
<div>
<h3>Promoted to high</h3>
<ul class="list">
{% for item in diff.promoted_to_high %}
<li>
<span class="pill"></span>
<a href="/cve/{{ item.cve_id }}.html">{{ item.cve_id }}</a>
<a href="https://github.com/{{ item.repo_full_name }}" target="_blank" rel="noreferrer">{{ item.repo_full_name }}</a>
<span class="muted small">(prev {{ item.previous_tier }})</span>
</li>
{% else %}
<li class="muted">No promotions this run.</li>
{% endfor %}
</ul>
</div>
</div>
<div class="grid-2">
<div>
<h3>Demoted or removed</h3>
<ul class="list">
{% for item in diff.demoted_or_removed %}
<li>
<span class="pill warn"></span>
<a href="/cve/{{ item.cve_id }}.html">{{ item.cve_id }}</a>
<span class="muted small">{{ item.repo_full_name }}</span>
</li>
{% else %}
<li class="muted">No removals.</li>
{% endfor %}
</ul>
</div>
<div>
<h3>Dead links (optional checks)</h3>
<ul class="list">
{% for item in diff.dead_links %}
<li><span class="pill warn">offline</span> <a href="{{ item.url }}">{{ item.url }}</a></li>
{% else %}
<li class="muted">Link checks skipped or none failed.</li>
{% endfor %}
</ul>
</div>
</div>
</section>
{% endblock %}

View File

@@ -1,69 +0,0 @@
{% extends "pipeline_base.html" %}
{% block title %}CVE PoC Radar{% endblock %}
{% block content %}
<section class="hero">
<div>
<p class="eyebrow">Daily GitHub sweep</p>
<h1>CVE PoC Goldmine</h1>
<p class="lede">Incremental discovery, scoring, and diffing for public exploit PoCs. High-confidence hits surface first; low-signal noise stays out of the spotlight.</p>
<div class="cta-row">
<a class="btn" href="/pocs/">Open PoC Explorer</a>
<a class="btn ghost" href="/api/v1/index.json">API index</a>
</div>
</div>
<div class="hero-panel">
<div class="stat">
<div class="label">High confidence</div>
<div class="value">{{ summary.high_total }}</div>
</div>
<div class="stat">
<div class="label">Medium confidence</div>
<div class="value">{{ summary.medium_total }}</div>
</div>
<div class="stat">
<div class="label">Tracked CVEs</div>
<div class="value">{{ summary.total_cves }}</div>
</div>
<div class="label muted small">Generated {{ summary.generated }}</div>
</div>
</section>
<section>
<div class="section-header">
<h2>Top PoCs right now</h2>
<a class="text-link" href="/api/v1/top/today.json">JSON</a>
</div>
<div class="card-grid">
{% for poc in top %}
<article class="card">
<div class="card-title"><a href="{{ poc.repo_url }}" target="_blank" rel="noreferrer">{{ poc.repo_full_name }}</a></div>
<div class="meta-row">
<span class="pill tier-{{ poc.tier }}">{{ poc.tier|capitalize }}</span>
<span class="pill">{{ poc.score|round(1) }} pts</span>
{% if poc.stars %}<span class="pill">{{ poc.stars }}★</span>{% endif %}
{% if poc.primary_language %}<span class="pill">{{ poc.primary_language }}</span>{% endif %}
</div>
<div class="muted small">CVE: <a href="/cve/{{ poc.cve_id }}.html">{{ poc.cve_id }}</a></div>
</article>
{% else %}
<p class="muted">No PoCs available yet.</p>
{% endfor %}
</div>
</section>
<section>
<div class="section-header">
<h2>Latest diff</h2>
<a class="text-link" href="/diffs/">Diffs</a>
</div>
{% if diff and diff.new_high_conf_pocs %}
<div class="pill-row">
{% for item in diff.new_high_conf_pocs %}
<span class="pill">+ {{ item.cve_id }} / {{ item.repo_full_name }}</span>
{% endfor %}
</div>
{% else %}
<p class="muted small">No new high-confidence PoCs in the latest run.</p>
{% endif %}
</section>
{% endblock %}

View File

@@ -1,47 +0,0 @@
{% extends "pipeline_base.html" %}
{% block title %}PoC Explorer{% endblock %}
{% block content %}
<section>
<div class="section-header">
<h1>PoC Explorer</h1>
<div class="muted">Search across the pre-built index JSON. Client-side results stay small and fast.</div>
</div>
<input class="input" type="search" placeholder="Search CVE id, language, tier…" data-index-search data-index-url="/api/v1/index.json" data-target="#search-results">
<div id="search-results" class="card-grid" data-search-results>
<p class="muted small">Type to search recent CVEs. Results stream in from <code>/api/v1/index.json</code>.</p>
</div>
</section>
<section>
<div class="section-header">
<h2>Latest high + medium</h2>
<div class="muted small">Server-side snapshot</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>CVE</th>
<th>High</th>
<th>Medium</th>
<th>Languages</th>
<th>Max score</th>
</tr>
</thead>
<tbody>
{% for item in index %}
<tr>
<td><a href="/cve/{{ item.cve_id }}.html">{{ item.cve_id }}</a></td>
<td>{{ item.high_confidence }}</td>
<td>{{ item.medium_confidence }}</td>
<td>{% for lang in item.top_languages %}<span class="pill">{{ lang }}</span>{% else %}<span class="muted"></span>{% endfor %}</td>
<td>{{ item.max_score|round(1) }}</td>
</tr>
{% else %}
<tr><td colspan="5" class="muted">No entries available.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@@ -1,20 +0,0 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ vendor.vendor }}</h1>
<p>{{ cves|length }} CVEs</p>
<div class="table-responsive">
<table class="list">
<thead><tr><th>CVE</th><th>EPSS</th><th>KEV</th><th>PoCs</th></tr></thead>
<tbody>
{% for detail in cves %}
<tr>
<td><a href="/cve/{{ detail.cve }}.html">{{ detail.cve }}</a></td>
<td>{% if detail.epss is not none %}{{ '%.3f'|format(detail.epss) }}{% else %}—{% endif %}</td>
<td>{{ 'Yes' if detail.kev else 'No' }}</td>
<td>{{ detail.poc_count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}