Share a security scan report
Every pipeline gained a scanner in the last few years. Trivy on the image,
Semgrep or CodeQL on the source, npm audit or pip-audit on the
dependency tree, something checking the IaC. Every one of them writes its
findings into a CI log, and the CI log is where triage goes to die.
The failure is not detection. It's that a list of findings with no addressable surface can't be worked. There is no place to write "accepted until the base image lands in April," no way to see which three of the sixty findings are new this week, and no link to send someone that opens on the finding you're talking about.
Publish the scan instead of logging it
Every serious scanner emits machine-readable output — that's the input:
# container image
trivy image --format json -o trivy.json "$IMAGE"
# source
semgrep --config auto --sarif -o semgrep.sarif
# dependencies
npm audit --json > npm-audit.json
Render whichever you use into one HTML table — severity, package or rule,
location, fixed-in version, and a link to the advisory — and publish it
with a scoped token (reports:write):
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 scan.html \
--arg title "Image scan — ${GITHUB_SHA:0:7}" \
'{title: $title, html: $html}')"
One report per scan target, PATCH on every run. That's the whole
integration.
Keep it private
This is the one report type where the default matters. A scan report is an inventory of known weaknesses in systems you operate. Publish it private or team-visible, never public — the sharing model covers private, team, domain-gated, and named-reviewer access. Treat the report with the same care as the CI log it replaces: same audience, better surface.
Two related habits worth keeping:
- Scope the token.
reports:writeand nothing else. See scoped tokens. - Attach, don't inline, the raw output. The full SARIF or JSON goes in as an asset (25 MB per file), so the machine-readable original is available without bloating the body past the 5 MB HTML cap.
What the revision history buys you
Scanners are noisy in a specific way: the same sixty findings, every run, forever. The number that matters is the delta.
Because each scan appends a revision at the same URL, "what's new since Friday" is a diff between two revisions. A finding that appears for the first time is visible as an added row; a finding that disappears is a fix you can point at. Teams that track this properly usually built a diffing script to do it; here it comes with the publish.
A thread per finding
The decisions are the valuable part, and they're the part that currently lives in Slack.
A reviewer highlights CVE-2026-1234 · openssl · HIGH · fixed in 3.2.1 and
pins a thread: "not exploitable in our build — we don't link the affected
codepath. Re-check when we move off the bullseye base image, tracked in
INFRA-812." That thread stays anchored to the finding across every
subsequent scan revision. The next engineer who opens the report six weeks
later reads the decision instead of asking for it again — the mechanism is
the same one described in commenting on HTML.
Agents can read those threads too. An agent attached over
Comma's MCP server can call list_comments before it opens a bump
PR, and skip the finding somebody already accepted.
Wiring it into a pipeline
- Run the publish unconditionally — a failing scan is the one you most want linkable.
- Announce it — a webhook on
revision.createdposts the scan into the security channel; the message is the notification, the report is the artifact. - Any provider — GitHub Actions, GitLab CI, Jenkins, Azure Pipelines. It's plain HTTPS.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history. Publish this week's scan privately and triage it on the page.
Related
- Publish from CI — the general pipeline recipe
- Share a Terraform plan — the other artifact that gets truncated
- How to share an HTML report — the six properties a shared report needs