Share a Grype report

Grype is fast and it is thorough, and those two facts combine into a delivery problem: a scan of a normal application image produces hundreds of rows.

NAME             INSTALLED    FIXED-IN   TYPE  VULNERABILITY   SEVERITY
libcrypto3       3.1.4-r5     3.1.4-r6   apk   CVE-2024-2511   Medium
libssl3          3.1.4-r5     3.1.4-r6   apk   CVE-2024-2511   Medium
…

That table is fine in a terminal you can widen and unusable everywhere else. Pasted into Slack it wraps, and once it wraps the CVE column and the severity column no longer belong to the same row. So the scan gets summarized as "about 200 findings, mostly medium", which is not something anyone can act on.

Get HTML out of Grype

Grype has no HTML writer, but it has a template writer, which is enough:

grype myapp:1.4.2 -o template -t html.tmpl > report.html

A minimal html.tmpl — the point is a real table with sortable severity, not elaborate styling:

<h1>{{.Source.Target.UserInput}}</h1>
<table>
<tr><th>Severity</th><th>CVE</th><th>Package</th><th>Installed</th><th>Fixed in</th></tr>
{{- range .Matches}}
<tr><td>{{.Vulnerability.Severity}}</td>
    <td><a href="{{.Vulnerability.DataSource}}">{{.Vulnerability.ID}}</a></td>
    <td>{{.Artifact.Name}}</td>
    <td>{{.Artifact.Version}}</td>
    <td>{{if .Vulnerability.Fix.Versions}}{{index .Vulnerability.Fix.Versions 0}}{{else}}—{{end}}</td></tr>
{{- end}}
</table>

The other route, if you already render SARIF for other scanners, is grype myapp:1.4.2 -o sarif > grype.sarif and then sarif-tools — which has the advantage of putting Grype's findings in the same report as everything else.

Publish only what can be fixed

grype myapp:1.4.2 --only-fixed -o template -t html.tmpl > report.html

--only-fixed is the single most useful flag for a report meant to be read. A list where every row has an available upgrade is a work queue. A list where 85% of rows say "no fix" is a document people learn to skip.

Publish it

Drag report.html into the app, or POST it:

curl -fsS -X POST "https://commareports.com/api/v1/reports" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html report.html \
        --arg t "Grype — myapp:1.4.2" '{title: $t, html: $html}')"

Set the access first

A Grype report is a precise inventory of the unpatched vulnerabilities in an image you are running, complete with versions. Reports are private by default; keep this one on team, domain-gated or named-reviewer access. See the sharing model.

From CI

grype "$IMAGE" --only-fixed --fail-on high -o template -t html.tmpl > report.html || SCAN_FAILED=1

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html report.html \
        --arg t "Grype — $IMAGE" '{title: $t, html: $html}')"

exit "${SCAN_FAILED:-0}"

Publish before you fail the build — a gate that fails without leaving the report behind is a gate people work around. PATCHing one report id per image gives a stable URL and a revision per scan, which is the remediation trend an auditor will ask you for.

What review adds

  • Anchored threads per CVE, so "not exploitable, we do not call that code path" is recorded against the finding. See commenting on HTML.
  • Revisions, so the count going down is evidence.
  • Named reviewers when security has to sign off before a release.

Limits

  • Entry HTML: 5 MB. A large base image with --only-fixed off can exceed it; scan with --only-fixed, or filter by severity in the template.
  • 60 requests/minute per token.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Publish a scan →

Related