diff --git a/ingestion/operators/docker/Dockerfile b/ingestion/operators/docker/Dockerfile index 742d7ff071f9..989104b00272 100644 --- a/ingestion/operators/docker/Dockerfile +++ b/ingestion/operators/docker/Dockerfile @@ -197,29 +197,38 @@ RUN pip install psycopg2 mysqlclient==2.1.1 # apt-mark manual pins them before autoremove runs so the cleanup doesn't take # them out too -- verified end-to-end by the `import MySQLdb` gate check. # -# The util-linux upgrade rides along in the same root layer. trixie's base ships -# 2.41-5, which carries CVE-2025-14104, CVE-2026-13595 and CVE-2026-27456; -# trixie-security has 2.41.5-0+deb13u1. -# The package set is computed from dpkg rather than hand-listed. One source +# The OS security upgrades ride along in the same root layer. trixie's base ships +# util-linux 2.41-5, which carries CVE-2025-14104, CVE-2026-13595 and +# CVE-2026-27456; trixie-security has 2.41.5-0+deb13u1. +# expat is the same story one release later. It is not in the trixie base at all +# -- it arrives transitively from the top-of-file apt layer -- and that layer's +# frozen index is exactly why the image keeps shipping 2.8.2 and being reported +# for CVE-2026-72522 while trixie-security has 2.8.3-1~deb13u1. +# The package sets are computed from dpkg rather than hand-listed. One source # package produces many binaries -- here util-linux, bsdutils, login, mount, # liblastlog2-2, libblkid1, libmount1, libsmartcols1 and libuuid1 -- and scanners # report each separately, so a hand-written list silently leaves behind whichever # binary it forgot, and liblastlog2-2 is exactly the one that is easy to forget. -# Asking dpkg which installed packages came from the util-linux source cannot -# miss one, and stays correct if Debian splits the source differently later. -# The empty check is not defensive noise: `apt-get install --only-upgrade` with -# no package arguments exits 0, so a query that silently returned nothing would -# give a green build that shipped the vulnerable packages anyway. Fail closed. +# Asking dpkg which installed packages came from the source cannot miss one, and +# stays correct if Debian splits the source differently later. +# util-linux is asserted non-empty and expat deliberately is not: util-linux is +# Essential, so an empty query there means dpkg-query misbehaved and the build +# must not continue, whereas expat is transitive and an empty query is a +# legitimate image with nothing to patch. Asserting one of the two is what the +# check is for -- `apt-get install --only-upgrade` with no package arguments +# exits 0, so an all-empty query would give a green build that shipped the +# vulnerable packages anyway. Fail closed. # This deliberately does NOT go in the top-of-file apt RUN: that layer's cache # key never changes, so an upgrade placed there freezes its Debian index with it # and the image keeps shipping whatever was current when the layer was first # built. Below the COPY and pip layers, the index is re-read on every build. USER root RUN set -eu; \ - pkgs="$(dpkg-query -W -f='${source:Package} ${Package}\n' | awk '$1=="util-linux"{print $2}')"; \ - [ -n "$pkgs" ] || { echo "no src:util-linux packages found; refusing to skip the CVE patch" >&2; exit 1; }; \ + ul="$(dpkg-query -W -f='${source:Package} ${Package}\n' | awk '$1=="util-linux"{print $2}')"; \ + [ -n "$ul" ] || { echo "no src:util-linux packages found; refusing to skip the CVE patch" >&2; exit 1; }; \ + ex="$(dpkg-query -W -f='${source:Package} ${Package}\n' | awk '$1=="expat"{print $2}')"; \ apt-get -qq update; \ - apt-get -qq install -y --only-upgrade $pkgs; \ + apt-get -qq install -y --only-upgrade $ul $ex; \ apt-get -qq purge -y libmariadb-dev libmariadb-dev-compat libunbound8; \ apt-mark manual libmariadb3 mariadb-common; \ apt-get -qq autoremove -y --purge; \ diff --git a/ingestion/operators/docker/Dockerfile.ci b/ingestion/operators/docker/Dockerfile.ci index c7fac3356237..ede6cd033875 100644 --- a/ingestion/operators/docker/Dockerfile.ci +++ b/ingestion/operators/docker/Dockerfile.ci @@ -205,29 +205,38 @@ RUN pip install psycopg2 mysqlclient==2.1.1 # apt-mark manual pins them before autoremove runs so the cleanup doesn't take # them out too -- verified end-to-end by the `import MySQLdb` gate check. # -# The util-linux upgrade rides along in the same root layer. trixie's base ships -# 2.41-5, which carries CVE-2025-14104, CVE-2026-13595 and CVE-2026-27456; -# trixie-security has 2.41.5-0+deb13u1. -# The package set is computed from dpkg rather than hand-listed. One source +# The OS security upgrades ride along in the same root layer. trixie's base ships +# util-linux 2.41-5, which carries CVE-2025-14104, CVE-2026-13595 and +# CVE-2026-27456; trixie-security has 2.41.5-0+deb13u1. +# expat is the same story one release later. It is not in the trixie base at all +# -- it arrives transitively from the top-of-file apt layer -- and that layer's +# frozen index is exactly why the image keeps shipping 2.8.2 and being reported +# for CVE-2026-72522 while trixie-security has 2.8.3-1~deb13u1. +# The package sets are computed from dpkg rather than hand-listed. One source # package produces many binaries -- here util-linux, bsdutils, login, mount, # liblastlog2-2, libblkid1, libmount1, libsmartcols1 and libuuid1 -- and scanners # report each separately, so a hand-written list silently leaves behind whichever # binary it forgot, and liblastlog2-2 is exactly the one that is easy to forget. -# Asking dpkg which installed packages came from the util-linux source cannot -# miss one, and stays correct if Debian splits the source differently later. -# The empty check is not defensive noise: `apt-get install --only-upgrade` with -# no package arguments exits 0, so a query that silently returned nothing would -# give a green build that shipped the vulnerable packages anyway. Fail closed. +# Asking dpkg which installed packages came from the source cannot miss one, and +# stays correct if Debian splits the source differently later. +# util-linux is asserted non-empty and expat deliberately is not: util-linux is +# Essential, so an empty query there means dpkg-query misbehaved and the build +# must not continue, whereas expat is transitive and an empty query is a +# legitimate image with nothing to patch. Asserting one of the two is what the +# check is for -- `apt-get install --only-upgrade` with no package arguments +# exits 0, so an all-empty query would give a green build that shipped the +# vulnerable packages anyway. Fail closed. # This deliberately does NOT go in the top-of-file apt RUN: that layer's cache # key never changes, so an upgrade placed there freezes its Debian index with it # and the image keeps shipping whatever was current when the layer was first # built. Below the COPY and pip layers, the index is re-read on every build. USER root RUN set -eu; \ - pkgs="$(dpkg-query -W -f='${source:Package} ${Package}\n' | awk '$1=="util-linux"{print $2}')"; \ - [ -n "$pkgs" ] || { echo "no src:util-linux packages found; refusing to skip the CVE patch" >&2; exit 1; }; \ + ul="$(dpkg-query -W -f='${source:Package} ${Package}\n' | awk '$1=="util-linux"{print $2}')"; \ + [ -n "$ul" ] || { echo "no src:util-linux packages found; refusing to skip the CVE patch" >&2; exit 1; }; \ + ex="$(dpkg-query -W -f='${source:Package} ${Package}\n' | awk '$1=="expat"{print $2}')"; \ apt-get -qq update; \ - apt-get -qq install -y --only-upgrade $pkgs; \ + apt-get -qq install -y --only-upgrade $ul $ex; \ apt-get -qq purge -y libmariadb-dev libmariadb-dev-compat libunbound8; \ apt-mark manual libmariadb3 mariadb-common; \ apt-get -qq autoremove -y --purge; \