From a030bdf1e602784efac9916d671e83356484b3cc Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Mon, 31 Aug 2026 23:48:20 -0400 Subject: [PATCH] Apply Debian's security patches in the cell's image The `image scan and SBOM` job failed on `master` and on every open PR: Trivy reported `CVE-2026-14456` in `libssl3t64` as a fixable High, and no rebuild of this repository could clear it. `ruby:3.4-slim` ships `3.5.6-1~deb13u2` while the fix `3.5.7-1~deb13u2` is already in `trixie-security`, so `--pull` cannot reach it until [docker-library/ruby](https://github.com/docker-library/ruby) rebuilds the tag. Run `apt-get upgrade` in the installed `Dockerfile` after the `FROM`, so the image's patch level is its own rather than upstream's release cadence. That stops the class rather than this advisory: the next one to land ahead of a tag rebuild would have failed the same way. An upgrade leaves an existing application's `Dockerfile` alone, so a cell installed before this needs the line added by hand and the image rebuilt. ref: https://github.com/basecamp/hotcell/issues/46 Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 2 ++ hotcell-client/lib/hot_cell/install/Dockerfile.tt | 7 +++++++ hotcell-client/test/install_test.rb | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 518572a..7b0fcb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ This changelog covers five gems, which release together on the same version: * The installed `Dockerfile` sets `OMP_NUM_THREADS` and `OMP_THREAD_LIMIT`. OpenMP sizes its thread pool from the host's core count, and a container's `cpus` quota is a CFS quota rather than an affinity mask, so libvips and ImageMagick asked a 98-core host for 98 threads however small the cell's share of it was. A thread stack is 8MB of private anonymous memory, which `RLIMIT_DATA` charges, so the pool alone cleared the cell's `memory` limit — and libgomp calls `exit(1)` on the first `pthread_create` it cannot satisfy. Match `OMP_NUM_THREADS` to the container's `cpus`. An upgrade leaves an existing `Dockerfile` alone, so a cell installed before this needs both added by hand and the image rebuilt. `docs/DEPLOYMENT.md` covers why the guard has to be a test rather than a deploy to beta: the failure exists only at production's core count. +* The installed `Dockerfile` applies Debian's pending security patches with an `apt-get upgrade` after the `FROM`. `docker build --pull` takes the newest base tag, but the tag itself can sit behind an advisory that is already in `trixie-security` until [docker-library/ruby](https://github.com/docker-library/ruby) rebuilds it, so a clean build shipped a fixable High that no rebuild of the cell could clear. Upgrading during the build makes the image's patch level its own rather than upstream's release cadence, and it stops the class rather than the one advisory. An upgrade leaves an existing `Dockerfile` alone, so a cell installed before this needs the line added by hand and the image rebuilt. + ### HotCell::Server #### Fixed diff --git a/hotcell-client/lib/hot_cell/install/Dockerfile.tt b/hotcell-client/lib/hot_cell/install/Dockerfile.tt index 707e98d..74708d3 100644 --- a/hotcell-client/lib/hot_cell/install/Dockerfile.tt +++ b/hotcell-client/lib/hot_cell/install/Dockerfile.tt @@ -12,6 +12,13 @@ ARG RUBY_VERSION=3.4 FROM ruby:${RUBY_VERSION}-slim +# Apply Debian's pending security patches. `--pull` takes the newest base tag, and the tag can still be +# behind an advisory that is already in `trixie-security`, because only a rebuild by docker-library/ruby +# moves it. Upgrading here makes the image's patch level its own rather than upstream's release cadence, +# and it is what keeps the image scan in CI passing on a fixable vulnerability the base has not picked up +# yet. It costs a layer and a little build time on every build. +RUN apt-get update && apt-get upgrade -y --no-install-recommends && rm -rf /var/lib/apt/lists/* + # Install the tools and libraries your operations run — and nothing else. For example: # # RUN apt-get update && \ diff --git a/hotcell-client/test/install_test.rb b/hotcell-client/test/install_test.rb index 7c5179b..df639eb 100644 --- a/hotcell-client/test/install_test.rb +++ b/hotcell-client/test/install_test.rb @@ -83,6 +83,20 @@ def test_install_bounds_the_openmp_thread_pools end end + # `--pull` takes the newest base tag, but that tag itself can sit behind a Debian advisory until + # docker-library/ruby rebuilds it, and the image scan gate blocks on a fixable High no rebuild of this + # repository can clear. Applying the pending security patches during the build makes the gate's claim the + # image's own rather than upstream's release cadence. + def test_install_applies_the_bases_pending_security_patches + Dir.mktmpdir do |root| + HotCell::Install.call(root, out: StringIO.new) + + dockerfile = File.read(File.join(root, "hotcell", "Dockerfile")) + + assert_match(/apt-get update && apt-get upgrade -y.*rm -rf \/var\/lib\/apt\/lists/, dockerfile) + end + end + def test_install_leaves_an_existing_file_exactly_as_it_is Dir.mktmpdir do |root| customized = File.join(root, "hotcell", "Dockerfile")