A Debian security update the image does not have, and no way to notice #155

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

What the scan found

Grype 0.115.0, against postulo:latest as built on the test instance:

[High] CVE-2026-86145   libpcre2-8-0   10.42-1  ->  10.42-1+deb12u1   (deb)

A Debian security update that exists, is in bookworm, and is not in the image. It was the
only genuinely fixable operating-system finding across both scanners — and Trivy did not
report it as fixable at all
, which is a reason to keep running both rather than picking one.

Why it is not there

docker/Dockerfile installs what it needs and never upgrades what it inherits:

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libfontconfig1 \
        fonts-dejavu-core fonts-noto-core \
        curl \
    && rm -rf /var/lib/apt/lists/*

There is no apt-get upgrade. libpcre2-8-0 comes from python:3.14-slim-bookworm, so the
image carries whatever that base image was built with. When Debian publishes a security
update for a package the base image already has, Postulo's image keeps the old one until the
base image is rebuilt and the build is re-run without a cache.

What this asks for

A decision about how the image gets Debian's security updates, and a way to notice when it
has not.

Worth being careful about

apt-get upgrade is the obvious answer and it costs reproducibility. Two builds of the
same commit, a week apart, produce different images — which is exactly what a release image
should not do. The usual compromises: upgrade and accept it (most projects), pin the base
image by digest and bump it deliberately (reproducible, and now somebody has to bump it), or
upgrade only the packages a scan names.

The base image is where most of this lives. 6 CRITICAL and 76 HIGH from Trivy, and
essentially all of them have no fix in bookworm — the normal state of Debian stable,
where the security team triages a great many as no-DSA. Those are not ignorable by choice;
they are unfixable in place, and the only real lever is which base image is used. Debian 13
(trixie) is out, and python:3.14-slim-trixie would reset a large part of that backlog. That
is a bigger decision than this issue and belongs beside it, not inside it.

The two scanners disagree about severity and both are right. Trivy reported 6 CRITICAL
and 78 HIGH; Grype reported 23 Critical and 88 High over the same image. Trivy prefers the
distribution's rating — Debian's view of how a CVE affects its build — while Grype leans on
the NVD's. Neither number is the number, and any threshold written into a pipeline has to say
which tool and which source it means.

Grype also reported python 3.14.7 -> 3.15.0a6, which is a binary match fixed only in an
unreleased alpha. Worth knowing so it is not mistaken for an action.

Nothing was found that should not be in an image. Trivy's secret and misconfiguration
scanners both returned zero findings: no keys, no tokens, no credentials, and nothing the
misconfiguration rules object to. That is worth recording, because it is the result that gets
forgotten when only the bad news is written down.

## What the scan found Grype 0.115.0, against `postulo:latest` as built on the test instance: ``` [High] CVE-2026-86145 libpcre2-8-0 10.42-1 -> 10.42-1+deb12u1 (deb) ``` A Debian security update that exists, is in bookworm, and is **not in the image**. It was the only genuinely fixable operating-system finding across both scanners — and **Trivy did not report it as fixable at all**, which is a reason to keep running both rather than picking one. ## Why it is not there `docker/Dockerfile` installs what it needs and never upgrades what it inherits: ```dockerfile RUN apt-get update \ && apt-get install -y --no-install-recommends \ libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libfontconfig1 \ fonts-dejavu-core fonts-noto-core \ curl \ && rm -rf /var/lib/apt/lists/* ``` There is no `apt-get upgrade`. `libpcre2-8-0` comes from `python:3.14-slim-bookworm`, so the image carries whatever that base image was built with. When Debian publishes a security update for a package the base image already has, Postulo's image keeps the old one until the base image is rebuilt and the build is re-run without a cache. ## What this asks for A decision about how the image gets Debian's security updates, and a way to notice when it has not. ## Worth being careful about **`apt-get upgrade` is the obvious answer and it costs reproducibility.** Two builds of the same commit, a week apart, produce different images — which is exactly what a release image should not do. The usual compromises: upgrade and accept it (most projects), pin the base image by digest and bump it deliberately (reproducible, and now somebody has to bump it), or upgrade only the packages a scan names. **The base image is where most of this lives.** 6 CRITICAL and 76 HIGH from Trivy, and essentially all of them **have no fix in bookworm** — the normal state of Debian stable, where the security team triages a great many as no-DSA. Those are not ignorable by choice; they are unfixable in place, and the only real lever is which base image is used. Debian 13 (trixie) is out, and `python:3.14-slim-trixie` would reset a large part of that backlog. That is a bigger decision than this issue and belongs beside it, not inside it. **The two scanners disagree about severity and both are right.** Trivy reported 6 CRITICAL and 78 HIGH; Grype reported 23 Critical and 88 High over the same image. Trivy prefers the distribution's rating — Debian's view of how a CVE affects *its* build — while Grype leans on the NVD's. Neither number is the number, and any threshold written into a pipeline has to say which tool and which source it means. **Grype also reported `python 3.14.7 -> 3.15.0a6`**, which is a binary match fixed only in an unreleased alpha. Worth knowing so it is not mistaken for an action. **Nothing was found that should not be in an image.** Trivy's secret and misconfiguration scanners both returned **zero** findings: no keys, no tokens, no credentials, and nothing the misconfiguration rules object to. That is worth recording, because it is the result that gets forgotten when only the bad news is written down.
tiagoagueda added this to the 0.3.0 milestone 2026-09-09 11:38:37 +00:00
Author
Owner

apt-get upgrade now runs at build time, in both apt layers — the second one matters,
because an image built with POSTULO_EXTRA_PACKAGES refreshes the index again and must not
end up quietly less patched than one built without plugins.

The reproducibility trade is deliberate and written down. Two builds of the same commit a
week apart are no longer the same image. Pinning the base by digest buys that back and makes
Debian's updates arrive only when somebody remembers to bump the digest — a manual step
nobody performs, which is precisely how this finding got here. Between an image that drifts
towards being patched and one that reliably stays unpatched, this takes the first; a release
is published by digest, so the artefact anybody runs is still named exactly. A test fails if
the base is ever pinned by digest without that decision being revisited.

Order is the whole of whether the upgrade does anything, so four tests read the Dockerfile
and hold it there: before update it runs against the stale index that caused this; after
install the packages just installed came from the old one; and in a RUN of its own it
becomes a layer served from cache exactly when the index it needed had changed.

The "way to notice" half is documented rather than automated, because the pipeline that
would run it is #156, and that waits on a runner able to build an image at all (#81).
CONTRIBUTING.md now records both scanner invocations, why both are run, why they disagree
about severity — and why the gate is --ignore-unfixed / --only-fixed rather than a
severity threshold: a Debian stable base carries dozens of High with no fix, so a pipeline
failing on CRITICAL is red every day for reasons nobody can act on, and is switched off
within a fortnight.

python:3.14-slim-trixie is deliberately not in here, as the issue said it should not be —
it is a bigger decision and belongs beside this one.

The wiki gained Hardening → The image and Debian's updates: rebuilding is how you get
patched, a running container never updates itself, and most findings in a Debian base image
have no fix, which is normal rather than alarming.

Shipped in 81d16e6 on 0.3.0, with main kept level.

`apt-get upgrade` now runs at build time, in **both** apt layers — the second one matters, because an image built with `POSTULO_EXTRA_PACKAGES` refreshes the index again and must not end up quietly less patched than one built without plugins. **The reproducibility trade is deliberate and written down.** Two builds of the same commit a week apart are no longer the same image. Pinning the base by digest buys that back and makes Debian's updates arrive only when somebody remembers to bump the digest — a manual step nobody performs, which is precisely how this finding got here. Between an image that drifts towards being patched and one that reliably stays unpatched, this takes the first; a release is published by digest, so the artefact anybody runs is still named exactly. A test fails if the base is ever pinned by digest without that decision being revisited. **Order is the whole of whether the upgrade does anything**, so four tests read the Dockerfile and hold it there: before `update` it runs against the stale index that caused this; after `install` the packages just installed came from the old one; and in a `RUN` of its own it becomes a layer served from cache exactly when the index it needed had changed. **The "way to notice" half is documented rather than automated**, because the pipeline that would run it is #156, and that waits on a runner able to build an image at all (#81). `CONTRIBUTING.md` now records both scanner invocations, why both are run, why they disagree about severity — and why the gate is `--ignore-unfixed` / `--only-fixed` rather than a severity threshold: a Debian stable base carries dozens of High with no fix, so a pipeline failing on CRITICAL is red every day for reasons nobody can act on, and is switched off within a fortnight. `python:3.14-slim-trixie` is deliberately not in here, as the issue said it should not be — it is a bigger decision and belongs beside this one. The wiki gained *Hardening → The image and Debian's updates*: rebuilding is how you get patched, a running container never updates itself, and most findings in a Debian base image have no fix, which is normal rather than alarming. Shipped in `81d16e6` on `0.3.0`, with `main` kept level.
tiagoagueda referenced this issue from a commit 2026-09-12 13:15:08 +00:00
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.

Dependencies

No dependencies set.

Reference
Postulo/postulo#155
No description provided.