From 6822fdbfbc0ef53811a2c4a0b2abc953bc170ae7 Mon Sep 17 00:00:00 2001 From: 0xMarcio Date: Mon, 31 Aug 2026 00:22:44 +0200 Subject: [PATCH] Fix three ways the index misread its own data An advisory copied from a GitHub Security Advisory opens with "### Impact" or "### Summary". The section parser treated any heading as a boundary, so those descriptions ended themselves and reached the site empty. Only the four headings an entry actually uses end a section now, and leading heading markers are stripped when the text is rendered. A trailing slash made exploit-db.com/exploits/41034 and .../41034/ two different links, which put 53 duplicate rows in front of readers. github.com also serves attachments and advisories, and 103 of those were being parsed as owner/repo -- rendering "user-attachments / files" with an empty star column instead of the PoC archive they point to. --- docs/logic.js | 7 ++++++- scripts/build_site.py | 18 +++++++++++++++--- scripts/update_cves.py | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/logic.js b/docs/logic.js index 3d5887fa32f..29f2da09623 100644 --- a/docs/logic.js +++ b/docs/logic.js @@ -72,9 +72,14 @@ function shortAge(hours) { return Math.round(days / 365) + 'y'; } +// github.com serves more than repositories; an attachment or an advisory has +// no owner and no stars, and reads as nonsense in an owner / repo row. +const NOT_A_REPO = new Set(['user-attachments', 'advisories', 'security', 'orgs', + 'apps', 'marketplace', 'sponsors', 'topics', 'collections', 'settings', 'notifications']); + function repoFromUrl(url) { const match = /^https?:\/\/(?:www\.)?github\.com\/([^/#?]+)\/([^/#?]+)/i.exec(url || ''); - if (!match) return null; + if (!match || NOT_A_REPO.has(match[1].toLowerCase())) return null; return { owner: match[1], repo: match[2].replace(/\.git$/i, '') }; } diff --git a/scripts/build_site.py b/scripts/build_site.py index aa175fd01e3..ec4552b5a00 100644 --- a/scripts/build_site.py +++ b/scripts/build_site.py @@ -31,18 +31,30 @@ def load_blacklist() -> set[str]: 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")] + lines = [ + re.sub(r"^#{1,6}\s+", "", line.lstrip("- ")).rstrip() + for line in text.split("\n") + ] return "\n".join(line for line in lines if line) +SECTION_HEADERS = ("### Description", "### POC", "#### Reference", "#### Github") + + def parse_sections(content: str) -> Dict[str, str]: + """Split an entry on its own headings only. + + CVE text lifted from a GitHub advisory often opens with "### Impact" or + "### Summary"; treating any heading as a boundary let a description end + itself and vanish from the index. + """ 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 header in SECTION_HEADERS: if current_header is not None: sections[current_header] = "\n".join(buffer).strip() current_header = header @@ -81,7 +93,7 @@ def is_blacklisted(url: str, blacklist: Collection[str]) -> bool: def link_key(url: str) -> str: parsed = urlparse(url) if (parsed.hostname or "").lower() not in {"github.com", "www.github.com"}: - return url + return url.rstrip("/") parts = [part for part in parsed.path.split("/") if part] if len(parts) < 2: return url diff --git a/scripts/update_cves.py b/scripts/update_cves.py index 711b9938fea..600f96c168e 100755 --- a/scripts/update_cves.py +++ b/scripts/update_cves.py @@ -116,7 +116,7 @@ def stable_unique(values: Iterable[str]) -> list[str]: def poc_link_key(url: str) -> str: parsed = urlparse(url) if (parsed.hostname or "").lower() not in {"github.com", "www.github.com"}: - return url + return url.rstrip("/") parts = [part for part in parsed.path.split("/") if part] if len(parts) < 2: return url