Nothing scans the image, so the scan that found these was done by hand #156

Closed
opened 2026-09-09 11:38:38 +00:00 by tiagoagueda · 1 comment
Owner

Observation

Everything in the two issues beside this one was found by running a scanner by hand, on a
Raspberry Pi that happened to have aquasec/trivy and anchore/grype pulled for something
else. Neither finding was new. Both had been in the image since it was built.

What exists

Nothing scans the image, and nothing can. .forgejo/workflows/ci.yml runs the tests, the
lint, pip-audit over the exported lock, zizmor over the workflows and check --deploy —
a genuinely careful pipeline — and it never builds a container, because building one needs a
runner advertising the docker label and none is registered (#81). image.yml has never run.

pip-audit is the closest thing, and it covers the lock: Postulo's own Python
dependencies, before they are put in an image. It says nothing about the base image, the
apt packages, what uv sync actually installed, or anything left in a layer.

This is the same gap that let #121 ship an image that could not be built at all.

What this asks for

A scan the project runs rather than one somebody remembers to run.

Worth being careful about

It depends on #81 and there is no way around that. Scanning an image requires an image.
Until a runner can build one, the honest options are a scan on whatever machine builds by
hand, or a documented step in the release checklist — both of which are what just happened,
and neither of which is a pipeline.

Both tools, not one. They disagreed usefully here: Grype found the only actionable
Debian update, which Trivy did not mark fixable; Trivy found the Python packages and the
leftover cache, which Grype's deb-and-binary scan did not see at all. Running one and
believing it would have missed half of this.

A threshold that fails the build has to be survivable. Six CRITICAL findings with no fix
available are the normal state of a Debian base image; a pipeline that fails on
--severity CRITICAL fails every day for reasons nobody can act on, and is switched off
within a fortnight. The useful gate is fixable findings — Trivy's --ignore-unfixed —
which on this image today would be one line, and that is a gate worth having.

The tools' own database is a network dependency. Trivy downloads a vulnerability DB; a
build that cannot reach the internet cannot scan. That is fine for CI and worth remembering
before anything depends on it at release time.

An SBOM is the cheap half. Both tools produce one, and a signed SBOM per release is more
useful to somebody self-hosting Postulo than a scan result from the day it was built: it lets
them scan it later, against a database that did not exist yet. That is a small addition to
image.yml and worth considering alongside this.

Record what was found rather than only what was fixed. The scan that produced these
issues also produced a clean result — no secrets, no misconfigurations — and that half is
what nobody writes down.

## Observation Everything in the two issues beside this one was found by running a scanner by hand, on a Raspberry Pi that happened to have `aquasec/trivy` and `anchore/grype` pulled for something else. Neither finding was new. Both had been in the image since it was built. ## What exists Nothing scans the image, and nothing can. `.forgejo/workflows/ci.yml` runs the tests, the lint, `pip-audit` over the exported lock, `zizmor` over the workflows and `check --deploy` — a genuinely careful pipeline — and it never builds a container, because building one needs a runner advertising the `docker` label and none is registered (#81). `image.yml` has never run. `pip-audit` is the closest thing, and it covers the *lock*: Postulo's own Python dependencies, before they are put in an image. It says nothing about the base image, the apt packages, what `uv sync` actually installed, or anything left in a layer. This is the same gap that let #121 ship an image that could not be built at all. ## What this asks for A scan the project runs rather than one somebody remembers to run. ## Worth being careful about **It depends on #81 and there is no way around that.** Scanning an image requires an image. Until a runner can build one, the honest options are a scan on whatever machine builds by hand, or a documented step in the release checklist — both of which are what just happened, and neither of which is a pipeline. **Both tools, not one.** They disagreed usefully here: Grype found the only actionable Debian update, which Trivy did not mark fixable; Trivy found the Python packages and the leftover cache, which Grype's `deb`-and-`binary` scan did not see at all. Running one and believing it would have missed half of this. **A threshold that fails the build has to be survivable.** Six CRITICAL findings with no fix available are the normal state of a Debian base image; a pipeline that fails on `--severity CRITICAL` fails every day for reasons nobody can act on, and is switched off within a fortnight. The useful gate is **fixable** findings — Trivy's `--ignore-unfixed` — which on this image today would be one line, and that is a gate worth having. **The tools' own database is a network dependency.** Trivy downloads a vulnerability DB; a build that cannot reach the internet cannot scan. That is fine for CI and worth remembering before anything depends on it at release time. **An SBOM is the cheap half.** Both tools produce one, and a signed SBOM per release is more useful to somebody self-hosting Postulo than a scan result from the day it was built: it lets *them* scan it later, against a database that did not exist yet. That is a small addition to `image.yml` and worth considering alongside this. **Record what was found rather than only what was fixed.** The scan that produced these issues also produced a clean result — no secrets, no misconfigurations — and that half is what nobody writes down.
tiagoagueda added this to the 0.3.0 milestone 2026-09-09 11:38:38 +00:00
Author
Owner

The blocker is gone. The issue said "It depends on #81 and there is no way around
that"
— and #81 is closed: a runner advertising the docker label exists, so image.yml
can do more than sit there. This is the pipeline rather than the release-checklist
consolation.

It gates the push. Native-architecture build → scan → multi-architecture push. A scan
after the push would be a report about something already published.

One script, two callers. scripts/scan-image.sh is what a person runs and what the
workflow runs. Two descriptions of one intent drift apart, and the one nobody executes is
always the one that is wrong.

The five warnings

Both tools. They disagreed usefully — Grype found the only actionable Debian update that
Trivy did not mark fixable; Trivy found the Python packages and the leftover uv cache that
Grype's deb-and-binary scan did not see at all. Both, pinned by tag, because a scanner that
changes under you changes what the gate means.

A threshold that fails the build has to be survivable. The gate is --ignore-unfixed
and --only-fixed at HIGH and above. Six unfixable CRITICALs is the ordinary state of a
Debian base image; a build that fails daily for reasons nobody can act on is switched off
within a fortnight. What stops a push is always something somebody can do something about.

The database is a network dependency. Said in the script's header and beside the step,
with the caches mounted as volumes so a second run does not fetch both again.

An SBOM is the cheap half. Taken. CycloneDX, kept as an artefact per run — worth more to
somebody self-hosting Postulo than this run's verdict, because it lets them scan the release
later against a database that does not exist yet. From Trivy rather than a third tool.

Record what was found rather than only what was fixed. The full report — every finding
either scanner saw, fixable or not, plus the secret and misconfiguration scans — goes into
the job summary, and the step runs if: always() so a failing gate still publishes what it
saw. That clean half is what nobody writes down, and it is the half that says the image is in
the state you think it is.

Checked, since nothing here can run it yet from this machine

Six tests in tests/test_image_build.py, in the same read-the-file style as the rest of that
file: the scan step precedes the push step, the workflow calls the script rather than
restating it, both scanners are present and pinned, the gate is on fixable findings, and the
bill of materials leaves the runner. zizmor passes on the changed workflow.

The first real run belongs to whoever builds the next release image — and with #157 landing
just before this, the before-and-after is worth recording when they do.

Shipped in 55549be on 0.3.0, with main kept level.

**The blocker is gone.** The issue said *"It depends on #81 and there is no way around that"* — and #81 is closed: a runner advertising the `docker` label exists, so `image.yml` can do more than sit there. This is the pipeline rather than the release-checklist consolation. **It gates the push.** Native-architecture build → scan → multi-architecture push. A scan after the push would be a report about something already published. **One script, two callers.** `scripts/scan-image.sh` is what a person runs and what the workflow runs. Two descriptions of one intent drift apart, and the one nobody executes is always the one that is wrong. ## The five warnings **Both tools.** They disagreed usefully — Grype found the only actionable Debian update that Trivy did not mark fixable; Trivy found the Python packages and the leftover uv cache that Grype's deb-and-binary scan did not see at all. Both, pinned by tag, because a scanner that changes under you changes what the gate means. **A threshold that fails the build has to be survivable.** The gate is `--ignore-unfixed` and `--only-fixed` at HIGH and above. Six unfixable CRITICALs is the ordinary state of a Debian base image; a build that fails daily for reasons nobody can act on is switched off within a fortnight. What stops a push is always something somebody can do something about. **The database is a network dependency.** Said in the script's header and beside the step, with the caches mounted as volumes so a second run does not fetch both again. **An SBOM is the cheap half.** Taken. CycloneDX, kept as an artefact per run — worth more to somebody self-hosting Postulo than this run's verdict, because it lets them scan the release later against a database that does not exist yet. From Trivy rather than a third tool. **Record what was found rather than only what was fixed.** The full report — every finding either scanner saw, fixable or not, plus the secret and misconfiguration scans — goes into the job summary, and the step runs `if: always()` so a failing gate still publishes what it saw. That clean half is what nobody writes down, and it is the half that says the image is in the state you think it is. ## Checked, since nothing here can run it yet from this machine Six tests in `tests/test_image_build.py`, in the same read-the-file style as the rest of that file: the scan step precedes the push step, the workflow calls the script rather than restating it, both scanners are present and pinned, the gate is on fixable findings, and the bill of materials leaves the runner. `zizmor` passes on the changed workflow. The first real run belongs to whoever builds the next release image — and with #157 landing just before this, the before-and-after is worth recording when they do. Shipped in `55549be` on `0.3.0`, with `main` kept level.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
Postulo/postulo#156
No description provided.