From aeeae636660a628e0653b4d8b5a0cb7dceeedf1d Mon Sep 17 00:00:00 2001 From: Karl Cardenas <29551334+karl-cardenas-coding@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:01:03 -0700 Subject: [PATCH] chore: Fix CVE URL logic (#4936) * docs: fix CVE url logic * chore: add missing line * chore: fix list data (cherry picked from commit 8fb75f8d3c97d9976cd6b5163c9feb847d1f1626) --- .../CveReportsTable/CveReportsTable.tsx | 44 +++++++++++++++---- utils/cves/index.js | 44 ++++++++++++++----- utils/helpers/urls.js | 28 ++++++++++++ utils/helpers/urls.test.ts | 39 ++++++++++++++++ 4 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 utils/helpers/urls.js create mode 100644 utils/helpers/urls.test.ts diff --git a/src/components/CveReportsTable/CveReportsTable.tsx b/src/components/CveReportsTable/CveReportsTable.tsx index 129ab04da2..f9c3aa178b 100644 --- a/src/components/CveReportsTable/CveReportsTable.tsx +++ b/src/components/CveReportsTable/CveReportsTable.tsx @@ -77,6 +77,31 @@ type CveDataUnion = vertexAirgap: MinimizedCve[]; }; +// generateCVEOfficialDetailsUrl returns a URL that is used to link to the official CVE report. +// The URL is generated based on the cveId. +// The function checks if the cveId starts with "ghsa" and returns a GitHub Security Advisory URL. Other formal sites can be added in the future. +// The default URL is the NVD official CVE report. +function generateCVEOfficialDetailsUrl(cveId: string) { + let url; + + // If cveId is empty, return the default reports page URL + if (!cveId) { + return "/security-bulletins/reports/"; + } + + switch (true) { + // GitHub Security Advisory + case cveId.toLocaleLowerCase().startsWith("ghsa"): + url = `https://github.com/advisories/${cveId.toLocaleLowerCase()}`; + break; + // Default CVE URL + default: + url = `https://nvd.nist.gov/vuln/detail/${cveId.toLocaleLowerCase()}`; + } + + return url; +} + export default function CveReportsTable() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); @@ -146,11 +171,13 @@ export default function CveReportsTable() { dataIndex: ["metadata", "cve"], key: "cve", sorter: (a, b) => a.metadata.cve.localeCompare(b.metadata.cve), - render: (cve: string, record) => ( - - {cve} - - ), + render: (cve: string, record) => { + return ( + + {cve} + + ); + }, }, { title: "Initial Pub Date", @@ -199,9 +226,10 @@ export default function CveReportsTable() { dataIndex: ["metadata", "cvssScore"], key: "baseScore", sorter: (a, b) => a.metadata.cvssScore - b.metadata.cvssScore, - render: (baseScore: number, record) => ( - {baseScore} - ), + render: (baseScore: number, record) => { + const url = generateCVEOfficialDetailsUrl(record.metadata.cve.toLocaleLowerCase()); + return {baseScore}; + }, }, { title: "Status", diff --git a/utils/cves/index.js b/utils/cves/index.js index 590dede895..aab3f61ba6 100644 --- a/utils/cves/index.js +++ b/utils/cves/index.js @@ -7,6 +7,7 @@ const { formatDateCveDetails } = require("../helpers/date"); const { escapeMDXSpecialChars } = require("../helpers/string"); const { generateMarkdownTable } = require("../helpers/affected-table"); const { generateRevisionHistory } = require("../helpers/revision-history"); +const { generateCVEOfficialDetailsUrl } = require("../helpers/urls"); async function getSecurityBulletins(payload) { const limit = 100; @@ -40,6 +41,21 @@ async function getSecurityBulletins(payload) { } } +// This function filters the items by UID and returns only the items that start with the keyword, such as "PA-", "VA-", etc. +function filterByUID(items, keyword) { + if (!Array.isArray(items)) { + throw new Error("Input must be an array of objects"); + } + + return items.filter((item) => { + if (!item.metadata || typeof item.metadata.uid !== "string") { + console.warn("Skipping item due to missing or invalid metadata.uid:", item); + return false; + } + return item.metadata.uid.startsWith(keyword); + }); +} + async function generateCVEs() { let GlobalCVEData = {}; @@ -153,19 +169,25 @@ async function generateCVEs() { ], }); + // There is no way to filter by product in the API, so we need to filter the results manually to get a list of CVEs for each product + const filterdPalette = filterByUID(palette.data, "PC-"); + const filterdPaletteAirgap = filterByUID(paletteAirgap.data, "PA-"); + const filterdVertex = filterByUID(vertex.data, "VC-"); + const filterdVertexAirgap = filterByUID(vertexAirgap.data, "VA-"); + // Debug logs - // logger.info(`Palette CVEs:", ${palette.data.length}`); - // logger.info(`Palette Airgap CVEs:", ${paletteAirgap.data.length}`); - // logger.info(`Vertex CVEs:", ${vertex.data.length}`); - // logger.info(`Vertex Airgap CVEs:", ${vertexAirgap.data.length}`); + // logger.info(`Palette CVEs:", ${filterdPalette.length}`); + // logger.info(`Palette Airgap CVEs:", ${filterdPaletteAirgap.length}`); + // logger.info(`Vertex CVEs:", ${filterdVertex.length}`); + // logger.info(`Vertex Airgap CVEs:", ${filterdVertexAirgap.length}`); - securityBulletins.set("palette", palette); - securityBulletins.set("paletteAirgap", paletteAirgap); - securityBulletins.set("vertex", vertex); - securityBulletins.set("vertexAirgap", vertexAirgap); + securityBulletins.set("palette", filterdPalette); + securityBulletins.set("paletteAirgap", filterdPaletteAirgap); + securityBulletins.set("vertex", filterdVertex); + securityBulletins.set("vertexAirgap", filterdVertexAirgap); const plainObject = Object.fromEntries( - Array.from(securityBulletins.entries()).map(([key, value]) => [key, value.data]) + Array.from(securityBulletins.entries()).map(([key, value]) => [key, value]) ); GlobalCVEData = plainObject; @@ -269,7 +291,7 @@ tags: ["security", "cve"] ## CVE Details -[${upperCaseCve}](https://nvd.nist.gov/vuln/detail/${upperCaseCve}) +Visit the official vulnerability details page for [${upperCaseCve}](${generateCVEOfficialDetailsUrl(item.metadata.cve)}) to learn more. ## Initial Publication @@ -288,7 +310,7 @@ ${escapeMDXSpecialChars(item.metadata.summary)} ## CVE Severity -${item.metadata.cvssScore} +[${item.metadata.cvssScore}](${generateCVEOfficialDetailsUrl(item.metadata.cve)}) ## Our Official Summary diff --git a/utils/helpers/urls.js b/utils/helpers/urls.js new file mode 100644 index 0000000000..4e733174fe --- /dev/null +++ b/utils/helpers/urls.js @@ -0,0 +1,28 @@ +// generateCVEOfficialDetailsUrl returns a URL that is used to link to the official CVE report. +// The URL is generated based on the cveId. +// The function checks if the cveId starts with "ghsa" and returns a GitHub Security Advisory URL. Other formal sites can be added in the future. +// The default URL is the NVD official CVE report. +function generateCVEOfficialDetailsUrl(cveId) { + let url; + + // If cveId is empty, return the default reports page URL + if (!cveId) { + return "/security-bulletins/reports/"; + } + + switch (true) { + // GitHub Security Advisory + case cveId.toLocaleLowerCase().startsWith("ghsa"): + url = `https://github.com/advisories/${cveId.toLocaleLowerCase()}`; + break; + // Default CVE URL + default: + url = `https://nvd.nist.gov/vuln/detail/${cveId.toLocaleLowerCase()}`; + } + + return url; +} + +module.exports = { + generateCVEOfficialDetailsUrl, +}; diff --git a/utils/helpers/urls.test.ts b/utils/helpers/urls.test.ts new file mode 100644 index 0000000000..577c872b89 --- /dev/null +++ b/utils/helpers/urls.test.ts @@ -0,0 +1,39 @@ +const { generateCVEOfficialDetailsUrl } = require("./urls"); + +describe("generateCVEOfficialDetailsUrl", () => { + it("should generate the GitHub Security Advisory URL for CVEs starting with 'ghsa'", () => { + const cveId = "GHSA-27wf-5967-98gx"; + const result = generateCVEOfficialDetailsUrl(cveId); + expect(result).toBe("https://github.com/advisories/ghsa-27wf-5967-98gx"); + }); + + it("should handle 'ghsa' case-insensitively and generate the correct URL", () => { + const cveId = "ghsa-27wf-5967-98gx"; + const result = generateCVEOfficialDetailsUrl(cveId); + expect(result).toBe("https://github.com/advisories/ghsa-27wf-5967-98gx"); + }); + + it("should generate the NVD URL for a CVE ID not starting with 'ghsa'", () => { + const cveId = "CVE-2020-16156"; + const result = generateCVEOfficialDetailsUrl(cveId); + expect(result).toBe("https://nvd.nist.gov/vuln/detail/cve-2020-16156"); + }); + + it("should generate the NVD URL for another CVE ID not starting with 'ghsa'", () => { + const cveId = "CVE-2019-20838"; + const result = generateCVEOfficialDetailsUrl(cveId); + expect(result).toBe("https://nvd.nist.gov/vuln/detail/cve-2019-20838"); + }); + + it("should return the default reports page URL for an empty CVE ID", () => { + const cveId = ""; + const result = generateCVEOfficialDetailsUrl(cveId); + expect(result).toBe("/security-bulletins/reports/"); + }); + + it("should return the NVD URL for a CVE ID with mixed case and normalize it", () => { + const cveId = "CVE-2020-16156"; + const result = generateCVEOfficialDetailsUrl(cveId); + expect(result).toBe("https://nvd.nist.gov/vuln/detail/cve-2020-16156"); + }); +});