Make the CVE scan runnable unattended, reporting only what changed - #2143
Open
NickJosevski wants to merge 5 commits into
Open
Make the CVE scan runnable unattended, reporting only what changed#2143NickJosevski wants to merge 5 commits into
NickJosevski wants to merge 5 commits into
Conversation
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) <noreply@anthropic.com>
__pycache__ was picked up from a local syntax check; it should never have been tracked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Artifacts were registered from /tmp, which inside an execution container is gone by the time Octopus collects them from the host. Under --octopus the work directory now defaults to the step's working directory. Exiting 3 to mean 'the set changed' failed the Octopus step, which skipped the notify and save-state steps - exactly the case that most needs them. Under --octopus the script now exits 0 and signals change through HasNewFindings. Outside Octopus exit 3 is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each identifier is now its own bullet and links to the advisory. GHSA ids go to GitHub and CVE ids to NVD - both schemes appear because the two scanners name the same underlying advisory differently. The report is rendered twice: Slack gets bullets and links, the task log gets bare URLs, which stay clickable in a terminal and readable in Octopus. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A run only scans the versions it was asked for. Writing just those back dropped every other version's baseline, so an ad-hoc 'scan the version this customer reported' run silently wiped the scheduled baseline and the next scheduled run re-alerted from scratch as a first scan. Found by a real run: scanning 2025.3.417 removed the stored 2026.3.508 entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
NickJosevski
marked this pull request as ready for review
August 19, 2026 04:03
NickJosevski
enabled auto-merge (rebase)
August 19, 2026 04:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to Modern-Deployments#4,
which adds the
Scan Calamari for CVEsrunbook. That runbook cannot run until these changesland — it invokes this script straight from
main.The runbook has now had real runs against it — results below.
Why
scan.shreproduced a customer scan on demand, but only helped someone who thought to run it.Two independent things change the answer:
Nothing in this repo changes.
Only a schedule catches the second, and it is the one that bites:
CVE-2026-44788was absentfrom the audit source at 10:45 and present by 15:00 on the same day.
The part that makes it an alert rather than a report
Running nightly and posting the output is useless — the same 1 medium CVE every morning is
something everyone mutes by week two. So the script now takes
--previous-state, comparesagainst it, and exits 3 only when the set actually differs. Without it, exit 0 as before.
compare.pyholds that logic;summarise.pycollapses each version's raw scanner output(the 43-matches-to-1-distinct fold this repo already documents).
Other changes
what show up in reports, not
main.--runner=docker|native. Containers by default so a local run needs no install. Insidean Octopus execution container the step is already in a container, so docker-in-docker is
unavailable and the binaries are used from
PATH.codes appeared verbatim.
--octopuswires the above to output variables and run artifacts.Drift is folded into the stored state, deliberately
A runtime that permanently trails the current patch would otherwise fire an identical alert
every night — the exact failure this design exists to avoid. It is stored, so it alerts when
it appears, then goes quiet.
Verified
The alerting contract was tested in both directions with synthetic scanner output, since a
real scan takes ~10 minutes:
NEW: CVE-…gone:+runtime: 8.0.29 -> 8.0.30Case 2 is the important one: it stayed silent while a real drift condition was present,
which is what proves the folding works. The drift check is live — it fetched Microsoft's
index and correctly identified 8.0.29 as trailing 8.0.30.
Known gap
The
nativescanner installs are unpinned. For a scheduled job that is weaker than itshould be: a scanner upgrade and a genuine new CVE look identical in the diff.
TRIVY_VERSION/
GRYPE_VERSIONpin them; baking both into a pinned execution container image is the realfix and is worth doing before this runs unattended for long.
Verified on the real instance
Two consecutive runs against
2026.3.508, using the runbook inModern-Deployments:HasNewFindingstruefalseRun B logged
no change: same distinct CVE set, same bundled runtime. Both directions ofthe alerting contract now hold against real scanner output, not just fixtures.
Real result for
2026.3.508: 6 distinct identifiers and8.0.29, which trails the current8.0.30. The README recorded this same artifact as 1 medium CVE on 2026-08-01 — nothingin Calamari changed in between. That is the "world changes" driver, observed rather than
argued.
Trap worth recording
An Octopus API key is created as read or write scoped. A read-scoped key
authenticates, passes every
GET, and fails writes withMissing permission: LibraryVariableSetEdit— indistinguishable from the account lackingthe role. It cost an afternoon of chasing teams and role scoping. If a write 403s while the
permissions API insists the permission is granted, check the key's scope first.