mirror of
https://github.com/0xMarcio/cve.git
synced 2026-10-27 16:51:03 +00:00
The trending table sorted and displayed updated_at, which also moves when a repository merely gains a star, so a four-month-old exploit was claiming it had been updated three hours ago. Record pushed_at and created_at and let the page decide: last commit for the age it shows, repository age for the trending rank. The job now writes trending.json alongside the README table, so the site reads the data instead of regex-parsing markdown the same job generated.
209 lines
6.4 KiB
Python
209 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Collection, Dict, List, Optional
|
|
from urllib.parse import urlparse
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DOCS_DIR = ROOT / "docs"
|
|
BLACKLIST = ROOT / "blacklist.txt"
|
|
TRENDING_INPUT = ROOT / "trending.json"
|
|
CVE_OUTPUT = DOCS_DIR / "CVE_list.json"
|
|
TRENDING_OUTPUT = DOCS_DIR / "trending_poc.json"
|
|
REPO_META = ROOT / "repo_meta.json"
|
|
REPO_META_OUTPUT = DOCS_DIR / "repo_meta.json"
|
|
def load_blacklist() -> set[str]:
|
|
if not BLACKLIST.exists():
|
|
return set()
|
|
return {
|
|
line.strip().lower()
|
|
for line in BLACKLIST.read_text(encoding="utf-8").splitlines()
|
|
if line.strip() and not line.lstrip().startswith("#")
|
|
}
|
|
|
|
|
|
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 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 repo_from_url(url: str) -> str:
|
|
try:
|
|
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 "/".join(parts[:2]).lower()
|
|
return (parts[-1] if parts else "").lower()
|
|
|
|
|
|
def is_blacklisted(url: str, blacklist: Collection[str]) -> bool:
|
|
full_name = repo_from_url(url)
|
|
if not full_name:
|
|
return False
|
|
return full_name in blacklist
|
|
|
|
|
|
def link_key(url: str) -> str:
|
|
parsed = urlparse(url)
|
|
if (parsed.hostname or "").lower() not in {"github.com", "www.github.com"}:
|
|
return url
|
|
parts = [part for part in parsed.path.split("/") if part]
|
|
if len(parts) < 2:
|
|
return url
|
|
repo = f"{parts[0]}/{parts[1]}".lower()
|
|
suffix = "/" + "/".join(parts[2:]) if len(parts) > 2 else ""
|
|
return f"github:{repo}{suffix}?{parsed.query}#{parsed.fragment}"
|
|
|
|
|
|
def collect_links(block: str, *, blacklist: Optional[Collection[str]] = None) -> List[str]:
|
|
links: List[str] = []
|
|
blacklist = blacklist or set()
|
|
for raw in block.splitlines():
|
|
entry = raw.strip()
|
|
if not entry or "No PoCs" in entry:
|
|
continue
|
|
if entry.startswith("- "):
|
|
entry = entry[2:].strip()
|
|
if not entry:
|
|
continue
|
|
if is_blacklisted(entry, blacklist):
|
|
continue
|
|
if entry not in links:
|
|
links.append(entry)
|
|
return links
|
|
|
|
|
|
def build_cve_list(blacklist: Collection[str]) -> tuple[List[Dict[str, object]], int]:
|
|
cve_entries = []
|
|
total = 0
|
|
|
|
for md_path in sorted(ROOT.glob("[12][0-9][0-9][0-9]/CVE-*.md")):
|
|
total += 1
|
|
content = md_path.read_text(encoding="utf-8")
|
|
sections = parse_sections(content)
|
|
description = normalise_block(sections.get("### Description", ""))
|
|
references = collect_links(sections.get("#### Reference", ""), blacklist=blacklist)
|
|
github_links = collect_links(sections.get("#### Github", ""), blacklist=blacklist)
|
|
|
|
poc_entries: List[str] = []
|
|
seen = set()
|
|
for link in references + github_links:
|
|
key = link_key(link)
|
|
if key not in seen:
|
|
poc_entries.append(link)
|
|
seen.add(key)
|
|
|
|
cve_id = md_path.stem
|
|
if not poc_entries:
|
|
continue
|
|
|
|
cve_entries.append({
|
|
"cve": cve_id,
|
|
"desc": description,
|
|
"poc": poc_entries,
|
|
})
|
|
|
|
return cve_entries, total
|
|
|
|
|
|
def build_repo_meta(cve_entries: List[Dict[str, object]]) -> Dict[str, object]:
|
|
"""Star count and last-push date for the repositories the index actually links."""
|
|
try:
|
|
stored = json.loads(REPO_META.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError):
|
|
return {}
|
|
linked = {
|
|
repo_from_url(url)
|
|
for entry in cve_entries
|
|
for url in entry["poc"]
|
|
if repo_from_url(url)
|
|
}
|
|
return {name: value for name, value in stored.items() if name in linked}
|
|
|
|
|
|
def build_trending(blacklist: Collection[str]) -> List[Dict[str, object]]:
|
|
"""The trending job's own output, minus anything the blacklist covers."""
|
|
try:
|
|
rows = json.loads(TRENDING_INPUT.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError):
|
|
return []
|
|
return [
|
|
row for row in rows
|
|
if row.get("url") and not is_blacklisted(str(row["url"]), blacklist)
|
|
]
|
|
|
|
|
|
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,
|
|
separators=None if indent else (",", ":"))
|
|
|
|
|
|
def main() -> int:
|
|
argparse.ArgumentParser(description="Build the CVE PoC site data files").parse_args()
|
|
|
|
DOCS_DIR.mkdir(parents=True, exist_ok=True)
|
|
blacklist = load_blacklist()
|
|
|
|
cve_payload, total_cves = build_cve_list(blacklist)
|
|
write_json(CVE_OUTPUT, cve_payload)
|
|
|
|
repo_meta = build_repo_meta(cve_payload)
|
|
write_json(REPO_META_OUTPUT, repo_meta)
|
|
|
|
trending_items = build_trending(blacklist)
|
|
write_json(
|
|
TRENDING_OUTPUT,
|
|
{
|
|
"generated": datetime.now(timezone.utc).isoformat(),
|
|
"total_cves": total_cves,
|
|
"with_pocs": len(cve_payload),
|
|
"items": trending_items,
|
|
},
|
|
indent=2,
|
|
)
|
|
|
|
print(
|
|
f"Wrote {CVE_OUTPUT.name} ({len(cve_payload)} of {total_cves} CVEs), "
|
|
f"{REPO_META_OUTPUT.name} ({len(repo_meta)} repositories) and {TRENDING_OUTPUT.name}"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|