mirror of
https://github.com/0xMarcio/cve.git
synced 2026-02-12 14:32:50 +00:00
Simplify site to search + trending
This commit is contained in:
69
.github/workflows/build.yml
vendored
69
.github/workflows/build.yml
vendored
@@ -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
|
||||
41
.github/workflows/generate_cve_json.yml
vendored
41
.github/workflows/generate_cve_json.yml
vendored
@@ -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 }}
|
||||
2
.github/workflows/hot_cves.yml
vendored
2
.github/workflows/hot_cves.yml
vendored
@@ -3,7 +3,7 @@ name: Hot CVEs List
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '30 */12 * * *'
|
||||
- cron: '0 */6 * * *'
|
||||
|
||||
jobs:
|
||||
ScheduledRun:
|
||||
|
||||
14
.github/workflows/site.yml
vendored
14
.github/workflows/site.yml
vendored
@@ -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
|
||||
|
||||
|
||||
45
.github/workflows/static.yml
vendored
45
.github/workflows/static.yml
vendored
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
})();
|
||||
@@ -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);
|
||||
})();
|
||||
@@ -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);
|
||||
})();
|
||||
@@ -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();
|
||||
});
|
||||
})();
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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'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>
|
||||
283
docs/index.html
283
docs/index.html
@@ -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 'panel_client' using a bundled scanning and exploitation tool. Affected: <= 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 "Lab Environment" 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 & Root Escalation toolkit. Verify the vulnerability, take the host, destroy the logs. > 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'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>
|
||||
|
||||
2423
docs/kev/index.html
2423
docs/kev/index.html
File diff suppressed because it is too large
Load Diff
@@ -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 => ({
|
||||
'&': '&',
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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
4
docs/trending_poc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"generated": "2025-12-18T00:00:00Z",
|
||||
"items": []
|
||||
}
|
||||
@@ -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/`.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user