Share an OWASP ZAP report

A DAST scan produces a list of alerts and, immediately afterwards, a much more valuable list: which of them matter. The scan is automated. The second list is human, and CI has nowhere to put it — so the reasoning ends up in a Slack thread and is gone by the next scan, at which point someone triages the same alert again from scratch.

ZAP's HTML output is a single self-contained file. Give it a URL and the triage stops evaporating.

Publish it

zap-baseline.py -t https://staging.example.com -r zap.html || true

curl -fsS -X PATCH \
  "https://commareports.com/api/v1/reports/$COMMA_REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html zap.html \
        --arg title "ZAP — staging — $(date +%F)" \
        '{title: $title, html: $html}')"

|| true because zap-baseline.py exits non-zero when it finds warnings, and that is precisely the run worth publishing. PATCH against a saved report id keeps one URL per target with a revision per scan.

Use a scoped token with reports:write only, and keep the report private or team-visible — see the sharing model.

What the URL changes

  • Accepted risks stay accepted. The note explaining why an alert is not exploitable is pinned to the alert, not to a channel. See commenting on HTML.
  • Scan-over-scan diffs. New alerts stand out from the standing noise.
  • Security and engineering read the same artifact. No CI access needed on either side.
  • Scheduled rescans. A routine can rescan on a cron, so a regression in a dependency shows up without a deploy to trigger it.

Limits

  • HTML body: 5 MB. A full active scan against a large app can exceed it — publish the baseline summary and attach the full XML/JSON as an asset.
  • Scripts run, sandboxed: allow-scripts, no allow-same-origin.
  • 60 requests/minute per token.

Try it

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

Publish a scan →

Related