From ab1263befd7919f6fb4b1bdf48fc4c0ede27b7b6 Mon Sep 17 00:00:00 2001 From: Brad Flaugher Date: Fri, 4 Sep 2026 17:13:39 -0400 Subject: [PATCH] fix(sandbox): overlay patched Tornado and Python packages onto the image Fedora 44 ships python3-tornado 6.5.7 (GHSA-mpf4-983q-p7j4), pulled in by python3-ipykernel, plus pypdf 4.x, soupsieve, pygments and pip builds that lag their upstream security releases. Grype flags every one of them as an open, fixable CVE in this bundle's sandbox image. Port fleet's config/default/sandbox/Containerfile fix: pip-install the current PyPI releases (tornado>=6.5.8) into /usr/local, `rpm -e --nodeps` the vulnerable RPM copies, and fail the build if any RPM copy still shadows the overlay. Also run `microdnf upgrade` before `install` so a rebuild refreshes base-layer packages inherited from fedora-minimal:latest. Hand-ported, not synced: bundles are peers of fleet's default bundle. The publish workflow rebuilds and pushes the image on merge; running boxes pick it up on their next `fleet update` / image rebuild. Co-Authored-By: Claude Fable 5.1 --- sandbox/Containerfile | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/sandbox/Containerfile b/sandbox/Containerfile index 24d464c..6538c22 100644 --- a/sandbox/Containerfile +++ b/sandbox/Containerfile @@ -36,7 +36,11 @@ FROM registry.fedoraproject.org/fedora-minimal:latest # Python stack via Fedora RPMs rather than pip. Fedora rebuilds the native # packages against whatever python3 it ships, which sidesteps the "no wheel for # this Python" trap, and shared libs dedupe cleanly across the stack. -RUN microdnf install -y \ +# `upgrade` refreshes the packages inherited from the moving fedora-minimal:latest +# base before `install` resolves the data stack as one distro-tested transaction, +# so a rebuild never ships a base-layer package older than Fedora's current fix. +RUN microdnf upgrade -y \ + && microdnf install -y \ --setopt=install_weak_deps=0 \ --setopt=tsflags=nodocs \ ImageMagick \ @@ -75,7 +79,34 @@ RUN microdnf install -y \ sed \ shadow-utils \ && microdnf clean all \ - && rm -rf /var/cache/dnf /var/cache/yum /var/log/dnf* /var/log/yum* /usr/share/locale/* /usr/share/man + && rm -rf /var/cache/dnf /var/cache/yum /var/log/dnf* /var/log/yum* /usr/share/locale/* /usr/share/man \ + && python3 -c "import pip, pypdf, tornado, idna, pygments, soupsieve" + +# Exception to the RPM-first rule: Fedora's builds of a few Python packages +# lag upstream security releases (Tornado 6.5.7 vs patched 6.5.8 — +# GHSA-mpf4-983q-p7j4 — pypdf 4.x vs upstream 6.x, soupsieve, pygments, and +# pip itself), and every Grype scan flags that gap as open CVEs +# (GHSA-2wc2-fm75-p42x, GHSA-g867-7843-wf8q, GHSA-wf93-45jw-7689, …). Overlay +# the current PyPI releases into /usr/local and remove the vulnerable RPM +# copies so neither the runtime nor the CVE scan sees them. These packages +# work without native extensions (Tornado includes an optional accelerator), +# unlike the native data stack kept on RPMs. None of these pull in further +# dependencies. Unpinned on purpose, same policy as the base image: every +# rebuild picks up the latest upstream fixes. `rpm -e --nodeps` leaves +# dangling RPM-db requires (beautifulsoup4 → soupsieve, ipython → pygments, +# ipykernel → tornado) — harmless, the /usr/local copies satisfy the actual +# imports, and nothing runs dnf inside the built image. +# +# Hand-ported from fleet's config/default/sandbox/Containerfile; bundles are +# peers, so a future fix there is ported here as its own PR, never synced. +RUN python3 -m pip install --no-cache-dir --break-system-packages --upgrade \ + pip pypdf soupsieve pygments "tornado>=6.5.8" \ + && rpm -e --nodeps python3-pip python3-pypdf python3-soupsieve python3-pygments python3-tornado \ + && rm -rf /root/.cache/pip \ + && python3 -c "import sys, pip, pypdf, soupsieve, pygments, tornado; \ +stale = [m.__name__ for m in (pip, pypdf, soupsieve, pygments, tornado) \ + if not m.__file__.startswith('/usr/local/')]; \ +sys.exit('RPM copy still shadows the pip overlay: %r' % stale if stale else 0)" # Pre-warm matplotlib's font cache so the first plot at runtime doesn't pay the # multi-second TTF scan.