A JavaScript library for rendering CSAF (Common Security Advisory Framework) documents (versions 2.0 and 2.1) to HTML, using the same Mustache templates as Secvisogram.
It is used by @secvisogram/cli to
render CSAF documents from the command line, and by the Secvisogram web app
itself for its preview/export features.
npm install @secvisogram/html-templateRendering a document is a three-step pipeline: enrich the raw CSAF document with the data the template needs, render markdown fields, then render the HTML template. All three steps apply to both CSAF versions.
import {
enrichDocumentV2_0,
HTMLTemplate2_0,
renderMarkdown,
} from '@secvisogram/html-template'
const csafDocument = JSON.parse(await readFile('advisory.json', 'utf8'))
const { document: enriched } = enrichDocumentV2_0(csafDocument)
const parsed = renderMarkdown(enriched)
const html = HTMLTemplate2_0({ document: parsed })import {
enrichDocumentV2_1,
HTMLTemplate2_1,
renderMarkdown,
} from '@secvisogram/html-template'
const csafDocument = JSON.parse(await readFile('advisory.json', 'utf8'))
const { document: enriched } = enrichDocumentV2_1(csafDocument)
const parsed = renderMarkdown(enriched)
const html = HTMLTemplate2_1({ document: parsed })Important
Always call renderMarkdown on the enriched document (the return value
of enrichDocumentV2_0/enrichDocumentV2_1), not the original raw
document, and always pass its result into HTMLTemplate2_0/
HTMLTemplate2_1. Skipping this step - or passing the wrong document -
means markdown syntax (e.g. **bold**) in text fields such as notes,
references, and remediation details will appear as raw, unrendered
markdown in the output HTML.
Takes a raw, parsed CSAF 2.0 or 2.1 document (a plain object, as produced by
JSON.parse) and returns { document }, a deep-cloned copy enriched with
the extra, denormalised data the Mustache templates rely on - for example:
- resolving
product_id/group_idreferences (inproduct_status,remediations,threats,product_groups, ...) to include the product/group's display name - attaching the matching CVSS vector string/base score (and, for CSAF 2.1, which CVSS version it came from) to each affected product
- computing
document.max_base_score, the highest CVSS base score across all vulnerabilities - splitting
notes(both document-level and per-vulnerability) andremediations/threatsinto per-category buckets (e.g.notes_summary,remediations_vendor_fix,threats_impact, ...), sorted by date where applicable - attaching the Mustache lambda helpers the templates use, as properties on the returned document
The original input document is not mutated.
enrichDocumentV2_0 reads CVSS data from vulnerability.scores[].cvss_v3
(the CSAF 2.0 shape, which only ever carries CVSS v3). enrichDocumentV2_1
reads it from vulnerability.metrics[].content (the CSAF 2.1 shape), where
cvss_v2, cvss_v3, and cvss_v4 are all optional, independent siblings -
a single metric may carry more than one CVSS version at once. When more than
one is present, enrichDocumentV2_1 prefers the highest version (v4, then
v3, then v2) as the "primary" score/vector shown for that product, and also
exposes which version was picked via cvssVersion on each resolved product
entry.
Renders GitHub Flavored Markdown syntax to
HTML in a fixed allow-list of text fields (e.g. document.notes[].text,
vulnerabilities[].remediations[].details, ...) throughout the document,
mutating it in place, and also returning it. Fields not on the allow-list are
left untouched, even if they happen to contain markdown-like syntax.
If a field's content doesn't actually use any markdown syntax, it's left as
plain text rather than being wrapped in a <p> tag.
Renders the enriched (and markdown-processed) document into a complete HTML document string, using the bundled Mustache template for that CSAF version. There is currently no way to supply a custom template.
enrichDocumentV2_0/enrichDocumentV2_1 attach four
Mustache lambdas onto the
returned document, which the bundled templates invoke as e.g.
{{#secureHref}}{{someUrl}}{{/secureHref}}:
secureHref- only emits anhref="..."attribute if the URL's scheme (or, fordata:URLs, MIME type) is on an allow-list (#,mailto:,tel:,http(s):,ftp:, and base64-encodedimage/png,image/jpeg, orimage/gifdata URIs); otherwise renders nothing. This is a deliberate defense against untrusted advisory content (e.g.javascript:URLs) ending up as clickable links.upperCase- capitalises the first character of the rendered text.replaceUnderscores- replaces all_with spaces (e.g. for CSAF'ssnake_casecategory enum values).removeTrailingComma- strips a trailing comma from the rendered text (for comma-joined lists built with a trailing separator).
- Templates are not customisable -
HTMLTemplate2_0/HTMLTemplate2_1always use the bundled template.
The bundled lib/css stylesheets (a vendored copy of
gutenberg-css's base and modern theme
stylesheets, plus Secvisogram's own preview.css) are inlined directly into
<style> tags in the rendered HTML, rather than linked via
<link href="...">. This makes the output a
single, self-contained HTML string/file with no separate network request or
file path that needs to resolve correctly - which matters both for
@secvisogram/cli's output (a
standalone file, possibly opened directly from disk) and for the
Secvisogram web app's preview (rendered into a blank <iframe> with no
base URL of its own, where relative/absolute file paths can't reliably
resolve at all).
npm install
npm test # type-check, prettier --check, and run the test suite
npm run test-coverage