Mark the CVEs attackers are actually using

CISA publishes a catalogue of vulnerabilities observed being exploited in
the wild, and 1,111 of its 1,685 entries are CVEs this index already holds
PoCs for. That is the one fact a reader most wants before opening an
exploit, and it was nowhere on the page.

The weekly audit stores the catalogue, the build trims it to the CVEs the
index carries, and a result row shows a KEV chip -- with the ransomware
flag where CISA records one, which covers 352 of them. 37 KB served.

A failed fetch keeps the stored catalogue rather than dropping the badge
off every entry. The chip reuses the header pill's treatment: the palette
still spends green on things you can click and amber on star counts only.
This commit is contained in:
0xMarcio committed 2026-08-31 00:13:01 +02:00
1 parent 1377e03b6a
commit 1aa51fa852
7 files changed
+79 -1

No files matched your search

+34
View File
@@ -26,6 +26,11 @@ ROOT = Path(__file__).resolve().parents[1]
STATE_FILE = ROOT / ".github" / "link_audit_state.json"
GITHUB_LIST = ROOT / "github.txt"
REPO_META = ROOT / "repo_meta.json"
KEV_FILE = ROOT / "kev.json"
KEV_URL = (
"https://www.cisa.gov/sites/default/files/feeds/"
"known_exploited_vulnerabilities.json"
)
GITHUB_SECTION = "#### Github"
REFERENCE_SECTION = "#### Reference"
GITHUB_EMPTY = "No PoCs found on GitHub currently."
@@ -164,6 +169,34 @@ def prune_inventory(dead_urls: set[str], *, dry_run: bool) -> int:
return len(lines) - len(kept)
def refresh_kev(*, dry_run: bool) -> int:
"""Store CISA's known-exploited catalogue: the CVEs attackers actually use.
Keeps only what the page shows, so the whole catalogue costs a few tens of
kilobytes. A failure leaves the previous file in place rather than
dropping the badge off every entry.
"""
try:
payload = http_json(KEV_URL)
except Exception as exc:
print(f"Kept the stored KEV catalogue after a failed fetch: {exc}", file=sys.stderr)
return 0
entries = {}
for item in (payload or {}).get("vulnerabilities") or []:
cve_id = str(item.get("cveID") or "").upper()
if cve_id:
entries[cve_id] = [
str(item.get("dateAdded") or "")[:10],
1 if str(item.get("knownRansomwareCampaignUse") or "").lower() == "known" else 0,
]
if entries and not dry_run:
KEV_FILE.write_text(
json.dumps(entries, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n",
encoding="utf-8",
)
return len(entries)
def load_cursor() -> str:
try:
return str(json.loads(STATE_FILE.read_text(encoding="utf-8")).get("cursor") or "")
@@ -212,6 +245,7 @@ def main() -> int:
print(f"Removed {links} links from {entries} entries | {inventory} inventory lines")
print(f"Refreshed stars and last-push for {len(metadata)} repositories | {tracked} tracked")
print(f"KEV catalogue: {refresh_kev(dry_run=args.dry_run)} known-exploited CVEs")
return 0
+17 -1
View File
@@ -16,6 +16,8 @@ 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"
KEV_INPUT = ROOT / "kev.json"
KEV_OUTPUT = DOCS_DIR / "kev.json"
def load_blacklist() -> set[str]:
if not BLACKLIST.exists():
return set()
@@ -154,6 +156,16 @@ def build_repo_meta(cve_entries: List[Dict[str, object]]) -> Dict[str, object]:
return {name: value for name, value in stored.items() if name in linked}
def build_kev(cve_entries: List[Dict[str, object]]) -> Dict[str, object]:
"""CISA's known-exploited entries, limited to CVEs the index carries."""
try:
stored = json.loads(KEV_INPUT.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return {}
have = {str(entry["cve"]) for entry in cve_entries}
return {cve: value for cve, value in stored.items() if cve in have}
def build_trending(blacklist: Collection[str]) -> List[Dict[str, object]]:
"""The trending job's own output, minus anything the blacklist covers."""
try:
@@ -185,6 +197,9 @@ def main() -> int:
repo_meta = build_repo_meta(cve_payload)
write_json(REPO_META_OUTPUT, repo_meta)
kev = build_kev(cve_payload)
write_json(KEV_OUTPUT, kev)
trending_items = build_trending(blacklist)
write_json(
TRENDING_OUTPUT,
@@ -199,7 +214,8 @@ def main() -> int:
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}"
f"{REPO_META_OUTPUT.name} ({len(repo_meta)} repositories), "
f"{KEV_OUTPUT.name} ({len(kev)} known-exploited) and {TRENDING_OUTPUT.name}"
)
return 0