A truncated chunked OSV download raises http.client.IncompleteRead, an
HTTPException rather than an OSError, so it skipped the .part cleanup and
the error line; the handler now catches both. The runtime check's warn
summary no longer claims the mirror scan tracks every warning, since host
findings and images outside the mirror never reach it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A suppression entry under <service>.security.suppressions now names either
one CVE (id) or one image tag (image_tag), each with until. An image_tag
entry accepts every finding of the service's image while the owner runs that
tag; each owner of a shared digest is judged by its own tag (Config.Image at
runtime, the pinned version in the mirror scan), so a container on any other
tag stays unsuppressed. Host suppressions are the id entries of the engine
services; an image_tag entry there covers the engine's own image.
The cve lint requires image_tag to equal the service's version, and the
Docker image updater drops a service's image_tag entries with their markers
when it moves the service to another version, so a tag suppression never
outlives its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scan threshold becomes a severity-to-verdict mapping in
roles/sys-ctl-hlth-runtime/meta/services.yml, CRITICAL: fail and HIGH: warn,
for production and CI alike. A finding mapped to fail fails the service and
the deploy, a warn prints a warning line and passes, a suppressed one prints
an info line. Engine errors, missing databases and PHP hardening violations
still fail. The old list form is rejected with a config error. The daily
mirror scan stays strict: every unsuppressed fail or warn finding is a row,
so it fails while an image is red.
Every run publishes one list of judged findings four ways:
- report.json of the latest run (summary + records) under
DIR_HEALTH_RUNTIME_REPORT, written atomically;
- findings.jsonl under DIR_HEALTH_RUNTIME_LOG, one record per finding,
appended per run and rotated weekly by a logrotate drop-in;
- one JSON line per target plus a summary line on stdout for the journal;
- infinito_hlth_runtime.prom in DIR_METRICS_TEXTFILE when web-app-prometheus
is deployed: findings per target, kind, image, severity, scanner and
verdict (no CVE label), last run time and success, scanner errors.
Each record carries run_id, timestamp, host, environment, target, kind,
image, scanner, id, severity, package, installed_version, fixed_version,
suppressed, suppressed_until and verdict. The paths are SPOT variables in
group_vars/all/05_paths.yml, the script takes them as arguments.
Requirement 070 and the role README describe the mapping, the outputs and
the per-image vulnerability issue of the previous commit.
Part of #1324
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeRabbit review of PR #1326:
- fetch_databases downloads each OSV archive to all.zip.part and replaces
all.zip only after the transfer completes. An OSError removes the partial
file and comes back as an `osv <ecosystem>: <error>` line instead of a
traceback.
- run_gh bounds every gh call with a 120 s timeout. A failed or hung create,
edit or close becomes a `failed <action>: <error>` line while the other
issues and the close pass still run, and the command exits 1.
- The suppression lint checks every list item of a suppressions block, so an
entry that starts with `until:` still needs its `# nocheck: cve <reason>`
marker.
Part of #1324
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under act the deploy job pinned an engine but skipped the cache restores, so
every local swarm-* run stopped on a missing Grype database. The swarm
matrix also ran inside the act job container, where the shared host
directory did not exist, and the workspace job would have kept a copy of
about 5 GB in every worktree.
- scripts/tests/deploy/act/workflow.sh requires
INFINITO_VULNERABILITY_DB_HOST_PATH, creates it on the host and binds it
into the job container at the same path, because that container drives
the host daemon, which resolves every stack and swarm node bind on the
host.
- call-test-deploy.yml pins engine and database path in one step that runs
only outside act; under act the deploy draws an engine and refreshes the
shared directory. call-test-workspace.yml takes the shared path under act.
- The refresh lock moves into the shared directory, so host deploys and act
runs serialize on one file, and a root refresh hands its files to the
directory owner. hand_over skips paths a mocked download never created,
which failed four TestRefresh cases when the gate ran as root.
- A missing INFINITO_VULNERABILITY_DB_HOST_PATH stops compose up with a
"run 'make dotenv'" hint instead of a KeyError.
- Rationale paragraphs in the database and compose docstrings are dropped.
Part of #1324
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each CI deploy row now draws one vulnerability engine (trivy, grype or osv)
as a fifth rotation axis, shown in the job name next to distro and
filesystem (🔬 axis, 🔺 trivy, 🍇 grype, 🐙 osv). The deploy-time scan of
sys-ctl-hlth-runtime runs that engine behind Syft and the Grype runtime pass.
- A vulnerability-db job keeps one Actions cache per database and day and
downloads only the missing ones. Every deploy job restores Grype plus its
drawn engine, fails on a cache miss and never downloads a database itself.
- Local stacks share one host directory, INFINITO_VULNERABILITY_DB_HOST_PATH,
mounted read-only at INFINITO_VULNERABILITY_DB_DIR in compose.yml and in
every swarm node. A local deploy without a pinned engine draws one and
refreshes only the databases it reads, at most once a day, under a lock.
- A pinned engine whose database is missing stops the deploy before it
starts.
Part of #1324
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runtime check judged PHP by its upstream version and failed every
container on an end-of-life line. Distribution builds backport fixes without
raising that number, so main run 41154895895 failed Checkmk#0 on
`PHP 8.1.2-1ubuntu2.28 runs the end-of-life line 8.1`, a build Ubuntu lists
as fixed for CVE-2026-109618 and CVE-2026-109619.
- The role now runs Trivy, Grype and OSV-Scanner, with a Syft SBOM per
target, against the host and the images of the running containers, in
bounded parallel lanes. Targets, engines and the failing severities are
lists in meta/services.yml; the default is every target and engine at
CRITICAL and HIGH with a fix available.
- Grype always scans the binary-classified packages of the SBOM, so a PHP
built from source in an official php image (8.4.26) still fails on the
CVEs fixed in 8.4.27, which Trivy and OSV do not see.
- OSV reads an SBOM copy whose distro purls carry the source package name.
- A CVE is suppressed only in the role owning the image, under
services.<service>.security.suppressions with id, until and a
`# nocheck: cve <reason>` marker; a lint rejects bare, malformed and
expired entries.
- The deploy-time run fails on its first finding: sys-service gains
system_service_run_once and sys-service-terminator skips the retries
only for those units. The timer runs daily at 00:00 UTC.
- The PHP version floors are removed; the php_hardening check stays.
- make test-security replaces make security-runtimes: Syft builds an SBOM
of every pinned image straight from the GHCR mirror, cached by digest,
and the three engines scan it with the role's threshold and suppressions.
Part of #1324
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>