From 62e263fa5be10c98adb3f23aae76f6516789d94e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 30 Sep 2026 22:30:09 +0300 Subject: [PATCH 1/3] Autolink CVE-NNNN-NNNNN to cve.org --- src/layouts/BlogPostLayout.astro | 2 ++ src/plugins/remark-python-refs.ts | 53 ++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/layouts/BlogPostLayout.astro b/src/layouts/BlogPostLayout.astro index a6ed15c..b5d2272 100644 --- a/src/layouts/BlogPostLayout.astro +++ b/src/layouts/BlogPostLayout.astro @@ -37,6 +37,7 @@ const groupMeta: Record = { "gh-repo": { label: "Repositories", order: 2 }, "gh-user": { label: "People", order: 3 }, "pypi": { label: "Packages", order: 4 }, + "cve": { label: "Security", order: 5 }, }; const sortedGroups = [...refGroups.entries()] @@ -195,6 +196,7 @@ const sortedGroups = [...refGroups.entries()] type === "gh-repo" && "bg-zinc-100 text-zinc-700 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700", type === "gh-user" && "bg-zinc-100 text-zinc-700 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700", type === "pypi" && "bg-emerald-50 text-emerald-700 hover:bg-emerald-100 dark:bg-emerald-900/20 dark:text-emerald-300 dark:hover:bg-emerald-900/40", + type === "cve" && "bg-rose-50 text-rose-700 hover:bg-rose-100 dark:bg-rose-900/20 dark:text-rose-300 dark:hover:bg-rose-900/40", ]} target="_blank" rel="noopener noreferrer" diff --git a/src/plugins/remark-python-refs.ts b/src/plugins/remark-python-refs.ts index de7713a..cd299c5 100644 --- a/src/plugins/remark-python-refs.ts +++ b/src/plugins/remark-python-refs.ts @@ -13,9 +13,12 @@ * - GitHub users/orgs (github.com/NAME — exactly 1 segment, not reserved) * - CVE references (nvd.nist.gov/vuln/detail/CVE-YYYY-NNNNN) * - Python releases (python.org/downloads/release/python-XXXX/) + * + * Bare "CVE-YYYY-NNNN" text (not already inside a link) is autolinked + * to the CVE record and rendered as a badge. */ import type { Root, Link, Paragraph, PhrasingContent } from "mdast"; -import { visit } from "unist-util-visit"; +import { SKIP, visit } from "unist-util-visit"; import { pythonIcon, docsIcon, @@ -35,6 +38,9 @@ const DOCS = /^https?:\/\/docs\.python\.org\//i; const PYPI = /^https?:\/\/pypi\.org\/project\/([^/]+)\/?/i; const GH_ISSUE = /^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/(issues|pull)\/(\d+)\/?/i; const CVE = /^https?:\/\/nvd\.nist\.gov\/vuln\/detail\/(CVE-[\d-]+)\/?/i; +/** Bare CVE IDs in plain text, e.g. "CVE-2026-19445" */ +const CVE_TEXT = /\bCVE-\d{4}-\d{4,}\b/g; +const cveUrl = (id: string) => `https://www.cve.org/CVERecord?id=${id}`; const PY_RELEASE = /^https?:\/\/(?:www\.)?python\.org\/downloads\/release\/(python-[\w.]+)\/?/i; const GITHUB = /^https?:\/\/github\.com\/([\w.-]+)(?:\/([\w.-]+))?\/?$/i; @@ -330,6 +336,51 @@ export default function remarkPythonRefs() { } }); + // Pass 3: Autolink bare CVE IDs in text → badges + visit(tree, (node: any, index, parent: any) => { + // Don't touch text that is already a link (or a reference definition), + // or headings — Astro builds heading ids from text nodes only, so + // injecting HTML there would change the anchor slug. + if ( + node.type === "link" || + node.type === "linkReference" || + node.type === "definition" || + node.type === "heading" + ) { + return SKIP; + } + if (node.type !== "text" || index == null || !parent) return; + + const value: string = node.value; + CVE_TEXT.lastIndex = 0; + if (!CVE_TEXT.test(value)) { + CVE_TEXT.lastIndex = 0; + return; + } + + const parts: any[] = []; + let lastIndex = 0; + CVE_TEXT.lastIndex = 0; + let m: RegExpExecArray | null; + while ((m = CVE_TEXT.exec(value)) !== null) { + if (m.index > lastIndex) { + parts.push({ type: "text", value: value.slice(lastIndex, m.index) }); + } + const id = m[0]; + const url = cveUrl(id); + collectRef("cve", id, url); + parts.push({ type: "html", value: buildBadgeHtml({ type: "cve", icon: shieldIcon, label: id, url }) }); + lastIndex = m.index + m[0].length; + } + if (lastIndex < value.length) { + parts.push({ type: "text", value: value.slice(lastIndex) }); + } + + parent.children.splice(index, 1, ...parts); + // Continue after the nodes we just inserted + return index + parts.length; + }); + // Expose collected references via remarkPluginFrontmatter if (!file.data.astro) file.data.astro = {}; if (!file.data.astro.frontmatter) file.data.astro.frontmatter = {}; From 77aa956d6c08be6d7eadbfcf0f71c65af2319bd7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 30 Sep 2026 22:42:19 +0300 Subject: [PATCH 2/3] Autolink gh-NNNNN to python/cpython issue --- src/plugins/remark-python-refs.ts | 38 ++++++++++++++++--------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/plugins/remark-python-refs.ts b/src/plugins/remark-python-refs.ts index cd299c5..04351e7 100644 --- a/src/plugins/remark-python-refs.ts +++ b/src/plugins/remark-python-refs.ts @@ -14,8 +14,8 @@ * - CVE references (nvd.nist.gov/vuln/detail/CVE-YYYY-NNNNN) * - Python releases (python.org/downloads/release/python-XXXX/) * - * Bare "CVE-YYYY-NNNN" text (not already inside a link) is autolinked - * to the CVE record and rendered as a badge. + * Bare "gh-NNNN" and "CVE-YYYY-NNNN" text (not already inside a link + * or heading) is autolinked and rendered as a badge. */ import type { Root, Link, Paragraph, PhrasingContent } from "mdast"; import { SKIP, visit } from "unist-util-visit"; @@ -38,9 +38,14 @@ const DOCS = /^https?:\/\/docs\.python\.org\//i; const PYPI = /^https?:\/\/pypi\.org\/project\/([^/]+)\/?/i; const GH_ISSUE = /^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/(issues|pull)\/(\d+)\/?/i; const CVE = /^https?:\/\/nvd\.nist\.gov\/vuln\/detail\/(CVE-[\d-]+)\/?/i; -/** Bare CVE IDs in plain text, e.g. "CVE-2026-19445" */ -const CVE_TEXT = /\bCVE-\d{4}-\d{4,}\b/g; -const cveUrl = (id: string) => `https://www.cve.org/CVERecord?id=${id}`; + +/** + * Bare references in plain text that get autolinked (outside links, + * headings and code): + * - "gh-156293" → python/cpython issue + * - "CVE-2026-19445" → cve.org record + */ +const BARE_REF = /\b(?:(CVE-\d{4}-\d{4,})|gh-(\d+))\b/g; const PY_RELEASE = /^https?:\/\/(?:www\.)?python\.org\/downloads\/release\/(python-[\w.]+)\/?/i; const GITHUB = /^https?:\/\/github\.com\/([\w.-]+)(?:\/([\w.-]+))?\/?$/i; @@ -336,7 +341,7 @@ export default function remarkPythonRefs() { } }); - // Pass 3: Autolink bare CVE IDs in text → badges + // Pass 3: Autolink bare gh-NNNN issue refs and CVE IDs in text → badges visit(tree, (node: any, index, parent: any) => { // Don't touch text that is already a link (or a reference definition), // or headings — Astro builds heading ids from text nodes only, so @@ -352,26 +357,23 @@ export default function remarkPythonRefs() { if (node.type !== "text" || index == null || !parent) return; const value: string = node.value; - CVE_TEXT.lastIndex = 0; - if (!CVE_TEXT.test(value)) { - CVE_TEXT.lastIndex = 0; - return; - } - const parts: any[] = []; let lastIndex = 0; - CVE_TEXT.lastIndex = 0; + BARE_REF.lastIndex = 0; let m: RegExpExecArray | null; - while ((m = CVE_TEXT.exec(value)) !== null) { + while ((m = BARE_REF.exec(value)) !== null) { if (m.index > lastIndex) { parts.push({ type: "text", value: value.slice(lastIndex, m.index) }); } - const id = m[0]; - const url = cveUrl(id); - collectRef("cve", id, url); - parts.push({ type: "html", value: buildBadgeHtml({ type: "cve", icon: shieldIcon, label: id, url }) }); + const [label, cve, ghNum] = m; + const match: Match = cve + ? { type: "cve", icon: shieldIcon, label, url: `https://www.cve.org/CVERecord?id=${cve}` } + : { type: "gh-issue", icon: issueIcon, label, url: `https://github.com/python/cpython/issues/${ghNum}` }; + collectRef(match.type, match.label, match.url); + parts.push({ type: "html", value: buildBadgeHtml(match) }); lastIndex = m.index + m[0].length; } + if (parts.length === 0) return; if (lastIndex < value.length) { parts.push({ type: "text", value: value.slice(lastIndex) }); } From 51733e8f2bcd39377c2f8784179910c5d90de9df Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:28:10 +0300 Subject: [PATCH 3/3] Use autolinked IDs --- content/posts/python-31022-31117/index.md | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/content/posts/python-31022-31117/index.md b/content/posts/python-31022-31117/index.md index 59d8d62..9fb01d6 100644 --- a/content/posts/python-31022-31117/index.md +++ b/content/posts/python-31022-31117/index.md @@ -30,29 +30,29 @@ Python 3.12.15 is also a source-only security release, with security support con ## Security content in all five releases -* [gh-158446](https://github.com/python/cpython/issues/158446): Fix crashes or incorrect output when formatting float or complex values with precision close to INT_MAX. -* [CVE-2026-19553](https://www.cve.org/CVERecord?id=CVE-2026-19553) — [gh-156793](https://github.com/python/cpython/issues/156793): `ssl.SSLContext.wrap_bio()` now validates its `server_side`, `server_hostname`, and `session` arguments. `asyncio` also validates TLS `server_hostname` arguments. On Python 3.10, 3.11, and 3.12, missing hostnames with `check_hostname` enabled emit `DeprecationWarning` for compatibility; they raise `ValueError` on Python 3.13 and later. -* [CVE-2026-82049](https://www.cve.org/CVERecord?id=CVE-2026-82049) — [gh-157190](https://github.com/python/cpython/issues/157190): Fix a tarfile extraction-filter vulnerability involving hard links to symbolic links that could expose files outside the destination and change their permissions or modification times. -* [gh-157953](https://github.com/python/cpython/issues/157953): Update bundled libexpat to version 2.8.5. -* [CVE-2026-15310](https://www.cve.org/CVERecord?id=CVE-2026-15310) — [gh-156002](https://github.com/python/cpython/issues/156002): Bound `zipfile` decompression per read for bzip2 and LZMA members, and for Zstandard members on Python 3.14, preventing unbounded allocations from small compressed members. Third-party decompressors supplied by monkey-patching `_get_decompressor()` that lack `needs_input` and two-argument `decompress()` remain vulnerable. -* [CVE-2026-19672](https://www.cve.org/CVERecord?id=CVE-2026-19672) — [gh-155999](https://github.com/python/cpython/issues/155999): Prevent tarfile extraction filters from creating directories outside the destination for paths that leave it and then return. -* [CVE-2026-19445](https://www.cve.org/CVERecord?id=CVE-2026-19445) — [gh-156293](https://github.com/python/cpython/issues/156293): Fix an ssl crash when an SNI callback switches contexts and the original callback context is no longer referenced. -* [CVE-2026-17084](https://www.cve.org/CVERecord?id=CVE-2026-17084) — [gh-155292](https://github.com/python/cpython/issues/155292): Restrict stringprep and the IDNA codec to Unicode codepoint attributes defined by RFC 3454. -* [CVE-2026-15806](https://www.cve.org/CVERecord?id=CVE-2026-15806) — [gh-155694](https://github.com/python/cpython/issues/155694): Scope urllib.request HTTPPasswordMgr credentials by URL scheme to prevent HTTPS credentials from being used for matching HTTP URLs. +* gh-158446: Fix crashes or incorrect output when formatting float or complex values with precision close to INT_MAX. +* CVE-2026-19553 — gh-156793: `ssl.SSLContext.wrap_bio()` now validates its `server_side`, `server_hostname`, and `session` arguments. `asyncio` also validates TLS `server_hostname` arguments. On Python 3.10, 3.11, and 3.12, missing hostnames with `check_hostname` enabled emit `DeprecationWarning` for compatibility; they raise `ValueError` on Python 3.13 and later. +* CVE-2026-82049 — gh-157190: Fix a tarfile extraction-filter vulnerability involving hard links to symbolic links that could expose files outside the destination and change their permissions or modification times. +* gh-157953: Update bundled libexpat to version 2.8.5. +* CVE-2026-15310 — gh-156002: Bound `zipfile` decompression per read for bzip2 and LZMA members, and for Zstandard members on Python 3.14, preventing unbounded allocations from small compressed members. Third-party decompressors supplied by monkey-patching `_get_decompressor()` that lack `needs_input` and two-argument `decompress()` remain vulnerable. +* CVE-2026-19672 — gh-155999: Prevent tarfile extraction filters from creating directories outside the destination for paths that leave it and then return. +* CVE-2026-19445 — gh-156293: Fix an ssl crash when an SNI callback switches contexts and the original callback context is no longer referenced. +* CVE-2026-17084 — gh-155292: Restrict stringprep and the IDNA codec to Unicode codepoint attributes defined by RFC 3454. +* CVE-2026-15806 — gh-155694: Scope urllib.request HTTPPasswordMgr credentials by URL scheme to prevent HTTPS credentials from being used for matching HTTP URLs. ## Additional security content by version ### Python 3.10.22, 3.11.17, 3.12.15 and 3.13.16 -* [CVE-2026-87910](https://www.cve.org/CVERecord?id=CVE-2026-87910) — [gh-157265](https://github.com/python/cpython/issues/157265): Apply tarfile extraction filters when a link falls back to extracting an archive member, skipping members rejected by the filter. +* CVE-2026-87910 — gh-157265: Apply tarfile extraction filters when a link falls back to extracting an archive member, skipping members rejected by the filter. ### Python 3.13.16 and 3.14.8 -* [gh-158010](https://github.com/python/cpython/issues/158010): Update bundled OpenSSL to [3.5.9](https://openssl-library.org/news/secadv/20260929.txt). In Python 3.13.16 this applies to Windows, macOS and Android, moving from OpenSSL 3.0.21 to the 3.5 LTS series. In Python 3.14.8 it applies to Windows, macOS, Android and iOS. The source-only Python 3.10–3.12 releases do not bundle OpenSSL. +* gh-158010: Update bundled OpenSSL to [3.5.9](https://openssl-library.org/news/secadv/20260929.txt). In Python 3.13.16 this applies to Windows, macOS and Android, moving from OpenSSL 3.0.21 to the 3.5 LTS series. In Python 3.14.8 it applies to Windows, macOS, Android and iOS. The source-only Python 3.10–3.12 releases do not bundle OpenSSL. ### Python 3.10.22 -* [gh-149018](https://github.com/python/cpython/issues/149018): Improve protection against XML hash-flooding attacks in `xml.parsers.expat` and `xml.etree.ElementTree` when compiled with libexpat 2.8.0 or later. +* gh-149018: Improve protection against XML hash-flooding attacks in `xml.parsers.expat` and `xml.etree.ElementTree` when compiled with libexpat 2.8.0 or later. This XML hash-flooding protection was already included in Python 3.11.16.