Meet the query where it is written: slang, names, typos

Security slang and NVD prose rarely share a word. "sqli" found 843 CVEs
where "sql injection" found 12,719, "lpe" 80 against 1,311 for "privilege
escalation". A term now expands to its phrases and an entry matches on any
of them, with the best-scoring form counted.

A vulnerability searched by the name it is known by leads with the record
it belongs to, whatever the prose and the repository names say: ghostcat
put a different record first, moveit a newer one. Forty names are mapped.

A term with no word in the index at all is nearly always a typo. The
closest word one edit away stands in, weighed by how many records use it,
and the status line says which: citrx finds citrix, fortinte fortinet,
l0g4j log4j.

CVE pages carry BreadcrumbList markup for the home, year and block trail,
and the site publishes an OpenSearch descriptor so a browser can add it
as a search engine from the address bar.
This commit is contained in:
0xMarcio committed 2026-09-04 02:59:24 +02:00
1 parent 5f918199ab
commit aae0c789a3
5 files changed
+202 -34

No files matched your search

+15
View File
@@ -386,6 +386,20 @@ def page(entry: dict, data: dict) -> str:
}
)
year, block = block_of(cid)
trail = json_ld(
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": BRAND, "item": f"{SITE}/"},
{"@type": "ListItem", "position": 2, "name": f"CVE-{year}", "item": f"{SITE}/{year}"},
{"@type": "ListItem", "position": 3, "name": f"CVE-{year}-{block}", "item": f"{SITE}/CVE-{year}-{block}"},
{"@type": "ListItem", "position": 4, "name": cid, "item": url},
],
}
)
return f"""<!DOCTYPE html>
<html lang="en">
<head>
@@ -411,6 +425,7 @@ def page(entry: dict, data: dict) -> str:
{FONTS}
<link rel="stylesheet" href="/style.css"/>
<script type="application/ld+json">{ld}</script>
<script type="application/ld+json">{trail}</script>
</head>
<body>
{header(len(data["cves"]))}
+17 -1
View File
@@ -16,7 +16,7 @@ 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
from brand import BRAND, DESCRIPTION, SITE
DOCS = os.path.join(ROOT, "docs")
ROBOTS = os.path.join(DOCS, "robots.txt")
@@ -48,6 +48,20 @@ def robots() -> str:
return "\n".join(lines)
def opensearch() -> str:
"""The descriptor a browser reads to offer the site as a search engine."""
return (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">\n'
f" <ShortName>{BRAND}</ShortName>\n"
f" <Description>{DESCRIPTION}</Description>\n"
" <InputEncoding>UTF-8</InputEncoding>\n"
f' <Image width="16" height="16" type="image/x-icon">{SITE}/favicon.ico</Image>\n'
f' <Url type="text/html" method="get" template="{SITE}/?q={{searchTerms}}"/>\n'
"</OpenSearchDescription>\n"
)
def urlset(urls: list[tuple[str, str]]) -> str:
body = "".join(
f" <url>\n <loc>{loc}</loc>\n <lastmod>{lastmod}</lastmod>\n </url>\n"
@@ -79,6 +93,8 @@ def main() -> int:
with open(ROBOTS, "w", encoding="utf-8") as handle:
handle.write(robots())
with open(os.path.join(DOCS, "opensearch.xml"), "w", encoding="utf-8") as handle:
handle.write(opensearch())
# One sitemap per CVE year. The 50,000 URL cap makes some split necessary and
# the year is the split the data already has, which also means a crawler only