From 31da22381c564b7bdfedef511b60affbc6ea879b Mon Sep 17 00:00:00 2001 From: 0xMarcio Date: Wed, 2 Sep 2026 03:36:22 +0200 Subject: [PATCH] Rebrand to PoC Index and move to pocindex.io The site called itself three different things: CVE PoC Hub in the title, CVE Radar in the h1 and the social metadata, and cve on GitHub. Google had no consistent site name to settle on, so none of the mentions compounded. CVE Radar was taken anyway, by SOCRadar's free tool and by cveradar.com. Every user-facing name and URL now comes from scripts/brand.py, so the next move is one edit rather than a sweep through the tree. Also repairs the parts of the site a crawler could not use: - robots.txt and sitemap.xml were both 404. scripts/build_seo.py writes them, wired into the Pages job. trending_poc.json, kev.json and stats.json stay crawlable because the first paint needs them; the five bulk payloads are disallowed, which is 13.2 MB per crawl spent on data no crawler queries. - Search state lives in ?q= now. Results were unlinkable, so nobody could cite one and there was no route for a SearchAction to declare. replaceState rather than pushState, so typing does not bury the back button. - A WebSite JSON-LD block carries the name Google prints as the site name. - The footer carries the MITRE trademark notice. --- .github/getTrending.py | 39 ++++++------- .github/hero.py | 27 +++++---- .github/workflows/audit_poc_links.yml | 2 +- .github/workflows/hot_cves.yml | 2 +- .github/workflows/site.yml | 5 +- .github/workflows/sync_collections.yml | 2 +- .github/workflows/sync_cve_pocs.yml | 2 +- .github/workflows/sync_exploits.yml | 2 +- .github/workflows/sync_metadata.yml | 2 +- .github/workflows/sync_nuclei.yml | 2 +- .github/workflows/sync_pocingithub.yml | 2 +- .gitignore | 2 + README.md | 32 +++++------ docs/CNAME | 2 +- docs/hero.svg | 6 +- docs/index.html | 47 ++++++++++++---- docs/logic.js | 33 +++++++++++ docs/search.svg | 2 +- docs/social-card.svg | 6 +- docs/style.css | 6 +- scripts/brand.py | 46 ++++++++++++++++ scripts/build_seo.py | 76 ++++++++++++++++++++++++++ 22 files changed, 270 insertions(+), 75 deletions(-) create mode 100644 scripts/brand.py create mode 100644 scripts/build_seo.py diff --git a/.github/getTrending.py b/.github/getTrending.py index 00960039ba6..66106eec039 100644 --- a/.github/getTrending.py +++ b/.github/getTrending.py @@ -45,6 +45,7 @@ USER_AGENT = "0xMarcio-cve-trending" ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) sys.path.insert(0, os.path.join(ROOT, "scripts")) +from brand import BRAND, SITE from update_cves import load_blacklist, qualifying_repo_cves README = os.path.join(ROOT, "README.md") @@ -506,11 +507,11 @@ def refresh_count_header() -> int: badges = { "CVEs with PoCs": pill( "CVEs with PoCs", f"{counts['with_pocs']:,}", "2f81f7", - "https://cve.codepwn.win/", + f"{SITE}/", ), "known exploited": pill( "known exploited", f"{counts['kev']:,}", "f85149", - "https://cve.codepwn.win/", + f"{SITE}/", ), } badge_replacements = 0 @@ -536,7 +537,7 @@ def header(stamp: str, synced: str) -> list[str]: because GitHub proxies README images and would otherwise serve a stale copy of a file that is redrawn whenever the index moves.""" counts = figures() - home = "https://cve.codepwn.win/" + home = f"{SITE}/" badges = [ pill("last sync", synced, "2f81f7", f"https://github.com/{SLUG}/commits/main"), live_pill("actions/workflow/status/" + SLUG + "/hot_cves.yml", "CI", "2f81f7", @@ -551,11 +552,11 @@ def header(stamp: str, synced: str) -> list[str]: return [ '
', "", - f'CVE Radar', + f'{BRAND}', "", " ".join(badges), "", - f'Search CVE Radar', + f'Search {BRAND}', "", "
", "", @@ -570,25 +571,25 @@ Every file is plain JSON on the CDN. No key, no rate limit. ```bash # everything the index knows about one CVE -curl -s https://cve.codepwn.win/CVE_list.json \\ +curl -s @SITE@/CVE_list.json \\ | jq '.[] | select(.cve == "CVE-2021-44228") | {cve, poc: (.poc | length), nuclei, msf, edb, vulhub, collections}' # every published CVSS assessment plus vetted advisory links -curl -s https://cve.codepwn.win/cve_metadata.json | jq '."CVE-2021-44228"' +curl -s @SITE@/cve_metadata.json | jq '."CVE-2021-44228"' # likelihood of exploitation in the next 30 days -curl -s https://cve.codepwn.win/epss.json | jq '."CVE-2021-44228"' +curl -s @SITE@/epss.json | jq '."CVE-2021-44228"' # stars and last push for one PoC repository; repository keys are lowercased -curl -s https://cve.codepwn.win/repo_meta.json | jq '."sfewer-r7/cve-2026-55040"' +curl -s @SITE@/repo_meta.json | jq '."sfewer-r7/cve-2026-55040"' ``` What CISA says is being exploited, that also has a PoC here, ranked by how likely each is to be used next: ```bash -curl -s https://cve.codepwn.win/kev.json -o kev.json -curl -s https://cve.codepwn.win/epss.json -o epss.json +curl -s @SITE@/kev.json -o kev.json +curl -s @SITE@/epss.json -o epss.json jq -n --slurpfile kev kev.json --slurpfile epss epss.json \\ '[$kev[0] | keys[] | select($epss[0][.]) | {cve: ., epss: $epss[0][.][0]}] | sort_by(-.epss) | .[:10]' @@ -596,13 +597,13 @@ jq -n --slurpfile kev kev.json --slurpfile epss epss.json \\ | Endpoint | Holds | | --- | --- | -| [`CVE_list.json`](https://cve.codepwn.win/CVE_list.json) | Every CVE with a linked PoC, its description and its `poc`, `nuclei`, `msf`, `edb`, `vulhub` and `collections` links | -| [`cve_metadata.json`](https://cve.codepwn.win/cve_metadata.json) | NVD CVSS v2.0, v3.0, v3.1 and v4.0 assessments with vectors and vetted advisory links | -| [`epss.json`](https://cve.codepwn.win/epss.json) | Exploitation probability and percentile, for nearly every CVE indexed | -| [`nuclei.json`](https://cve.codepwn.win/nuclei.json) | Template metadata for the CVEs covered by a runnable Nuclei check | -| [`kev.json`](https://cve.codepwn.win/kev.json) | CISA known exploited, keyed by CVE id | -| [`repo_meta.json`](https://cve.codepwn.win/repo_meta.json) | Stars and last push date per PoC repository, keys lowercased | -| [`trending_poc.json`](https://cve.codepwn.win/trending_poc.json) | Trending repositories plus index totals | +| [`CVE_list.json`](@SITE@/CVE_list.json) | Every CVE with a linked PoC, its description and its `poc`, `nuclei`, `msf`, `edb`, `vulhub` and `collections` links | +| [`cve_metadata.json`](@SITE@/cve_metadata.json) | NVD CVSS v2.0, v3.0, v3.1 and v4.0 assessments with vectors and vetted advisory links | +| [`epss.json`](@SITE@/epss.json) | Exploitation probability and percentile, for nearly every CVE indexed | +| [`nuclei.json`](@SITE@/nuclei.json) | Template metadata for the CVEs covered by a runnable Nuclei check | +| [`kev.json`](@SITE@/kev.json) | CISA known exploited, keyed by CVE id | +| [`repo_meta.json`](@SITE@/repo_meta.json) | Stars and last push date per PoC repository, keys lowercased | +| [`trending_poc.json`](@SITE@/trending_poc.json) | Trending repositories plus index totals | | [`cves/2026/CVE-2026-68138.md`](cves/2026/CVE-2026-68138.md) | Markdown copy of one CVE, one directory per year | CVSS rows are `[version, score, severity, vector, source, assessment type]`. @@ -641,7 +642,7 @@ Advisory rows are `[URL, NVD reference tags]`. Missing PoC, wrong link, dead repository: open an issue with the CVE id and the repository URL. -""" +""".replace("@SITE@", SITE) def main() -> int: diff --git a/.github/hero.py b/.github/hero.py index a86d6b3362f..395e81b3153 100644 --- a/.github/hero.py +++ b/.github/hero.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Render the CVE Radar hero banner and social card as self-contained SVG. +"""Render the PoC Index hero banner and social card as self-contained SVG. Both cards are plain SVG with no external font, image or stylesheet reference: GitHub proxies README images through camo, which fetches the file on its own and @@ -11,12 +11,17 @@ from __future__ import annotations import importlib.util import json import os +import sys from urllib import request ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)) BUILD_SITE = os.path.join(ROOT, "scripts", "build_site.py") -LIVE_STATS = "https://cve.codepwn.win/trending_poc.json" -LIVE_KEV = "https://cve.codepwn.win/kev.json" +sys.path.insert(0, os.path.join(ROOT, "scripts")) + +from brand import BRAND, SITE, SUBTITLE, host + +LIVE_STATS = f"{SITE}/trending_poc.json" +LIVE_KEV = f"{SITE}/kev.json" HERO = os.path.join(ROOT, "docs", "hero.svg") SOCIAL = os.path.join(ROOT, "docs", "social-card.svg") STATS = os.path.join(ROOT, "docs", "stats.json") @@ -122,16 +127,16 @@ def hero(counts: dict) -> str: ) ) return f""" + role="img" aria-label="{BRAND}, {group(counts['with_pocs'])} CVEs with public proof-of-concept exploits"> {sweep(1090, 90, 160, 0.3)} CVE RADAR + fill="{FG}" letter-spacing="2">{BRAND} - Public proof-of-concept exploits, indexed by CVE. - cve.codepwn.win + {SUBTITLE} + {host()} {blocks} @@ -149,22 +154,22 @@ def social(counts: dict) -> str: ) ) return f""" + role="img" aria-label="{BRAND}"> {sweep(1000, 300, 220, 0.26)} CVE RADAR + fill="{FG}" letter-spacing="3">{BRAND} - Public proof-of-concept exploits, indexed by CVE. + {SUBTITLE} {blocks} cve.codepwn.win + letter-spacing="1">{host()} """ diff --git a/.github/workflows/audit_poc_links.yml b/.github/workflows/audit_poc_links.yml index 329ca96e4bb..9e89e108439 100644 --- a/.github/workflows/audit_poc_links.yml +++ b/.github/workflows/audit_poc_links.yml @@ -40,7 +40,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add --all -- \ .github/link_audit_state.json \ diff --git a/.github/workflows/hot_cves.yml b/.github/workflows/hot_cves.yml index 3abcbe13fad..4baa24ec039 100644 --- a/.github/workflows/hot_cves.yml +++ b/.github/workflows/hot_cves.yml @@ -47,7 +47,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add --all -- \ README.md \ index/trending.json \ diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 59e7bf31575..8e72d1aa676 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -1,4 +1,4 @@ -name: Build & Deploy CVE Radar +name: Build & Deploy PoC Index on: schedule: @@ -32,6 +32,9 @@ jobs: - name: Build site data run: python3 scripts/build_site.py + - name: Build robots.txt and sitemap.xml + run: python3 scripts/build_seo.py + - name: Verify published counts run: | python3 .github/hero.py diff --git a/.github/workflows/sync_collections.yml b/.github/workflows/sync_collections.yml index 8b975ba4c05..cd40a1815a6 100644 --- a/.github/workflows/sync_collections.yml +++ b/.github/workflows/sync_collections.yml @@ -40,7 +40,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add --all -- \ README.md \ docs/hero.svg \ diff --git a/.github/workflows/sync_cve_pocs.yml b/.github/workflows/sync_cve_pocs.yml index 2258a39e702..b7675033b50 100644 --- a/.github/workflows/sync_cve_pocs.yml +++ b/.github/workflows/sync_cve_pocs.yml @@ -42,7 +42,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add --all -- \ .github/cve_sync_state.json \ diff --git a/.github/workflows/sync_exploits.yml b/.github/workflows/sync_exploits.yml index a70a38f4513..55fa3a990ca 100644 --- a/.github/workflows/sync_exploits.yml +++ b/.github/workflows/sync_exploits.yml @@ -40,7 +40,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add --all -- \ README.md \ docs/hero.svg \ diff --git a/.github/workflows/sync_metadata.yml b/.github/workflows/sync_metadata.yml index e2bf9d2b661..26289848af4 100644 --- a/.github/workflows/sync_metadata.yml +++ b/.github/workflows/sync_metadata.yml @@ -57,7 +57,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add -- \ README.md \ cves/ \ diff --git a/.github/workflows/sync_nuclei.yml b/.github/workflows/sync_nuclei.yml index fa89c1e4c35..4ecb526d6fb 100644 --- a/.github/workflows/sync_nuclei.yml +++ b/.github/workflows/sync_nuclei.yml @@ -40,7 +40,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add --all -- \ README.md \ docs/hero.svg \ diff --git a/.github/workflows/sync_pocingithub.yml b/.github/workflows/sync_pocingithub.yml index d4fc61c2559..58d1626a75a 100644 --- a/.github/workflows/sync_pocingithub.yml +++ b/.github/workflows/sync_pocingithub.yml @@ -46,7 +46,7 @@ jobs: - name: Check for changes and commit run: | git config user.name 'CVE-BOT' - git config user.email 'cve@codepwn.win' + git config user.email 'bot@pocindex.io' git add --all -- \ README.md \ docs/hero.svg \ diff --git a/.gitignore b/.gitignore index 5a53b5e2b91..11fbe576a69 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ docs/nuclei.json docs/cve_metadata.json docs/epss.json docs/api/v1/cve/ +docs/robots.txt +docs/sitemap.xml __pycache__/ # Assistant tooling. None of it belongs in the repository. diff --git a/README.md b/README.md index ed4d97a4fb7..af74d844ef4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@
-CVE Radar +PoC Index -[![last sync](https://img.shields.io/badge/last%20sync-01%20Sep%202026%2023%3A15%20UTC-2f81f7?style=flat-square&labelColor=161b22)](https://github.com/0xMarcio/cve/commits/main) [![CI](https://img.shields.io/github/actions/workflow/status/0xMarcio/cve/hot_cves.yml?style=flat-square&label=CI&color=2f81f7&labelColor=161b22&_=202609012315)](https://github.com/0xMarcio/cve/actions/workflows/hot_cves.yml) [![CVEs with PoCs](https://img.shields.io/badge/CVEs%20with%20PoCs-82%2C409-2f81f7?style=flat-square&labelColor=161b22)](https://cve.codepwn.win/) [![known exploited](https://img.shields.io/badge/known%20exploited-1%2C181-f85149?style=flat-square&labelColor=161b22)](https://cve.codepwn.win/) [![stars](https://img.shields.io/github/stars/0xMarcio/cve?style=flat-square&label=stars&color=e3b341&labelColor=161b22&_=202609012315)](https://github.com/0xMarcio/cve/stargazers) +[![last sync](https://img.shields.io/badge/last%20sync-01%20Sep%202026%2023%3A15%20UTC-2f81f7?style=flat-square&labelColor=161b22)](https://github.com/0xMarcio/cve/commits/main) [![CI](https://img.shields.io/github/actions/workflow/status/0xMarcio/cve/hot_cves.yml?style=flat-square&label=CI&color=2f81f7&labelColor=161b22&_=202609012315)](https://github.com/0xMarcio/cve/actions/workflows/hot_cves.yml) [![CVEs with PoCs](https://img.shields.io/badge/CVEs%20with%20PoCs-82%2C409-2f81f7?style=flat-square&labelColor=161b22)](https://pocindex.io/) [![known exploited](https://img.shields.io/badge/known%20exploited-1%2C181-f85149?style=flat-square&labelColor=161b22)](https://pocindex.io/) [![stars](https://img.shields.io/github/stars/0xMarcio/cve?style=flat-square&label=stars&color=e3b341&labelColor=161b22&_=202609012315)](https://github.com/0xMarcio/cve/stargazers) -Search CVE Radar +Search PoC Index
@@ -107,25 +107,25 @@ Every file is plain JSON on the CDN. No key, no rate limit. ```bash # everything the index knows about one CVE -curl -s https://cve.codepwn.win/CVE_list.json \ +curl -s https://pocindex.io/CVE_list.json \ | jq '.[] | select(.cve == "CVE-2021-44228") | {cve, poc: (.poc | length), nuclei, msf, edb, vulhub, collections}' # every published CVSS assessment plus vetted advisory links -curl -s https://cve.codepwn.win/cve_metadata.json | jq '."CVE-2021-44228"' +curl -s https://pocindex.io/cve_metadata.json | jq '."CVE-2021-44228"' # likelihood of exploitation in the next 30 days -curl -s https://cve.codepwn.win/epss.json | jq '."CVE-2021-44228"' +curl -s https://pocindex.io/epss.json | jq '."CVE-2021-44228"' # stars and last push for one PoC repository; repository keys are lowercased -curl -s https://cve.codepwn.win/repo_meta.json | jq '."sfewer-r7/cve-2026-55040"' +curl -s https://pocindex.io/repo_meta.json | jq '."sfewer-r7/cve-2026-55040"' ``` What CISA says is being exploited, that also has a PoC here, ranked by how likely each is to be used next: ```bash -curl -s https://cve.codepwn.win/kev.json -o kev.json -curl -s https://cve.codepwn.win/epss.json -o epss.json +curl -s https://pocindex.io/kev.json -o kev.json +curl -s https://pocindex.io/epss.json -o epss.json jq -n --slurpfile kev kev.json --slurpfile epss epss.json \ '[$kev[0] | keys[] | select($epss[0][.]) | {cve: ., epss: $epss[0][.][0]}] | sort_by(-.epss) | .[:10]' @@ -133,13 +133,13 @@ jq -n --slurpfile kev kev.json --slurpfile epss epss.json \ | Endpoint | Holds | | --- | --- | -| [`CVE_list.json`](https://cve.codepwn.win/CVE_list.json) | Every CVE with a linked PoC, its description and its `poc`, `nuclei`, `msf`, `edb`, `vulhub` and `collections` links | -| [`cve_metadata.json`](https://cve.codepwn.win/cve_metadata.json) | NVD CVSS v2.0, v3.0, v3.1 and v4.0 assessments with vectors and vetted advisory links | -| [`epss.json`](https://cve.codepwn.win/epss.json) | Exploitation probability and percentile, for nearly every CVE indexed | -| [`nuclei.json`](https://cve.codepwn.win/nuclei.json) | Template metadata for the CVEs covered by a runnable Nuclei check | -| [`kev.json`](https://cve.codepwn.win/kev.json) | CISA known exploited, keyed by CVE id | -| [`repo_meta.json`](https://cve.codepwn.win/repo_meta.json) | Stars and last push date per PoC repository, keys lowercased | -| [`trending_poc.json`](https://cve.codepwn.win/trending_poc.json) | Trending repositories plus index totals | +| [`CVE_list.json`](https://pocindex.io/CVE_list.json) | Every CVE with a linked PoC, its description and its `poc`, `nuclei`, `msf`, `edb`, `vulhub` and `collections` links | +| [`cve_metadata.json`](https://pocindex.io/cve_metadata.json) | NVD CVSS v2.0, v3.0, v3.1 and v4.0 assessments with vectors and vetted advisory links | +| [`epss.json`](https://pocindex.io/epss.json) | Exploitation probability and percentile, for nearly every CVE indexed | +| [`nuclei.json`](https://pocindex.io/nuclei.json) | Template metadata for the CVEs covered by a runnable Nuclei check | +| [`kev.json`](https://pocindex.io/kev.json) | CISA known exploited, keyed by CVE id | +| [`repo_meta.json`](https://pocindex.io/repo_meta.json) | Stars and last push date per PoC repository, keys lowercased | +| [`trending_poc.json`](https://pocindex.io/trending_poc.json) | Trending repositories plus index totals | | [`cves/2026/CVE-2026-68138.md`](cves/2026/CVE-2026-68138.md) | Markdown copy of one CVE, one directory per year | CVSS rows are `[version, score, severity, vector, source, assessment type]`. diff --git a/docs/CNAME b/docs/CNAME index 4034a05272f..fc709dce326 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -cve.codepwn.win \ No newline at end of file +pocindex.io diff --git a/docs/hero.svg b/docs/hero.svg index 39f1d244a13..71e12417e98 100644 --- a/docs/hero.svg +++ b/docs/hero.svg @@ -1,5 +1,5 @@ + role="img" aria-label="PoC Index, 82,409 CVEs with public proof-of-concept exploits"> @@ -13,10 +13,10 @@ CVE RADAR + fill="#e6edf3" letter-spacing="2">PoC Index Public proof-of-concept exploits, indexed by CVE. - cve.codepwn.win + pocindex.io 170,946 diff --git a/docs/index.html b/docs/index.html index 03996b7b286..c8d7e2d777b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,22 +3,46 @@ - CVE PoC Hub - + + PoC Index: Search CVE Proof-of-Concept Exploits + + - + - - + + - - + + - + - + + + @@ -35,7 +59,7 @@
-

CVE RADAR

+

PoC Index

Public proof-of-concept exploits, indexed by CVE.

@@ -108,6 +132,9 @@ sources: nvd · cve program · github · github advisories · cisa vulnrichment · cisa kev · first epss + diff --git a/docs/logic.js b/docs/logic.js index e48174bda74..a81ea9d101f 100644 --- a/docs/logic.js +++ b/docs/logic.js @@ -702,7 +702,38 @@ function idleStatus() { return ''; } +/* ---- url state --------------------------------------------------------- */ + +// A result was unlinkable: every search lived in memory, so nobody could cite +// one and no crawler ever saw a second page. The query rides in ?q= now, which +// is also the route the SearchAction in index.html declares. +function readURLState() { + const params = new URLSearchParams(location.search); + const query = (params.get('q') || '').trim(); + if (query) { + state.query = query; + el.input.value = query; + } + if (params.get('kev') === '1') state.kevOnly = true; +} + +// replaceState, not pushState: typing a query should not bury the back button +// under one history entry per keystroke. +let urlTimer = null; +function syncURL() { + clearTimeout(urlTimer); + urlTimer = setTimeout(() => { + const params = new URLSearchParams(); + if (state.query.length >= MIN_QUERY) params.set('q', state.query); + if (state.kevOnly) params.set('kev', '1'); + const search = params.toString(); + const next = search ? `${location.pathname}?${search}` : location.pathname; + if (next !== location.pathname + location.search) history.replaceState(null, '', next); + }, 300); +} + function render() { + syncURL(); if (state.query.length < MIN_QUERY && !hasActiveFilters()) { el.status.textContent = state.ready ? idleStatus() : 'loading index…'; renderTrending(); @@ -914,6 +945,8 @@ async function loadJSON(url, options) { } catch (err) { console.warn(err.message); } + readURLState(); + syncFilterControls(); paintHeroStats(); render(); diff --git a/docs/search.svg b/docs/search.svg index 8689ba54b65..8f996b13c83 100644 --- a/docs/search.svg +++ b/docs/search.svg @@ -1,5 +1,5 @@ + role="img" aria-label="Search PoC Index"> diff --git a/docs/social-card.svg b/docs/social-card.svg index 1133f78ee8d..92f1a07c7ec 100644 --- a/docs/social-card.svg +++ b/docs/social-card.svg @@ -1,5 +1,5 @@ + role="img" aria-label="PoC Index"> @@ -14,7 +14,7 @@ CVE RADAR + fill="#e6edf3" letter-spacing="3">PoC Index Public proof-of-concept exploits, indexed by CVE. @@ -31,5 +31,5 @@ fill="#6e7681" letter-spacing="1.6">KNOWN EXPLOITED cve.codepwn.win + letter-spacing="1">pocindex.io diff --git a/docs/style.css b/docs/style.css index e2e8cdf7048..722ed96749a 100644 --- a/docs/style.css +++ b/docs/style.css @@ -1,4 +1,4 @@ -/* CVE PoC Hub. GitHub Primer dark_default tokens, per the refit handoff. */ +/* PoC Index. GitHub Primer dark_default tokens, per the refit handoff. */ :root { --canvas: #0d1117; --canvas-subtle: #161b22; @@ -520,7 +520,9 @@ a.chip:hover { color: var(--fg); border-color: var(--fg-muted); } font: 400 11px/1.6 var(--mono); color: var(--fg-muted); } -.site-footer span:last-child { margin-left: auto; } +.site-footer .container:first-child span:last-child { margin-left: auto; } +.site-legal { padding-top: 8px; } +.site-legal span { margin-left: 0; opacity: .72; } /* ---- narrow viewports --------------------------------------------------- */ diff --git a/scripts/brand.py b/scripts/brand.py new file mode 100644 index 00000000000..f84e5c52b39 --- /dev/null +++ b/scripts/brand.py @@ -0,0 +1,46 @@ +"""Every user-facing name and URL, in one place. + +The site header, the browser title, the social card, the README banner and the +GitHub About box all have to say the same thing. When they disagree Google picks +its own site name, and mentions stop compounding into one brand. Change a string +here and every generated surface follows. +""" + +BRAND = "PoC Index" +# Short line under the wordmark, on the site and on both cards. +SUBTITLE = "Public proof-of-concept exploits, indexed by CVE." +TITLE = f"{BRAND}: Search CVE Proof-of-Concept Exploits" +# og:description and twitter:description. Shorter than DESCRIPTION because a +# social card truncates far earlier than a search result does. +SOCIAL_DESCRIPTION = ( + f"{SUBTITLE} Searchable, refreshed hourly, no signup." +) +DESCRIPTION = ( + "Search 82,000+ public CVE proof-of-concept exploits from GitHub, Nuclei, " + "ExploitDB, Metasploit and Vulhub. Updated hourly. Free JSON API, no signup." +) +# Shown under the GitHub About box and in search results for the repository. +# Deliberately unlike the upstream wording: 167 forks carry the old description +# verbatim, so a generic line ranks the copies alongside the original. +REPO_DESCRIPTION = ( + "Search public CVE proof-of-concept exploits from GitHub, Nuclei, ExploitDB, " + "Metasploit and Vulhub. 82,000+ CVEs with PoCs, updated hourly with CVSS, KEV " + "and EPSS. Free JSON API, no signup." +) + +# Flip to https://pocindex.io only after DNS resolves and GitHub Pages has issued +# the certificate. A canonical pointing at a host that does not answer yet drops +# the page out of the index, so this trails the DNS cutover, it does not lead it. +SITE = "https://pocindex.io" +SLUG = "0xMarcio/cve" + +# CVE is a MITRE trademark, so it stays out of the brand and carries a notice. +LEGAL = ( + "CVE is a trademark of The MITRE Corporation. " + f"{BRAND} is not affiliated with or endorsed by MITRE or the CVE Program." +) + + +def host() -> str: + """SITE without the scheme, for the places that display a bare domain.""" + return SITE.split("://", 1)[-1].rstrip("/") diff --git a/scripts/build_seo.py b/scripts/build_seo.py new file mode 100644 index 00000000000..f4b0367bd23 --- /dev/null +++ b/scripts/build_seo.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Write docs/robots.txt and docs/sitemap.xml. + +Both files were 404 before this, so nothing told a crawler where the site ends +or how often it changes. Everything here is derived from scripts/brand.py, so a +domain move is one edit rather than a hunt through the tree. +""" + +from __future__ import annotations + +import os +import sys +from datetime import datetime, timezone + +ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) +sys.path.insert(0, os.path.join(ROOT, "scripts")) + +from brand import SITE + +DOCS = os.path.join(ROOT, "docs") +ROBOTS = os.path.join(DOCS, "robots.txt") +SITEMAP = os.path.join(DOCS, "sitemap.xml") + +# Googlebot renders the page before it indexes it, so anything the first paint +# needs has to stay crawlable. These three are small and carry the only server +# rendered content on the page, the trending table and the header counts. +RENDER_CRITICAL = ("trending_poc.json", "kev.json", "stats.json") + +# These only feed the search box, which a crawler never types into. Together they +# are over 13 MB on the wire, which is crawl budget spent on nothing. Blocking +# them here does not touch curl: robots.txt binds crawlers, not clients. +BULK_PAYLOADS = ( + "CVE_list.json", + "cve_metadata.json", + "epss.json", + "repo_meta.json", + "nuclei.json", +) + + +def robots() -> str: + lines = ["User-agent: *", "Allow: /"] + lines += [f"Disallow: /{name}" for name in BULK_PAYLOADS] + lines += ["", f"Sitemap: {SITE}/sitemap.xml", ""] + return "\n".join(lines) + + +def sitemap(urls: list[tuple[str, str]]) -> str: + body = "".join( + f" \n {loc}\n {lastmod}\n \n" + for loc, lastmod in urls + ) + return ( + '\n' + '\n' + f"{body}" + "\n" + ) + + +def main() -> int: + today = datetime.now(timezone.utc).strftime("%Y-%m-%d") + urls = [(f"{SITE}/", today)] + + os.makedirs(DOCS, exist_ok=True) + with open(ROBOTS, "w", encoding="utf-8") as handle: + handle.write(robots()) + with open(SITEMAP, "w", encoding="utf-8") as handle: + handle.write(sitemap(urls)) + + print(f"Wrote {ROBOTS} and {SITEMAP} ({len(urls)} URL) for {SITE}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())