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-fixedoff 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.