diff --git a/package.json b/package.json index 472b5fb..8f1c14c 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@google-cloud/storage": "^8.0.1", "@lucide/svelte": "^1.34.0", "chart.js": "^4.5.1", + "marked": "^18.0.13", "nodemailer": "^9.0.6", "pino": "^10.3.1" } diff --git a/src/lib/code-report/analysis-summary.ts b/src/lib/code-report/analysis-summary.ts index b550eda..a843af3 100644 --- a/src/lib/code-report/analysis-summary.ts +++ b/src/lib/code-report/analysis-summary.ts @@ -1,5 +1,7 @@ // Best-effort summary for Trivy-style JSON reports (top-level `Results[]`, each entry may // carry `Vulnerabilities`/`Secrets`/`Packages`). Other tool formats simply yield all-zero counts. +import { vulnerabilityUrl } from './vulnerability-format'; + export type AnalysisSummary = { vulnerabilities: { critical: number; @@ -156,9 +158,11 @@ export function extractVulnerabilities(result: unknown): VulnerabilityFinding[] ? Number((epss as Record).Percentile) : null; + const vulnerabilityId = String(vuln.VulnerabilityID || `${vuln.PkgName || 'unknown'}-${row.Target || ''}`); + return [ { - id: String(vuln.VulnerabilityID || `${vuln.PkgName || 'unknown'}-${row.Target || ''}`), + id: vulnerabilityId, packageName: String(vuln.PkgName || 'Paquete desconocido'), installedVersion: String(vuln.InstalledVersion || 'desconocida'), fixedVersion: String(vuln.FixedVersion || ''), @@ -179,7 +183,7 @@ export function extractVulnerabilities(result: unknown): VulnerabilityFinding[] title: String(vuln.Title || 'Vulnerabilidad sin título'), description: String(vuln.Description || ''), primaryUrl: String(vuln.PrimaryURL || ''), - cveUrl: `https://nvd.nist.gov/vuln/detail/${String(vuln.VulnerabilityID || '')}`, + cveUrl: vulnerabilityUrl(vulnerabilityId, String(vuln.PrimaryURL || '')), cvssScore: score, cweIds: Array.isArray(vuln.CweIDs) ? vuln.CweIDs.map(String) : [], references: Array.isArray(vuln.References) ? vuln.References.map(String) : [], diff --git a/src/lib/code-report/vulnerability-format.test.ts b/src/lib/code-report/vulnerability-format.test.ts new file mode 100644 index 0000000..b0a0223 --- /dev/null +++ b/src/lib/code-report/vulnerability-format.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest'; +import { + renderVulnerabilityDescription, + vulnerabilityUrl, +} from './vulnerability-format'; + +describe('vulnerability format', () => { + it('uses GitHub Advisories for GHSA identifiers and NVD for CVEs', () => { + expect(vulnerabilityUrl('GHSA-2x7j-588g-ccc2')).toBe( + 'https://github.com/advisories/GHSA-2X7J-588G-CCC2', + ); + expect(vulnerabilityUrl('CVE-2026-1234')).toBe( + 'https://nvd.nist.gov/vuln/detail/CVE-2026-1234', + ); + }); + + it('renders Markdown while escaping raw HTML and unsafe links', () => { + const rendered = renderVulnerabilityDescription( + '### Summary\n\n**Important** [safe](https://example.com) [unsafe](javascript:alert(1))\n\n', + ); + + expect(rendered).toContain('

Summary

'); + expect(rendered).toContain('Important'); + expect(rendered).toContain('href="https://example.com"'); + expect(rendered).not.toContain('