{"id":14864,"date":"2026-09-24T17:30:00","date_gmt":"2026-09-24T12:00:00","guid":{"rendered":"https:\/\/www.allerin.com\/blog\/?p=14864"},"modified":"2026-09-23T10:28:20","modified_gmt":"2026-09-23T04:58:20","slug":"active-storage-libvips-matrix","status":"publish","type":"post","link":"https:\/\/www.allerin.com\/blog\/active-storage-libvips-matrix\/","title":{"rendered":"Active Storage libvips checks before deployment"},"content":{"rendered":"<p>A patched Active Storage bundle can still refuse to boot against an older native library. These Active Storage libvips checks connect the package version listed for your base image with the library Ruby actually loads.<\/p>\n<p>The July advisory reports a possible arbitrary file read and remote code execution in Active Storage. Patched releases are <strong>7.2.3.2, 8.0.5.1 and 8.1.3.1<\/strong>. Their protection requires <strong>libvips 8.13 or later and ruby-vips 2.2.1 or later<\/strong>. Updating only one layer does not establish that the application can start. <a href=\"https:\/\/github.com\/rails\/rails\/security\/advisories\/GHSA-xr9x-r78c-5hrm\" target=\"_blank\" rel=\"noopener\">Rails advisory<\/a>, <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.1.3.1\/activestorage\/lib\/active_storage\/vips.rb\" target=\"_blank\" rel=\"noopener\">patched Active Storage code<\/a>.<\/p>\n<p>The advisory applies to applications that use libvips for Active Storage processing and accept image uploads from untrusted users. Generating a variant is not a separate requirement. For affected applications, it also directs changing <code>secret_key_base<\/code> and every secret readable by the application process, without retaining exposed secrets as fallbacks. Keep that response work separate from the compatibility checks below.<\/p>\n<h2 id=\"the-matrix-compares-published-package-versions\">The matrix compares published package versions<\/h2>\n<p><strong>Sources checked 7 September 2026.<\/strong> This table compares package-page versions with the 8.13 native-library floor. A package page does not guarantee that its download is still available. \u201cAbove\u201d means that version meets this particular minimum. It does not mean a deployed application is patched, correctly configured or unaffected.<\/p>\n<div style=\"overflow-x: auto;\" tabindex=\"0\" role=\"region\" aria-label=\"libvips package matrix\">\n<table>\n<thead>\n<tr>\n<th scope=\"col\">Platform or explicit image<\/th>\n<th scope=\"col\">Package page version<\/th>\n<th scope=\"col\">Against 8.13<\/th>\n<th scope=\"col\">Runtime check<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><a href=\"https:\/\/packages.debian.org\/bullseye\/libvips42\" target=\"_blank\" rel=\"noopener\">Debian 11 bullseye<\/a><\/td>\n<td><a href=\"https:\/\/packages.debian.org\/bullseye\/libvips42\" target=\"_blank\" rel=\"noopener\"><code>libvips42 8.10.5-2+deb11u1<\/code><\/a><\/td>\n<td>Below<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/packages.debian.org\/bookworm\/libvips42\" target=\"_blank\" rel=\"noopener\">Debian 12 bookworm<\/a><\/td>\n<td><a href=\"https:\/\/packages.debian.org\/bookworm\/libvips42\" target=\"_blank\" rel=\"noopener\"><code>libvips42 8.14.1-3+deb12u3<\/code><\/a><\/td>\n<td>Above<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/packages.debian.org\/trixie\/libvips42t64\" target=\"_blank\" rel=\"noopener\">Debian 13 trixie<\/a><\/td>\n<td><a href=\"https:\/\/packages.debian.org\/trixie\/libvips42t64\" target=\"_blank\" rel=\"noopener\"><code>libvips42t64 8.16.1-1+deb13u1<\/code><\/a><\/td>\n<td>Above<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/packages.ubuntu.com\/jammy\/libs\/libvips42\" target=\"_blank\" rel=\"noopener\">Ubuntu 22.04 jammy<\/a><\/td>\n<td><a href=\"https:\/\/packages.ubuntu.com\/jammy\/libs\/libvips42\" target=\"_blank\" rel=\"noopener\"><code>libvips42 8.12.1-1build1<\/code><\/a><\/td>\n<td>Below<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/packages.ubuntu.com\/noble\/libvips42t64\" target=\"_blank\" rel=\"noopener\">Ubuntu 24.04 noble<\/a><\/td>\n<td><a href=\"https:\/\/packages.ubuntu.com\/noble\/libvips42t64\" target=\"_blank\" rel=\"noopener\"><code>libvips42t64 8.15.1-1.1build4<\/code><\/a><\/td>\n<td>Above<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/pkgs.alpinelinux.org\/package\/v3.23\/community\/x86_64\/vips\" target=\"_blank\" rel=\"noopener\">Alpine 3.23 (x86_64, community)<\/a><\/td>\n<td><a href=\"https:\/\/pkgs.alpinelinux.org\/package\/v3.23\/community\/x86_64\/vips\" target=\"_blank\" rel=\"noopener\"><code>vips 8.17.3-r1<\/code><\/a><\/td>\n<td>Above<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/raw.githubusercontent.com\/docker-library\/ruby\/4ea2e7e222ba458994a266920e8815140bc2c901\/3.4\/slim-bookworm\/Dockerfile\" target=\"_blank\" rel=\"noopener\">Official ruby:3.4.10-slim-bookworm<\/a><\/td>\n<td><a href=\"https:\/\/packages.debian.org\/bookworm\/libvips42\" target=\"_blank\" rel=\"noopener\"><code>libvips42 8.14.1-3+deb12u3<\/code><\/a><\/td>\n<td>Above<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/raw.githubusercontent.com\/docker-library\/ruby\/4ea2e7e222ba458994a266920e8815140bc2c901\/3.4\/slim-trixie\/Dockerfile\" target=\"_blank\" rel=\"noopener\">Official ruby:3.4.10-slim-trixie<\/a><\/td>\n<td><a href=\"https:\/\/packages.debian.org\/trixie\/libvips42t64\" target=\"_blank\" rel=\"noopener\"><code>libvips42t64 8.16.1-1+deb13u1<\/code><\/a><\/td>\n<td>Above<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/devcenter.heroku.com\/articles\/heroku-22-stack\" target=\"_blank\" rel=\"noopener\">Heroku-22<\/a><\/td>\n<td><a href=\"https:\/\/packages.ubuntu.com\/jammy\/libs\/libvips42\" target=\"_blank\" rel=\"noopener\"><code>libvips42 8.12.1-1build1<\/code><\/a><\/td>\n<td>Below in Ubuntu archive; app unknown<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/devcenter.heroku.com\/articles\/heroku-24-stack\" target=\"_blank\" rel=\"noopener\">Heroku-24<\/a><\/td>\n<td><a href=\"https:\/\/packages.ubuntu.com\/noble\/libvips42t64\" target=\"_blank\" rel=\"noopener\"><code>libvips42t64 8.15.1-1.1build4<\/code><\/a><\/td>\n<td>Above in Ubuntu archive; app unknown<\/td>\n<td><code>bundle exec ruby script\/check_vips_floor.rb<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Run the check in the final container or dyno with its bundle and library search path. A missing <code>vips<\/code> CLI does not establish that the library is absent.<\/p>\n<p>The official Ruby rows show the base&#8217;s listed package, not a preinstalled library. <code>ruby:3.4-slim<\/code> currently maps to trixie; a Ruby minor tag does not fix the operating system. Record an explicit tag and deployed digest. <a href=\"https:\/\/github.com\/docker-library\/official-images\/blob\/master\/library\/ruby\" target=\"_blank\" rel=\"noopener\">Official Ruby image list<\/a>.<\/p>\n<p>FastRuby&#8217;s <a href=\"https:\/\/www.fastruby.io\/blog\/why-patching-the-gem-didnt-fix-cve-2026-66066.html\" target=\"_blank\" rel=\"noopener\">3 August analysis<\/a> covered the bullseye\/bookworm problem. The checks below connect the package, final image and Ruby&#8217;s loaded library, including buildpack applications.<\/p>\n<h2 id=\"the-patch-can-stop-boot-before-a-variant-is-requested\">The patch can stop boot before a variant is requested<\/h2>\n<p>The patched Active Storage code loads ruby-vips and checks whether it exposes <code>Vips.block_untrusted<\/code>. When the binding loads but that method is unavailable, Rails raises a <code>RuntimeError<\/code>. This is a startup failure, not a warning that can be deferred until the next thumbnail request. The three patched branches record the fix in their changelogs. <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v7.2.3.2\/activestorage\/CHANGELOG.md\" target=\"_blank\" rel=\"noopener\">7.2.3.2<\/a>, <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.5.1\/activestorage\/CHANGELOG.md\" target=\"_blank\" rel=\"noopener\">8.0.5.1<\/a>, <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.1.3.1\/activestorage\/CHANGELOG.md\" target=\"_blank\" rel=\"noopener\">8.1.3.1<\/a>.<\/p>\n<p>An excerpt of the actual diagnostic preserves both prerequisites. The full message is retained with the execution evidence.<\/p>\n<blockquote><p>Disabling them requires libvips 8.13 or later and ruby-vips 2.2.1 or later.<\/p><\/blockquote>\n<p>In Rails 7.2.3.2, 8.0.5.1 and 8.1.3.1, setting the variant processor to MiniMagick does not, by itself, avoid this check while ruby-vips remains available. The check runs when Active Storage loads the binding. First establish whether the application still needs that dependency for analysis, transformation or another library.<\/p>\n<p>Rails main has since changed where these checks run. <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58734\" target=\"_blank\" rel=\"noopener\">PR #58734<\/a> allows a MiniMagick application to boot with an installed but unsupported libvips or ruby-vips; the guard still raises when the Vips transformer loads or the Vips analyzer reads an image. <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58743\" target=\"_blank\" rel=\"noopener\">PR #58743<\/a> separately lets an application boot when the native libraries cannot load, without making vips processing available. As checked on 23 September 2026, neither change is in the released versions above or their stable branches. These development changes do not remove the two library prerequisites for Active Storage&#8217;s protected libvips processing.<\/p>\n<p>For libvips at or above 8.13, the advisory documents two ways to enable the protection before processing starts. Set <code>VIPS_BLOCK_UNTRUSTED<\/code> in the process environment before libvips initializes, as described in the <a href=\"https:\/\/www.libvips.org\/2022\/05\/28\/What%27s-new-in-8.13.html\" target=\"_blank\" rel=\"noopener\">libvips 8.13 release notes<\/a>, or use <code>Vips.block_untrusted(true)<\/code> with <a href=\"https:\/\/github.com\/libvips\/ruby-vips\/blob\/v2.2.1\/lib\/vips.rb\" target=\"_blank\" rel=\"noopener\">ruby-vips 2.2.1 or later<\/a>. The patched Rails code makes that call itself. Verify the application\u2019s patch and startup state after applying a mitigation.<\/p>\n<p>Below 8.13, that capability is unavailable. The advisory&#8217;s remaining option is removing the libvips dependency. Moving variant processing to MiniMagick may form part of that change, but it also requires removing ruby-vips where the bundle and application permit it, provisioning ImageMagick, and exercising analysis and transformations. Merely adding an initializer or environment variable cannot supply a missing native-library capability.<\/p>\n<h2 id=\"check-the-bundle-and-the-loaded-library-together\">Check the bundle and the loaded library together<\/h2>\n<p>Start with the two quick inventory checks. Keep both outputs with the final image digest and deployment record.<\/p>\n<pre style=\"overflow-x: auto; max-width: 100%;\"><code class=\"language-sh\">vips --version\r\nbundle list\r\n<\/code><\/pre>\n<p>The executable reports its own libvips version; the bundle reports resolved gems. Neither proves that they describe the same process. The following check asks the Ruby binding for its loaded library version and reports the Active Storage and ruby-vips versions selected by Bundler.<\/p>\n<p>Save it as <code>script\/check_vips_floor.rb<\/code> and run it under the application&#8217;s bundle. It loads Active Storage&#8217;s vips initialization without booting a full Rails application, reporting the dependency versions before attempting the component&#8217;s guard.<\/p>\n<pre style=\"overflow-x: auto; max-width: 100%;\"><code class=\"language-ruby\">#!\/usr\/bin\/env ruby\r\n# Compatibility gate for GHSA-xr9x-r78c-5hrm, checked 7 September 2026.\r\n# Run in the deployed application's bundle and image. No upload is processed.\r\nrequire \"rubygems\"\r\nrequire \"open3\"\r\nrequire \"openssl\" # A standalone check has not run Rails' normal boot requires.\r\n\r\nputs \"ruby=#{RUBY_VERSION} platform=#{RUBY_PLATFORM}\"\r\nbegin\r\n  storage = Gem.loaded_specs[\"activestorage\"] || Gem::Specification.find_by_name(\"activestorage\")\r\n  binding = Gem.loaded_specs[\"ruby-vips\"] || Gem::Specification.find_by_name(\"ruby-vips\")\r\n  gem \"activestorage\", storage.version.to_s\r\n  gem \"ruby-vips\", binding.version.to_s\r\n  puts \"activestorage=#{storage.version} ruby-vips=#{binding.version}\"\r\nrescue Gem::LoadError =&gt; error\r\n  warn \"MISSING_GEM #{error.message.lines.first.strip}\"\r\n  exit 2\r\nend\r\n\r\nbegin\r\n  require \"nokogiri\" # Match Active Storage's libxml2 load order.\r\n  require \"ruby-vips\"\r\nrescue LoadError =&gt; error\r\n  warn \"LIBRARY_LOAD_FAILED #{error.message.lines.first.strip}\"\r\n  exit 2\r\nend\r\n\r\nruntime = Gem::Version.new([Vips.version(0), Vips.version(1), Vips.version(2)].join(\".\"))\r\nputs \"libvips_runtime=#{runtime} block_untrusted_available=#{Vips.respond_to?(:block_untrusted)}\"\r\nbegin\r\n  cli, status = Open3.capture2e(\"vips\", \"--version\")\r\n  puts \"vips_cli=#{cli.strip} status=#{status.exitstatus}\"\r\n  cli_version = cli[\/vips-(\\d+\\.\\d+\\.\\d+)\/, 1]\r\n  mismatch = !status.success? || (cli_version &amp;&amp; Gem::Version.new(cli_version) != runtime)\r\nrescue Errno::ENOENT\r\n  puts \"vips_cli=not_installed (runtime library check continues)\"\r\n  mismatch = false\r\nend\r\n\r\nerrors = []\r\nfloor = {\"7.2\" =&gt; \"7.2.3.2\", \"8.0\" =&gt; \"8.0.5.1\", \"8.1\" =&gt; \"8.1.3.1\"}[storage.version.segments.first(2).join(\".\")]\r\nerrors &lt;&lt; \"Active Storage series is outside this check's verified patch list\" unless floor\r\nerrors &lt;&lt; \"Active Storage is below #{floor}\" if floor &amp;&amp; storage.version &lt; Gem::Version.new(floor)\r\nerrors &lt;&lt; \"Loaded libvips #{runtime} is below 8.13\" if runtime &lt; Gem::Version.new(\"8.13\")\r\nerrors &lt;&lt; \"ruby-vips #{binding.version} is below 2.2.1\" if binding.version &lt; Gem::Version.new(\"2.2.1\")\r\nerrors &lt;&lt; \"CLI and loaded library disagree, or the CLI failed\" if mismatch\r\n\r\nbegin\r\n  require \"active_storage\/vips\"\r\n  puts \"active_storage_vips_load=ok\"\r\nrescue LoadError, StandardError =&gt; error\r\n  warn \"active_storage_vips_load=#{error.class}: #{error.message.gsub(\/\\s+\/, ' ').strip}\"\r\n  errors &lt;&lt; \"Active Storage's vips initialization failed\"\r\nend\r\nerrors &lt;&lt; \"The loaded binding does not expose block_untrusted\" unless Vips.respond_to?(:block_untrusted)\r\nif errors.any?\r\n  errors.each { |message| warn \"FAIL #{message}\" }\r\n  exit 1\r\nend\r\nputs \"PASS verified patch floors and Active Storage vips initialization only\"\r\nputs \"This does not verify application configuration, all dependencies, or past exposure.\"\r\n<\/code><\/pre>\n<pre style=\"overflow-x: auto; max-width: 100%;\"><code class=\"language-sh\">bundle exec ruby script\/check_vips_floor.rb\r\n<\/code><\/pre>\n<p>The independent local run used Ruby 3.4.10, Active Storage 8.1.3.1, ruby-vips 2.2.1 and libvips 8.18.5. It exited successfully. A separate control kept that native library but selected ruby-vips 2.2.0; Active Storage raised the diagnostic above. Selecting MiniMagick while retaining that older binding still failed. These controls isolate the binding prerequisite; the native-library comparison follows below.<\/p>\n<p>A nonzero exit means a failed or incomplete check. A pass does not verify application configuration, initializers or accepted uploads. Boot the application separately and test upload, analysis and variant paths. Applications deliberately using no ruby-vips need a different check.<\/p>\n<h2 id=\"change-the-runtime-stage-and-test-what-it-contains\">Change the runtime stage and test what it contains<\/h2>\n<p>Installing a newer library in a builder stage does not update a final stage that starts from an older base. Inspect each <code>FROM<\/code>, the copied native dependencies and the image that actually runs the web and worker processes.<\/p>\n<p>Debian reproduction, applied to the supplied test fixture. The old fixture uses Debian\u2019s signed 1 September snapshot because live security-package downloads returned 404. The diff removes that historical repository setup when moving to bookworm; it is not a deployment instruction to retain an expired repository.<\/p>\n<pre style=\"overflow-x: auto; max-width: 100%;\"><code class=\"language-diff\">--- Dockerfile.debian.before\r\n+++ Dockerfile.debian.after\r\n@@ -1,17 +1,4 @@\r\n-# Historical failure fixture only. Keep archive transport and signatures verified.\r\n-FROM debian:bookworm-slim AS archive-certificates\r\n-RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends ca-certificates \\\r\n-    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\r\n-\r\n-FROM debian:bullseye-slim\r\n-COPY --from=archive-certificates \/etc\/ssl\/certs\/ca-certificates.crt \/etc\/ssl\/certs\/ca-certificates.crt\r\n-# Freeze the final LTS repository state for reproduction, not deployment.\r\n-# Only the historical timestamp check is waived; signatures and TLS stay enabled.\r\n-RUN printf '%s\\n' \\\r\n-    'deb [check-valid-until=no signed-by=\/usr\/share\/keyrings\/debian-archive-keyring.gpg] https:\/\/snapshot.debian.org\/archive\/debian\/20260901T000000Z\/ bullseye main' \\\r\n-    'deb [check-valid-until=no signed-by=\/usr\/share\/keyrings\/debian-archive-keyring.gpg] https:\/\/snapshot.debian.org\/archive\/debian-security\/20260901T000000Z\/ bullseye-security main' \\\r\n-    &gt; \/etc\/apt\/sources.list \\\r\n-    &amp;&amp; rm -f \/etc\/apt\/sources.list.d\/debian.sources\r\n+FROM debian:bookworm-slim\r\n ARG RUBY_VERSION=3.4.10\r\n ARG RUBY_SHA256=ecee2d072a14f2d14347dd56dfd8fe5c3130abf5117bfaacbda0f4ef9cc429ec\r\n RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \\\r\n<\/code><\/pre>\n<p>Ubuntu base change, applied to the supplied test fixture.<\/p>\n<pre style=\"overflow-x: auto; max-width: 100%;\"><code class=\"language-diff\">--- Dockerfile.ubuntu.before\r\n+++ Dockerfile.ubuntu.after\r\n@@ -1,4 +1,4 @@\r\n-FROM ubuntu:22.04\r\n+FROM ubuntu:24.04\r\n ARG DEBIAN_FRONTEND=noninteractive\r\n ARG RUBY_VERSION=3.4.10\r\n ARG RUBY_SHA256=ecee2d072a14f2d14347dd56dfd8fe5c3130abf5117bfaacbda0f4ef9cc429ec\r\n<\/code><\/pre>\n<p>The Linux x86_64 runs used Ruby 3.4.10, Active Storage 8.1.3.1 and ruby-vips 2.2.1 throughout. Both diffs were applied and checked before building.<\/p>\n<div style=\"overflow-x: auto;\" tabindex=\"0\" role=\"region\" aria-label=\"Recorded component results\">\n<table>\n<thead>\n<tr>\n<th scope=\"col\">Fixture<\/th>\n<th scope=\"col\">Loaded libvips<\/th>\n<th scope=\"col\">Component result<\/th>\n<th scope=\"col\">Exit<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Bullseye before<\/td>\n<td>8.10.5<\/td>\n<td>RuntimeError<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>Bookworm after<\/td>\n<td>8.14.1<\/td>\n<td>Initialized<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td>Ubuntu 22.04 before<\/td>\n<td>8.12.1<\/td>\n<td>RuntimeError<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>Ubuntu 24.04 after<\/td>\n<td>8.15.1<\/td>\n<td>Initialized<\/td>\n<td>0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>These are four isolated fixture results, not tests of every deployment or platform in the matrix.<\/p>\n<p>The <a href=\"https:\/\/www.allerin.com\/rails-checks\/active-storage-libvips-2026-09.zip\">downloadable fixtures<\/a> contain the full Dockerfiles, diffs, locked bundle and driver, with no sign-up. They isolate the native-library check. Application images also need Ruby, native gem, database adapter and image-fixture tests. Retain results from the rebuilt final runtime.<\/p>\n<h2 id=\"heroku-needs-a-check-inside-the-application\">Heroku needs a check inside the application<\/h2>\n<p><a href=\"https:\/\/devcenter.heroku.com\/articles\/heroku-22-stack\" target=\"_blank\" rel=\"noopener\">Heroku-22<\/a> uses Ubuntu 22.04 and reaches end of life on <strong>30 April 2027<\/strong>. Jammy&#8217;s package is below the floor, but buildpacks can supply another library. Heroku&#8217;s <a href=\"https:\/\/devcenter.heroku.com\/articles\/stack-packages\" target=\"_blank\" rel=\"noopener\">package table<\/a> describes the base, not every application dependency.<\/p>\n<p>The <a href=\"https:\/\/github.com\/heroku\/heroku-buildpack-apt\/blob\/main\/bin\/compile\" target=\"_blank\" rel=\"noopener\">APT buildpack<\/a> extracts packages under <code>.apt<\/code> and changes the library search path. Those packages need not appear in the system database, so check the built application environment.<\/p>\n<p>Retain the buildpack versions and how libvips entered the build. <a href=\"https:\/\/devcenter.heroku.com\/articles\/heroku-24-stack\" target=\"_blank\" rel=\"noopener\">Heroku-24<\/a> uses Ubuntu 24.04, whose package is above the floor; check the rebuilt application&#8217;s loaded library. Track Heroku-22&#8217;s end-of-life date separately from this immediate patch check.<\/p>\n<h2 id=\"the-image-pipeline-carries-several-generations-of-defaults\">The image pipeline carries several generations of defaults<\/h2>\n<p>Active Storage shipped with <a href=\"https:\/\/rubyonrails.org\/2018\/4\/9\/Rails-5-2-0-final\" target=\"_blank\" rel=\"noopener\">Rails 5.2 in 2018<\/a>, bringing file attachments into Rails. Its original <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v5.2.0\/activestorage\/app\/models\/active_storage\/variant.rb\" target=\"_blank\" rel=\"noopener\">variant implementation<\/a> used MiniMagick directly. An application from that period can still carry transformation options written for ImageMagick even after several framework upgrades.<\/p>\n<p><a href=\"https:\/\/guides.rubyonrails.org\/6_0_release_notes.html#active-storage\" target=\"_blank\" rel=\"noopener\">Rails 6.0 in 2019<\/a> introduced the ImageProcessing interface and the libvips option. It retained MiniMagick as the default and even had a transitional fallback when <code>image_processing<\/code> was missing. The shared interface made another backend possible; processor-specific transformation options still need review. Check the actual bundle and transformation hashes before changing processors.<\/p>\n<p>Rails 7.0 changed the default for new applications in 2021 to reduce processing time and resource use. See the <a href=\"https:\/\/guides.rubyonrails.org\/upgrading_ruby_on_rails.html#active-storage-default-variant-processor-changed-to-vips\" target=\"_blank\" rel=\"noopener\">upgrade guide<\/a> and <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v7.0.0\/railties\/lib\/rails\/application\/configuration.rb\" target=\"_blank\" rel=\"noopener\"><code>load_defaults \"7.0\"<\/code><\/a>. Upgrading a gem does not advance application defaults, and overrides still matter. Read the effective processor from a successfully booted application.<\/p>\n<p><a href=\"https:\/\/github.com\/rails\/rails\/blob\/v5.2.0\/activestorage\/activestorage.gemspec\" target=\"_blank\" rel=\"noopener\">Marcel was already an Active Storage dependency in 5.2<\/a>. The March 2021 mimemagic licensing incident <a href=\"https:\/\/github.com\/rails\/rails\/issues\/41750\" target=\"_blank\" rel=\"noopener\">broke dependency resolution for affected lockfiles<\/a>. The <a href=\"https:\/\/rubyonrails.org\/2021\/3\/26\/marcel-upgrade-releases\" target=\"_blank\" rel=\"noopener\">26 March patches<\/a> moved that existing dependency to Marcel 1.0, packaging Apache Tika-derived MIME data directly. Retain dependency provenance and verify clean bundle installation even when application code is unchanged.<\/p>\n<p>There is another content-type change to examine now. <a href=\"https:\/\/github.com\/rails\/marcel\/releases\/tag\/v2.0.0\" target=\"_blank\" rel=\"noopener\">Marcel 2.0<\/a> was released in August 2026 and requires Ruby 3.3 or later, while the Rails integration in <a href=\"https:\/\/github.com\/rails\/rails\/pull\/58549\" target=\"_blank\" rel=\"noopener\">PR #58549<\/a> is merged on main. At this check date, <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.1.3.1\/activestorage\/activestorage.gemspec\" target=\"_blank\" rel=\"noopener\">released Rails 8.1.3.1 still requires Marcel <code>~&gt; 1.0<\/code><\/a>. Do not describe the integration as released Rails behavior or assign it to a future release without checking.<\/p>\n<p>Marcel 2 can change canonical MIME results for newly identified content without rewriting stored blob types. The Rails PR describes newly identified YAML changing from <code>text\/x-yaml<\/code> to <code>application\/yaml<\/code>. Test exact comparisons and allowlists against both. Upload workflows can depend on MIME identification and image processing, so review them together.<\/p>\n<h2 id=\"what-to-do-this-week\">What to do this week<\/h2>\n<ol>\n<li>Record the final image or stack, resolved bundle and loaded libvips version for both web and worker environments. Keep missing information visible.<\/li>\n<li>Choose a documented path that supplies the native-library and binding prerequisites, or removes the libvips dependency where the application permits it. Verify startup after the change.<\/li>\n<li>Exercise representative uploads, analysis and existing transformation options. Include content-type comparisons and stored blob metadata in the review before adopting Marcel 2.<\/li>\n<li>Recheck the dated package rows before deployment. Track framework support and stack end of life separately in the <a href=\"https:\/\/www.allerin.com\/services\/ruby-on-rails\/support-calendar\">Rails support calendar<\/a>.<\/li>\n<\/ol>\n<p>For an application upgrade, the <a href=\"https:\/\/www.allerin.com\/services\/rails-upgrades\">Rails upgrades guide<\/a> puts these native dependencies alongside the changes between framework versions.<\/p>\n<h2 id=\"sources-and-execution-scope\">Sources and execution scope<\/h2>\n<p>Sources were checked on 7 September 2026 and are linked beside their claims. AI assisted source comparison, code execution and drafting. The recorded experiments are isolated checks, not results from an Allerin client system.<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/rails\/rails\/security\/advisories\/GHSA-xr9x-r78c-5hrm\" target=\"_blank\" rel=\"noopener\">Rails advisory<\/a> and <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.1.3.1\/activestorage\/lib\/active_storage\/vips.rb\" target=\"_blank\" rel=\"noopener\">patched Active Storage initialization<\/a>.<\/li>\n<li><a href=\"https:\/\/www.libvips.org\/2022\/05\/28\/What%27s-new-in-8.13.html\" target=\"_blank\" rel=\"noopener\">libvips 8.13 release notes<\/a> and <a href=\"https:\/\/github.com\/libvips\/ruby-vips\/blob\/v2.2.1\/lib\/vips.rb\" target=\"_blank\" rel=\"noopener\">ruby-vips 2.2.1 source<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/docker-library\/official-images\/blob\/master\/library\/ruby\" target=\"_blank\" rel=\"noopener\">Official Ruby image definitions<\/a> and <a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2026\/06\/30\/ruby-3-4-10-released\/\" target=\"_blank\" rel=\"noopener\">Ruby 3.4.10 archive checksums<\/a>.<\/li>\n<li><a href=\"https:\/\/guides.rubyonrails.org\/upgrading_ruby_on_rails.html\" target=\"_blank\" rel=\"noopener\">Rails upgrading guide<\/a>. Package, stack and historical release sources are linked where their claims appear above.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>A patched Active Storage bundle can still fail against an older native library. Check package availability, the deployed image and the library Ruby actually loads.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[2037],"tags":[2042,2047,2048,2046,2049,2038],"class_list":["post-14864","post","type-post","status-publish","format-standard","hentry","category-ruby-on-rails","tag-active-storage","tag-docker","tag-heroku","tag-libvips","tag-marcel","tag-rails-upgrades"],"_links":{"self":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14864","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/comments?post=14864"}],"version-history":[{"count":3,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14864\/revisions"}],"predecessor-version":[{"id":14969,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/posts\/14864\/revisions\/14969"}],"wp:attachment":[{"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/media?parent=14864"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/categories?post=14864"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.allerin.com\/blog\/wp-json\/wp\/v2\/tags?post=14864"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}