From 3adcd5a709d9406167dc07324f2eb618d0c505fc Mon Sep 17 00:00:00 2001 From: Nick Josevski Date: Tue, 18 Aug 2026 12:54:31 +1000 Subject: [PATCH 1/5] Let the CVE scan run unattended and report only what changed The script reproduced a customer scan on demand, but only helped someone who thought to run it. Two independent things change the answer: a new build bundling a new runtime, and a CVE being published against an artifact that has not moved. The second changes nothing in this repo, so only a schedule catches it. Running it nightly is useless without state - the same finding every morning is an alert everyone mutes by week two. So the script now takes a previous result, compares against it, and exits 3 only when the set actually differs. Scans several versions in one run, because customers run old versions and the tips of the supported release branches are what show up in reports, not main. The two scanners run as containers by default so a local run needs no install. Inside an Octopus execution container the step is already in a container, so docker-in-docker is unavailable and the binaries are used from PATH instead. Runtime drift against Microsoft's releases index is folded into the stored state rather than reported standalone. A runtime that permanently trails would otherwise fire an identical alert every night. Colour is now emitted only to a real terminal; Octopus captures stdout into a task log where escape codes show up verbatim. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/scan-calamari-cves/README.md | 52 +++- .../__pycache__/compare.cpython-314.pyc | Bin 0 -> 8834 bytes .../__pycache__/summarise.cpython-314.pyc | Bin 0 -> 6374 bytes scripts/scan-calamari-cves/compare.py | 159 +++++++++++ scripts/scan-calamari-cves/scan.sh | 267 ++++++++++++------ scripts/scan-calamari-cves/summarise.py | 99 +++++++ 6 files changed, 493 insertions(+), 84 deletions(-) create mode 100644 scripts/scan-calamari-cves/__pycache__/compare.cpython-314.pyc create mode 100644 scripts/scan-calamari-cves/__pycache__/summarise.cpython-314.pyc create mode 100755 scripts/scan-calamari-cves/compare.py create mode 100755 scripts/scan-calamari-cves/summarise.py diff --git a/scripts/scan-calamari-cves/README.md b/scripts/scan-calamari-cves/README.md index c7b9d13a9..4f50e5ea5 100644 --- a/scripts/scan-calamari-cves/README.md +++ b/scripts/scan-calamari-cves/README.md @@ -3,11 +3,17 @@ Run `./scan.sh`. Takes about ten minutes, most of it downloading a ~270 MB package. ```bash -./scan.sh # latest main-branch CI build -./scan.sh 2026.3.508 # a specific published version -./scan.sh --local # publish from your working tree and scan that +./scan.sh # latest main-branch CI build +./scan.sh 2026.3.508 # a specific published version +./scan.sh 2025.3.417 2026.3.508 # several, e.g. the supported release tips +./scan.sh --local # publish from your working tree and scan that +./scan.sh --previous-state=old.json 2026.3.508 # only tell me what changed ``` +`--previous-state` makes the script exit **3** when the reported CVE set differs from that +file, so a scheduler can treat "the answer changed" as the actionable event. Without it the +script always exits 0. + ## Why this rather than `dotnet list package --vulnerable` **They answer different questions, and the local one over-reports.** @@ -76,3 +82,43 @@ regression test for it passes on the *vulnerable* version, which is the proof. - Vulnerability databases move daily. `CVE-2026-44788` was absent from the NuGet audit source at 10:45 and present by 15:00 on the same day. Re-run rather than cite an old result. + +## Running it on a schedule + +The script only helps someone who thinks to run it, and two independent things change the +answer: + +1. **The artifact changes** — a new build bundles a new .NET runtime. +2. **The world changes** — a CVE is published against an artifact that has not moved. + Nothing in this repo changes. Only a schedule catches this, and it is the one that bites + (see the `CVE-2026-44788` note under Caveats). + +The `Scan Calamari for CVEs` runbook in the `calamari-cve-scanning` Octopus project runs +this script daily and posts to Slack **only when the set changes**. It invokes the script +straight from this repo, so there is one implementation and the runbook tracks `main`. + +`--octopus` is what it passes: read the previous state from the `Calamari.CveScan.State` +variable, write the new state, `HasNewFindings` and `SlackSummary` back as output variables, +and attach the raw scanner JSON as run artifacts. + +### Scanners in and out of containers + +By default the two scanners run as docker containers, so a local run needs no install step. +Inside an Octopus execution container the step is *already* in a container, so +docker-in-docker is unavailable — `--runner=native` (auto-detected) uses `trivy` and `grype` +from `PATH` instead, installing them if missing. + +Those installs are **unpinned**, which is weaker than it should be for a scheduled job: a +scanner upgrade and a genuine new CVE look identical in the diff. Set `TRIVY_VERSION` / +`GRYPE_VERSION` to pin, or bake both into a pinned execution container image. + +### Why runtime drift is part of the state + +The comparison also flags a bundled runtime that trails the current patch, or has fallen out +of support, using Microsoft's published releases index. That signal matters on its own — +per the EOL trap below, a clean runtime scan on an old artifact usually means nobody is +publishing advisories any more, not that it is safe. + +It is folded into the stored state rather than reported every run. A runtime that +permanently trails would otherwise fire an identical alert every night, which is how an +alert becomes something everyone mutes. diff --git a/scripts/scan-calamari-cves/__pycache__/compare.cpython-314.pyc b/scripts/scan-calamari-cves/__pycache__/compare.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbdd9abfe2412c21a8b8a495fcc398bbf32ab461 GIT binary patch literal 8834 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 literal 0 HcmV?d00001 diff --git a/scripts/scan-calamari-cves/__pycache__/summarise.cpython-314.pyc b/scripts/scan-calamari-cves/__pycache__/summarise.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..393bf4545e02c08fc98d6c6b8a2d48cf9331a94b GIT binary patch literal 6374 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/