Rails Active Storage ImageMagick Flaw: CVSS 9.8 Fix Guide

The CyberSec Guru

Rails Active Storage ImageMagick Flaw

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

On November 4, 2026, the Ruby on Rails core team shipped versions 8.1.4.1 and 8.0.5.2, a coordinated security release that fixes 21 vulnerabilities across Action Pack, Action View, Active Support, Action Cable, Action Mailbox, and Active Storage. Besides the critical advisory, the batch includes four High-severity advisories, thirteen Medium-severity bugs, and three Low-severity flaws.

The critical one is GHSA-f3w6-w445-fwh9, scored 9.8 on CVSS v3 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). An unauthenticated remote attacker can write arbitrary files to an application server just by uploading a crafted file to an Active Storage attachment point. If the application keeps attachments on the local filesystem through Active Storage’s Disk service, the same bug also becomes an arbitrary file read. That read can expose secret_key_base, config/master.key, encrypted credentials, and database or cloud provider keys, which gives an attacker a direct path to remote code execution and lateral movement.

RyotaK of GMO Flatt Security found the flaw and reported it through HackerOne. It is not a memory-corruption bug in an image library or a broken authentication filter in a controller. It comes from a collision between two designs: Active Storage automatically extracts metadata when a file is attached, and ImageMagick picks its internal format handlers, called coders, based on file content instead of file extension. Active Storage historically called ImageMagick without any framework-level coder policy, so every Rails app configured with :mini_magick inherited whatever the host’s ImageMagick installation allowed. On standard Debian and Ubuntu servers, the packaged /etc/ImageMagick-6/policy.xml or /etc/ImageMagick-7/policy.xml leaves scripting and file-referencing coders enabled. One malicious SVG or polyglot attachment can then trigger file writes and reads before the controller action finishes.

The Rails team also disclosed a second High-severity Active Storage bug, GHSA-v924-7rp8-4mxh (CVSS 8.8). It involves temporary filename extensions in ActiveStorage::Blob#open when files are handed to ffprobe, ffmpeg, and ImageMagick. Add July 2026’s CVE-2026-66066, which exposed Rails 7.0+ apps running the default :vips processor to arbitrary file reads through unfuzzed libvips loaders, and a pattern emerges. Web frameworks keep passing untrusted byte streams to Swiss-Army-knife command-line media tools, and the bill is coming due.

There is also a hard deadline. Under the Rails maintenance policy, security support for the Rails 8.0 series ends on November 7, 2026, three days after 8.0.5.2 shipped. Rails 7.2 and earlier branches get no upstream patches at all.

Quick summary: who needs to patch and what to do

  • The flaw is GHSA-f3w6-w445-fwh9, CVSS 9.8, in Active Storage’s ImageMagick path.
  • The fixes ship in Rails 8.1.4.1 and 8.0.5.2. Rails 7.2 and earlier get no upstream patch.
  • You are exposed if you use :mini_magick, your host policy.xml does not block the dangerous coders, and your app accepts uploads from untrusted users.
  • Upgrade mini_magick to 5.1 or newer in the same commit as the Rails bump, or the app will refuse to boot.
  • If you also use the Disk service and met the exposure criteria, rotate every secret the application process can read.
Technical architecture of GHSA-f3w6-w445-fwh9, illustrating the Rails Active Storage upload pipeline, ImageMagick coder exploitation, arbitrary filesystem access, potential compromise and the patched security architecture
Technical architecture of GHSA-f3w6-w445-fwh9, illustrating the Rails Active Storage upload pipeline, ImageMagick coder exploitation, arbitrary filesystem access, potential compromise and the patched security architecture

How Active Storage hands every upload to ImageMagick

Many developers assume ImageMagick or libvips only touches an uploaded file when the app asks for a transformation, such as user.avatar.variant(resize_to_limit: [100, 100]) or a preview in a view template. In reality, Active Storage runs an analysis pipeline on every newly attached blob to fill its metadata column: width, height, rotation, duration, and whether audio or video streams exist. That is why GHSA-f3w6-w445-fwh9 needs nothing beyond an ordinary file upload.

When an untrusted user submits a multipart upload, or finishes a direct upload and attaches the signed blob ID to an Active Record model, Active Storage enqueues ActiveStorage::AnalyzeJob. It runs the analysis inline if the app is configured that way or calls it directly. The blob calls ActiveStorage::Blob#analyze, which walks Rails.application.config.active_storage.analyzers and picks the first analyzer whose .accept?(blob) returns true. With config.active_storage.variant_processor set to :mini_magick, the image analyzer is ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick. A single flag therefore decides both which engine builds image variants on demand and which analyzer inspects image blobs the moment they are attached.

Inside ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick#metadata, Active Storage streams the blob into a local temporary file with blob.open and passes that path to MiniMagick::Image.new(file.path). To read dimensions and orientation, MiniMagick spawns an external identify (or magick identify) child process. That process runs with the operating system user, filesystem permissions, working directory, and environment variables of the Rails server or the Sidekiq or Solid Queue worker.

How ImageMagick coders decide what a file is

At this point control moves from Ruby into ImageMagick’s C core, MagickCore, which has to work out how to parse the input. ImageMagick supports more than two hundred file formats, and each one is implemented by a modular handler called a coder. It identifies a format in one of three ways: an explicit prefix on the filename (such as png:/tmp/file), magic-byte inspection against the signatures registered in magic.xml, or inference from the file extension. Once it has a format, it calls that coder’s read function (Read*Image).

Ordinary coders such as PNG, JPEG, and WEBP decode pixel buffers into memory. Several built-in coders work differently. They act as scripting engines, meta-formats, or external command delegates, and they operate on file paths named inside the document itself. The main one here is MSL, the Magick Scripting Language, an XML-based language built into ImageMagick for automating batch operations without shell scripts. An MSL document tells ImageMagick to open files from disk, apply transformations, and write the result to whatever output path it names.

📬 Stay Ahead of Cyber Threats

Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.

Subscribe to the Newsletter →

According to the patch commit and advisory, the reported attack chain reached the MSL coder through an uploaded SVG processed by the Active Storage analyzer. Whether that works depends on how ImageMagick was compiled (it can render SVG with an internal XML/MVG renderer, librsvg, or inkscape) and on how XML namespaces, embedded elements, and nested format specifiers get resolved during identify. In the vulnerable setups, an SVG can push ImageMagick into the MSL coder during routine metadata probing. If an attacker can also influence coder selection through magic bytes or filename extensions, which GHSA-v924-7rp8-4mxh makes easier, ImageMagick runs the read and write directives in the payload while MiniMagick believes it is only asking for image dimensions.

Other dangerous ImageMagick coders

MSL is not the only risky handler in a default ImageMagick install. The Rails security team said in the advisory that it received one specific attack chain and does not assume it is the only one. Other coders show similar file or network behavior:

  • TEXT, CAPTION, LABEL, and PANGO, combined with @filename indirection. A string or path prefixed with @ tells ImageMagick to read that file from disk and render its contents as text pixels on a canvas.
  • EPHEMERAL reads an image from disk and then calls unlink() on the file once it is loaded, which turns a read primitive into arbitrary file deletion.
  • MVG (Magick Vector Graphics) is ImageMagick’s internal vector language and the engine behind the ImageTragick bugs (CVE-2016-3714). It can reference external coders or files through fill "url(...)" or image Over ... primitives.
  • URL, HTTP, HTTPS, FTP, and FTPS are virtual protocol coders. They let an attacker mount server-side request forgery by fetching remote payloads or probing internal cloud metadata endpoints such as 169.254.169.254.
  • The Ghostscript-backed coders (PS, PS2, PS3, EPS, PDF, XPS) and the gs delegate. PostScript is Turing-complete. Modern Ghostscript enables -dSAFER by default, but sandbox escapes in its interpreter have repeatedly allowed arbitrary file I/O and shell command execution when ImageMagick invoked it silently.

Active Storage passed uploads straight to MiniMagick without restricting any of these coders, so the only barrier between an untrusted upload and these handlers was the host’s global policy.xml. Researchers who examined the default policy files shipped by Debian and Ubuntu found gaps. Those two distributions underpin most containerized and VM-based Rails deployments, including the official Ruby Docker base images. Debian had added rules after the 2016 ImageTragick disclosures to block EPHEMERAL, URL, HTTPS, MVG, and MSL on certain paths or patterns, but coverage varied across distribution releases, and the coders involved in the reported SVG-to-MSL chain were not comprehensively blocked.

From arbitrary file write to remote code execution

The 9.8 rating reflects how easy the bug is to trigger and how fast a file read or write inside a Rails application directory becomes full server compromise. The advisory separates two impact tiers based on config/storage.yml:

  • Every vulnerable application (:mini_magick, a permissive policy, and untrusted uploads) is exposed to arbitrary file write on the server running the ImageMagick analyzer.
  • Applications that also use the Disk service (ActiveStorage::Service::DiskService) are additionally exposed to arbitrary file read of anything the Ruby process can access.

Why the Disk service turns a write bug into a file read

The underlying primitive is a write inside ImageMagick, so why does the Disk service enable reads? When the analyzer opens an uploaded file, it collects width and height and finishes. identify itself never writes a transformed image back into Active Storage. An MSL script running during that identify call can, however, read a sensitive local file (or render its text onto an image canvas, or embed the raw bytes in an image structure or EXIF profile) and write the result directly over an existing attachment blob in the local storage/ directory.

With a cloud backend such as Amazon S3, Google Cloud Storage, or Azure Storage, Active Storage streams the blob down to a temporary file on the worker, runs identify, and deletes the tempfile. Overwriting that tempfile does not change the object in S3, so the attacker has no easy way to download what ImageMagick wrote unless it lands in the public/ directory of a web worker that serves static assets.

The Disk service is different. Every uploaded blob lives permanently on the local filesystem at storage/xx/yy/<blob_key>, where xx and yy are the first two pairs of characters of the blob’s key. Keys and paths can be predicted, leaked, or overwritten in place. An attacker can upload a harmless decoy attachment, note its download URL or path, then upload a malicious payload that reads /app/config/master.key or /proc/self/environ and overwrites the decoy blob on disk. Fetching the decoy through Active Storage’s normal download route returns the stolen secret.

Direct routes from file write to RCE

Even without the Disk service read path, an arbitrary file write on a Rails host offers several routes to remote code execution:

  • Writing into public/ or an asset directory. If config.public_file_server.enabled = true, or Nginx shares a volume with the app container, files copied there can be fetched immediately.
  • Overwriting executed code or templates. If the container or VM filesystem is not mounted read-only, an ERB template written into app/views/, or a Ruby file loaded dynamically during request handling, gives instant Ruby execution on the next HTTP request.
  • Writing to authorized_keys, crontabs, or .bashrc. On traditional VM deployments where the app runs as a normal user with a writable home directory (/home/deploy/), writing /home/deploy/.ssh/authorized_keys hands the attacker an SSH shell.

How a stolen secret_key_base leads to RCE

When an attacker uses the file-read primitive against a Disk-service deployment, RCE follows through cryptographic forgery instead of filesystem changes. Reading /proc/self/environ on Linux, or config/master.key together with config/credentials.yml.enc, yields the app’s secret_key_base. Rails passes that root secret to ActiveSupport::KeyGenerator (PBKDF2-HMAC-SHA1 or SHA256) to derive the signing and encryption keys for the whole framework. With it, an attacker can:

  • Forge session cookies. Anyone holding secret_key_base can decrypt a user’s session cookie (ActionDispatch::Cookies::EncryptedKeyRotatingCookieJar), change the user_id or privilege flags, re-encrypt it with AES-GCM, and log in as an administrator. If any cookie or internal serializer uses Marshal deserialization, as in legacy or hybrid cookie setups, a forged signed cookie triggers Ruby deserialization RCE on the first request.
  • Forge Signed Global IDs. Active Storage and Action Text use SignedGlobalID to reference models safely across requests, so a forged ID can attach, overwrite, or expose records that belong to other tenants.
  • Mint Active Storage tokens. DiskController checks a signed token from Rails.application.message_verifier(:active_storage) before it serves or accepts local files. The stolen key lets an attacker create DiskController#show and DiskController#update tokens for any key or content disposition on the server.
  • Decrypt external service credentials. A stolen config/master.key opens config/credentials.yml.enc, which exposes the production PostgreSQL or MySQL password, Redis connection URLs (where an attacker can push malicious Sidekiq or ActiveJob payloads serialized as JSON or Marshal), AWS IAM keys, Stripe API keys, and OAuth client secrets.

Is your Rails app vulnerable to GHSA-f3w6-w445-fwh9?

Not every Rails app that uses Active Storage is vulnerable. This advisory is exploitable only when all three of these conditions hold at once:

  1. ImageMagick is the Active Storage processor (:mini_magick) instead of libvips.
  2. The host’s ImageMagick policy.xml does not already block the dangerous coders. Standard Debian and Ubuntu packages do not block everything this attack chain needs.
  3. The app accepts file uploads from untrusted users. A public registration avatar, a support ticket attachment, a comment upload, or any authenticated customer upload endpoint qualifies.

The first condition catches many teams off guard because of how Rails handles defaults across upgrades. Rails 7.0 switched the default Active Storage variant processor from :mini_magick to :vips, but Rails applies version-specific defaults through config.load_defaults in config/application.rb. An app created on Rails 5.2, 6.0, or 6.1 and later bumped to Rails 7.1, 8.0, or 8.1 without raising config.load_defaults to 7.0 or higher is still running ImageMagick on Rails 8. The same is true if a developer left config.active_storage.variant_processor = :mini_magick in config/application.rb or config/environments/production.rb to avoid migrating legacy combine_options syntax.

In a scan of nearly four hundred production Rails repositories published right after the advisory, roughly 4.8% of Active Storage deployments were still explicitly or implicitly set to :mini_magick, and more than two-thirds of those pinned older versions of the mini_magick gem. Do not guess from static config files. Check what the live app actually uses by running this in the production environment:

bin/rails runner 'puts Rails.application.config.active_storage.variant_processor.inspect; puts Rails.application.config.active_storage.analyzers.inspect'

If variant_processor prints :mini_magick (or nil on an app whose load_defaults is 6.1 or lower) and ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick appears in the analyzers array, the app meets the first condition.

Why :mini_magick and :vips swapped places in 2026

The history here has an odd symmetry. In late July 2026 the Rails team patched CVE-2026-66066 (CVSS 9.5), a critical Active Storage flaw that hit apps using :vips, the default for load_defaults 7.0 and newer. Active Storage had not set VIPS_BLOCK_UNTRUSTED (or called Vips.block_untrusted(true)), so crafted uploads could invoke unfuzzed third-party format loaders linked into libvips and achieve arbitrary file read and RCE during variant generation. Teams still on :mini_magick were completely unaffected at the time.

Three months later the exposure has flipped. :vips deployments are immune to the ImageMagick coder injection, while :mini_magick deployments face a worse primitive that fires on attachment, before anyone requests a variant.

GHSA-v924-7rp8-4mxh: how filename extensions pick the decoder

Upgrading only for ImageMagick, or assuming that a switch to :vips removes the need to patch, would be a mistake. The same release fixes GHSA-v924-7rp8-4mxh (CVSS 8.8, AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H), a High-severity bug in how Active Storage names temporary files when it hands blobs to external media tools.

When Active Storage analyzes a video or audio attachment, generates a video preview image, or processes an image with ImageMagick, it downloads the blob to a local temporary file with ActiveStorage::Blob#open. Before 8.1.4.1 and 8.0.5.2, that method built the temp file’s name by keeping the extension from the user-supplied blob.filename. Suppose a user uploaded exploit.avs and declared its Content-Type as video/mp4 or image/jpeg in the multipart request or the direct upload metadata. Active Storage recorded the content type, accepted the attachment, and wrote the raw bytes to something like /tmp/ActiveStorage-12345-20261104-xxxxx.avs. It then passed that exact .avs path to:

  • ffprobe, through ActiveStorage::Analyzer::VideoAnalyzer and ActiveStorage::Analyzer::AudioAnalyzer, which run automatically on attach by default, even on :vips apps
  • ffmpeg, through ActiveStorage::Previewer::VideoPreviewer
  • identify and convert, through ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick and the :mini_magick variant processor

FFmpeg and ImageMagick normally identify a container format from its leading magic bytes. When a file has no recognizable magic bytes, as with plain-text scripting formats, both fall back to the filename extension to choose a demuxer, decoder, coder, or delegate. An attacker who controlled the extension and the declared Content-Type could therefore force ffprobe, ffmpeg, or ImageMagick to run any extension-mapped decoder compiled into the binary.

The advisory describes one concrete path through FFmpeg: AviSynth script evaluation. AviSynth (.avs) is a frameserving scripting language for video post-production. When FFmpeg is built with --enable-avisynth and the AviSynthPlus runtime is installed, a file ending in .avs makes ffprobe or ffmpeg evaluate it as an AviSynth script. Inside such a script an attacker can read arbitrary local files and render their contents as text frames in the generated preview image, which discloses secrets like secret_key_base. The script can also load a compiled native shared library the attacker previously uploaded, which means immediate RCE inside the ffmpeg process.

Default Debian, Ubuntu, and Homebrew FFmpeg packages do not enable --enable-avisynth. Static GPL builds downloaded directly from ffmpeg.org for Linux and Windows do, and other extension-triggered decoders in both FFmpeg and ImageMagick stay reachable without AviSynth.

The fix separates the temp file’s extension from unverified user input by checking it against Marcel, Rails’ MIME-type detection library. In 8.1.4.1 and 8.0.5.2, ActiveStorage::Blob#open keeps the uploaded file’s extension on the tempfile only when Marcel maps that extension to the blob’s recorded content_type. If an attacker declares Content-Type: video/mp4 but names the file payload.avs, Marcel sees that .avs does not match video/mp4, and the temp file gets no extension at all. If a multipart upload arrives as a Rack tempfile whose filename fails the same check, Active Storage now copies it to a stripped tempfile before handing it to any external analyzer or previewer. Legitimate uploads whose extension disagrees with their MIME type still work, because FFmpeg and ImageMagick identify standard binary media formats from their magic bytes.

How the Rails patch hardens ImageMagick with MAGICK_CONFIGURE_PATH

The Rails team did not try to sanitize SVG payloads in Ruby or bolt fragile flags onto individual identify and convert calls. It fixed GHSA-f3w6-w445-fwh9 at the process configuration layer. Versions 8.1.4.1 and 8.0.5.2 ship a restrictive ImageMagick security policy inside the activestorage gem, at activestorage/lib/active_storage/image_magick/policy.xml.

During application initialization, Active Storage takes the directory that holds this file and prepends it to the MAGICK_CONFIGURE_PATH environment variable set on MiniMagick.cli_env. MiniMagick passes cli_env to Open3 whenever it spawns a child process, so every ImageMagick command run through MiniMagick inherits the policy. That covers Active Storage analyzer and variant calls and also any direct MiniMagick use elsewhere in your application.

The shipped policy denies (rights="none") five groups of dangerous capabilities:

  • The MSL scripting coder (pattern="MSL"), blocked whether it is reached through a file extension, an explicit msl: prefix, or nested SVG/XML resolution.
  • Coders that read files, delete files, or render text from disk: EPHEMERAL, TEXT, CAPTION, LABEL, PANGO, HTML, CIN, PLT, and related meta-coders.
  • Remote and local reference coders and screen-display coders: URL, HTTP, HTTPS, FTP, FTPS, FILE, MVG, SHOW, WIN, and X (the X11 display and capture coders).
  • Ghostscript-backed coders and the gs delegate: PS, PS2, PS3, EPS, EPSF, EPSI, PDF, PDFA, and XPS, plus <policy domain="delegate" rights="none" pattern="gs" />.
  • Path indirection through @filename, blocked with <policy domain="path" rights="none" pattern="@*" />, so ImageMagick cannot read arguments or text payloads from local paths prefixed with @.

Operators who cannot upgrade Rails right away should merge the following ruleset into the system policy at /etc/ImageMagick-7/policy.xml or /etc/ImageMagick-6/policy.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)+>
<!ATTLIST policymap xmlns CDATA #FIXED ''>
<!ELEMENT policy EMPTY>
<!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
]>
<policymap>
<!-- 1. Block indirect file reads via @filename syntax -->
<policy domain="path" rights="none" pattern="@*" />
<!-- 2. Block the MSL scripting coder and vector/meta scripting coders -->
<policy domain="coder" rights="none" pattern="MSL" />
<policy domain="coder" rights="none" pattern="MVG" />
<!-- 3. Block coders that delete, read external text files, or render local files -->
<policy domain="coder" rights="none" pattern="EPHEMERAL" />
<policy domain="coder" rights="none" pattern="TEXT" />
<policy domain="coder" rights="none" pattern="TXT" />
<policy domain="coder" rights="none" pattern="CAPTION" />
<policy domain="coder" rights="none" pattern="LABEL" />
<policy domain="coder" rights="none" pattern="PANGO" />
<!-- 4. Block remote/local network fetchers and X11/screen display coders -->
<policy domain="coder" rights="none" pattern="URL" />
<policy domain="coder" rights="none" pattern="HTTP" />
<policy domain="coder" rights="none" pattern="HTTPS" />
<policy domain="coder" rights="none" pattern="FTP" />
<policy domain="coder" rights="none" pattern="FTPS" />
<policy domain="coder" rights="none" pattern="FILE" />
<policy domain="coder" rights="none" pattern="SHOW" />
<policy domain="coder" rights="none" pattern="WIN" />
<policy domain="coder" rights="none" pattern="X" />
<policy domain="coder" rights="none" pattern="XBM" />
<policy domain="coder" rights="none" pattern="XPM" />
<policy domain="coder" rights="none" pattern="XWD" />
<!-- 5. Block Ghostscript-backed document coders and the gs delegate -->
<policy domain="coder" rights="none" pattern="PS" />
<policy domain="coder" rights="none" pattern="PS2" />
<policy domain="coder" rights="none" pattern="PS3" />
<policy domain="coder" rights="none" pattern="EPS" />
<policy domain="coder" rights="none" pattern="EPSF" />
<policy domain="coder" rights="none" pattern="EPSI" />
<policy domain="coder" rights="none" pattern="PDF" />
<policy domain="coder" rights="none" pattern="PDFA" />
<policy domain="coder" rights="none" pattern="XPS" />
<policy domain="delegate" rights="none" pattern="gs" />
</policymap>

Six deployment pitfalls when upgrading to Rails 8.1.4.1 or 8.0.5.2

The fix depends on gem behavior, environment variables, and the order in which ImageMagick evaluates XML policy rules. Bumping rails in your Gemfile and deploying can crash the app at boot or leave the hole open in production. Check these six failure modes before you ship.

Boot-time crash when mini_magick is older than 5.1

Prepending the policy path to MAGICK_CONFIGURE_PATH relies on MiniMagick.cli_env, which mini_magick supports properly from version 5.1.0 onward. With an older version such as 4.12.0 or 5.0.x, Rails 8.1.4.1 and 8.0.5.2 deliberately raise an exception during boot if config.active_storage.variant_processor == :mini_magick, instead of failing silently. Check your lockfile and upgrade mini_magick in the same commit as the Rails bump:

bundle list | grep mini_magick
bundle update mini_magick

The 5.x series modernized several internal APIs compared with 4.x. If your app calls MiniMagick::Tool::Convert or custom MiniMagick methods outside Active Storage, run your full test suite against mini_magick 5.1 or newer before pushing to production.

Host policy.xml rules can override the shipped Rails policy

ImageMagick does not stop at the first policy.xml it finds in MAGICK_CONFIGURE_PATH. It loads every policy.xml along its configuration search path, so the Rails-shipped policy gets combined with /etc/ImageMagick-7/policy.xml (or the version 6 path) and any user-level configuration files. Under ImageMagick’s evaluation model, a later rule in the host policy that explicitly grants access, such as <policy domain="coder" rights="read|write" pattern="*" /> or an explicit rights="read|write" pattern="MSL", overrides the block in the Rails policy.

A successful boot does not prove the policy works. Run the verification command from the Rails advisory inside your actual production container or server:

bin/rails runner 'puts MiniMagick.identify { |i| i.list "policy" }'

The output should list the path to activestorage-.../lib/active_storage/image_magick/policy.xml at the top, followed by coder: MSL with rights None, delegate: gs with rights None, and path: @* with rights None. Confirm that no later section re-enables those coders with Read Write.

Custom MiniMagick environment or shell overrides

If an initializer assigns a fresh hash with MiniMagick.cli_env = { ... } instead of changing the existing one with MiniMagick.cli_env["KEY"] = ..., it overwrites the MAGICK_CONFIGURE_PATH entry that Active Storage set in its railtie boot sequence. The shipped policy also never reaches the identify or convert binary if your app clears the child environment through MiniMagick.cli_prefix (for example by wrapping calls in env -i, firejail, or bwrap) or registers a custom MiniMagick::Shell. In any of these setups, copy the rules from activestorage/lib/active_storage/image_magick/policy.xml directly into the host’s /etc/ImageMagick-7/policy.xml.

Broken PDF and PostScript handling

The shipped policy blocks Ghostscript (delegate="gs") and every Ghostscript-backed coder (PDF, PDFA, PS, EPS). Standard Active Storage PDF previewers are unaffected, because ActiveStorage::Previewer::MuPDFPreviewer uses mutool and PopplerPDFPreviewer uses pdftoppm. Apps that rasterize PDFs through custom MiniMagick calls, or that run ImageMagick variants directly on PDF blobs, will start failing after the upgrade. If your app deliberately renders PDFs or PostScript through ImageMagick, re-allow the gs delegate and the PDF coder in your host’s system policy, and make sure Ghostscript is fully patched.

GraphicsMagick deployments get no protection

If your app points MiniMagick at GraphicsMagick (MiniMagick.cli = :graphicsmagick), the shipped policy.xml does nothing, because GraphicsMagick does not read ImageMagick policy files. The advisory scopes GHSA-f3w6-w445-fwh9 to ImageMagick’s coder architecture, but operators on gm should audit their configuration anyway and still upgrade for GHSA-v924-7rp8-4mxh.

The other 19 vulnerabilities in the release

Because 8.1.4.1 and 8.0.5.2 bundle 21 security fixes, two other changes can affect production behavior on deploy:

  • Google Cloud Storage direct uploads (GHSA-hfr4-8rjc-97m2 and GHSA-3w93-6hmw-whh4). Active Storage now enforces content-length range and content-type signing on GCS direct uploads. If you use them, add x-goog-content-length-range to the responseHeader array in your bucket’s CORS configuration, or browser direct uploads will fail with CORS errors after the patch.
  • A new rails-html-sanitizer floor. Action View now requires rails-html-sanitizer 1.7.2 or newer to resolve five XSS advisories (GHSA-v6rj-xm57-3p59, GHSA-rq8q-p985-fjq7, GHSA-68p9-83h6-5wpj, GHSA-2p2f-4rq4-gjwc, and GHSA-2w9f-fxwj-w2qf). Version 1.7.2 changes how some sanitized HTML attributes are serialized, so brittle tests that assert exact raw HTML equality on sanitize or strip_tags output may fail in CI.

The release also patches three other issues worth knowing about. GHSA-wm3x-v3qw-4jh3 (CVSS 7.5) is a Strong Parameters bypass: ActionController::Parameters#each_key yielded raw underlying hashes instead of wrapped Parameters instances, which completes an incomplete fix for CVE-2020-8164. GHSA-8474-xrp6-wh69 (CVSS 7.5) is an unauthenticated regular expression denial of service in ActiveSupport::Inflector#safe_constantize, reachable through Action Cable’s default endpoint. The third is an IP-spoofing flaw in Rails 8.1, where Rack 3 prioritized the Forwarded HTTP header over X-Forwarded-For when calculating request.remote_ip.

How to tell if you were attacked and which secrets to rotate

The Rails advisory says plainly that there is no built-in way to check whether an application has already been targeted through GHSA-f3w6-w445-fwh9. The bug fires inside the identify subprocess during routine attachment analysis, so a successful file read or write does not have to raise a Ruby exception, crash a worker, or leave an odd stack trace in log/production.log.

If you are auditing a system that was exposed (:mini_magick plus untrusted uploads), either before or right after patching, these are the places to look.

Audit the active_storage_blobs table

Query for suspicious uploads over the past several weeks:

SELECT id, key, filename, content_type, metadata, byte_size, created_at
FROM active_storage_blobs
WHERE filename ILIKE '%.svg%'
OR filename ILIKE '%.msl%'
OR filename ILIKE '%.mvg%'
OR filename ILIKE '%.avs%'
OR content_type ILIKE '%svg%'
OR metadata = '{}'
OR metadata IS NULL
ORDER BY created_at DESC;

Pay particular attention to blobs whose content_type claims image/png, image/jpeg, or image/svg+xml but whose metadata has no width and height keys. That pattern suggests identify ran a script or failed to return normal raster dimensions. Also inspect the raw contents of recently uploaded SVG files on disk or S3 for <image>, <read, <write, msl:, text:, or @ strings.

Inspect the filesystem and container diff

On Docker or Kubernetes deployments, run docker diff <container_id> or inspect the overlayfs upperdir layers on web and worker nodes. Either one shows any file written or modified inside /app, /tmp, or /home after the container started.

Rotate every secret the app process could read

A file-read attack against a Disk-service deployment, or an AviSynth script attack through GHSA-v924-7rp8-4mxh, can exfiltrate /proc/self/environ or config/master.key without changing a single byte on the filesystem. A clean forensic result is not proof of safety. If your app met the exposure criteria and used the Disk service, the advisory says to rotate everything the application process can read:

  • secret_key_base
  • config/master.key (and RAILS_MASTER_KEY in environment variables), plus every secret inside config/credentials.yml.enc
  • Database usernames and passwords (DATABASE_URL and config/database.yml)
  • Cloud storage credentials (AWS S3, GCS, Azure keys) and Redis or cache credentials
  • All third-party API keys, webhook signing secrets, and OAuth tokens

Do not keep the old secret_key_base in a fallback rotator. In routine hygiene rotations, developers often register the old key in ActiveSupport::MessageEncryptor or MessageVerifier rotation queues so existing sessions and signed URLs keep validating during a transition. After a suspected compromise through GHSA-f3w6-w445-fwh9 or GHSA-v924-7rp8-4mxh, that fallback still lets an attacker with the stolen key forge valid session cookies and signed GlobalIDs. Replace secret_key_base cleanly. That logs out every active cookie session, invalidates existing SignedGlobalID tokens, and expires previously generated Active Storage signed URLs.

Rails 8.0 end of life on November 7, 2026: what to do now

This release forces a decision for any team on Rails 8.0 or an older branch. Under the Ruby on Rails maintenance policy, security support for the 8.0 series ends on Saturday, November 7, 2026, seventy-two hours after 8.0.5.2 was tagged. Unless an emergency regression or a new zero-day lands first, 8.0.5.2 is the last security patch the 8.0 branch will receive. Applications on Rails 7.2 (which reached end of life in August 2026), 7.1, 7.0, or 6.1 received no patch releases for any of these 21 vulnerabilities.

If you run Rails 8.0

Deploy 8.0.5.2 today, along with mini_magick 5.1 or newer. Patch-level security bumps add no features and can go out in hours. After that, 8.0.5.2 leaves you on an unsupported branch starting next week, so your roadmap needs to move to 8.1.4.1, which stays supported for security fixes until October 2027.

If you are stuck on Rails 7.2 or older

If you cannot reach Rails 8.1 this week, do these three things:

  1. Copy the full ruleset from activestorage/lib/active_storage/image_magick/policy.xml into /etc/ImageMagick-7/policy.xml (or /etc/ImageMagick-6/policy.xml) on every web and background worker node, then verify it with identify -list policy.
  2. Backport the git patches linked in GHSA-f3w6-w445-fwh9 and GHSA-v924-7rp8-4mxh, or turn off automatic video and audio analysis and previews with an initializer until you can upgrade:
# config/initializers/active_storage_hardening.rb
Rails.application.config.after_initialize do
Rails.application.config.active_storage.previewers.delete(ActiveStorage::Previewer::VideoPreviewer)
Rails.application.config.active_storage.analyzers.delete(ActiveStorage::Analyzer::VideoAnalyzer)
Rails.application.config.active_storage.analyzers.delete(ActiveStorage::Analyzer::AudioAnalyzer)
end
  1. Audit every call site where user-supplied files or filenames reach MiniMagick, ImageProcessing, ruby-vips, ffmpeg, Ghostscript, or LibreOffice. Run those external media binaries inside least-privilege sandboxes, with read-only application code mounts and no access to application secrets in /proc/self/environ.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

News

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.