Image processing is code execution surface. That is the uncomfortable lesson behind CVE-2026-66066, a critical flaw in Ruby on Rails that the framework's security team disclosed on July 29, 2026. An unauthenticated attacker who can get a Rails application to build a single image thumbnail can read any file the server process can reach. The file that matters most is secret_key_base, the one key Rails uses to sign every session and cookie. Read it once and you have not just leaked a file. You have inherited the trust the whole application is built on. GitHub, acting as the numbering authority, scored it 9.5 on the CVSS v4 scale.
The bug lives in Active Storage, the file-attachment layer bundled with Rails. When an app renders a resized or cropped version of an uploaded image, Active Storage hands the raw file to libvips, a fast image library, without switching off the loaders that libvips itself marks as untrusted. Two research teams, Ethiack and GMO Flatt Security, found the same problem independently and reported it on July 22. The Rails team shipped fixes a week later and held the full technical writeup for August 28, but the details did not stay secret: by July 31, a working analysis and a third-party proof-of-concept were already public.
| Rails line | Vulnerable versions | Patched release |
|---|---|---|
| 7.0 through 7.2 | 7.2.3.1 and earlier | 7.2.3.2 |
| 8.0 | 8.0.5.0 and earlier | 8.0.5.1 |
| 8.1 | 8.1.3.0 and earlier | 8.1.3.1 |
| 6.x | only if the vips processor was enabled by hand | upgrade libvips or drop the dependency |
Every line since 7.0 is in scope by default: anything at or below 7.2.3.1, 8.0.5.0, or 8.1.3.0. The patch bumps each of those a single notch, so a 7.2 app moves to 7.2.3.2, an 8.0 app to 8.0.5.1, and an 8.1 app to 8.1.3.1. Rails 6.x escapes unless someone deliberately switched its image processor over to vips, which it never used out of the box.
How a picture reads your filesystem
The trick is that the upload is not really a picture. An attacker crafts a MAT version 7.3 file, a scientific data format built on top of HDF5, and labels it with an image/png content type. The file opens with the bytes MATLAB 5.0, which is enough to slip past the first sanity check. libvips then routes it to libmatio, the library that reads MAT files, and libmatio chases an external-dataset pointer buried in that file straight to whatever path the attacker wrote in. Whatever sits at that path comes back inside what the app believes is a harmless rendered thumbnail.
This is why the standard defenses do nothing here. Checking that an upload has an image content type does not help, because the file claims to be a PNG. Checking a magic-byte signature does not help either, because the interesting parsing happens several layers down, inside a format most developers have never heard of. The weakness is not really in Rails' own code. It lives in the seam where Active Storage hands a file off to a general-purpose image library that was never built to face an attacker's input. We saw the same shape in the Budibase symlink read via a crafted archive and in the MCP Atlassian upload flaw: an upload feature becomes a file-read primitive the moment the parser behind it is more powerful than the input it is fed.
Why one file read is a full compromise
An arbitrary file read sounds like a data-leak problem. On Rails it is an authentication problem. secret_key_base is not just another secret; it is the root of Rails' entire signing scheme. Every signed cookie, every session, and the variation_key that authorizes an Active Storage transformation all trace back to it. Recover that one value from the process environment and you can mint valid sessions for any user, including an administrator, without ever touching a password. That is the collapse: a single read does not expose one file, it hands over the key the application uses to trust everyone.
The escalation goes further. Security firm Rapid7, which published an early analysis under the name KindaRails2Shell, showed that the recovered signing material can be turned into remote code execution by forging a malicious image variation, a path that does not even need the older Ruby object-deserialization tricks. Ethiack chained its own read up to code execution through a separate ImageMagick issue, CVE-2025-24293. So the honest way to read this bug is not "attacker can read a file." It is "attacker can become any user, and with a little more work, run code." Anything the process can read is fair game: the database login, the object-storage keys, the mail and payment tokens, the lot. That is the same lesson the Keycloak vault-secret leak taught: the value of a read primitive is set by what it can point at.
The exposure you did not know you had
Here is the part the coverage has mostly skipped. You do not have to build an upload feature to be exposed. Mount Active Storage's routes and the anonymous direct-upload endpoint comes along for free, even in an app whose own screens never once offer a visitor a file picker. Your real attack surface includes a route you may believe you do not use. To fire the bug an attacker also needs a genuine signed variation_key, which they can lift from any public page that renders an Active Storage image, such as an avatar or a product photo. Most apps that store images at all hand one out for free.
Then there is the evidence problem. Rails runs a background job that sweeps away unattached blobs, the stray uploads that never got tied to a record. An attack like this leaves exactly those strays behind, so the sweep is quietly shredding your evidence: the cron cadence, not the intruder, sets the clock on any investigation. Rails shipped a forensic kit, rails-forensics-CVE-2026-66066, so defenders can comb their stored uploads for the malicious files before that job clears them out. Pause the cleanup job before you start hunting. This read-then-abuse-secrets pattern is the same one behind the recent Adminer takeover: the initial bug is only the door.
What to do today
Patch first. Take a 7.2 app to 7.2.3.2, an 8.0 app to 8.0.5.1, an 8.1 app to 8.1.3.1, and check that the box carries libvips at 8.13 or above alongside ruby-vips 2.2.1 or better. Cannot cut a release this hour? Shut off the dangerous loaders in place: export VIPS_BLOCK_UNTRUSTED, or drop a Vips.block_untrusted(true) line into an initializer. If your libvips is too old to know that switch, the only honest fallback is pulling the libvips dependency out and rendering images with something else.
Patching is the cheap part. The expensive part is that you must now treat every secret the app could read as burned. Cycle secret_key_base first, which tears up every signed cookie and boots all users out, then the database login, the storage service keys, and each outside token that shares the process environment. That blast radius is the piece teams underestimate: the fix is one bundle update, but the cleanup is a full credential rotation across everything the process can touch. Before you rotate, run the forensic toolkit against your stored blobs to see whether anyone reached the door before you closed it.