Back to Blog

A 9.5 in Rails: the flaw is not in your application, it is in the library nobody chose

CVE-2026-66066, a CVSS 9.5 flaw in Ruby on Rails Active Storage, allows server files to be read by uploading an image when the application processes variants with libvips

Somebody uploads a photo. Their profile avatar, an attachment on a ticket, a product image in your customer portal. Your application does what it has done for three years: generate the thumbnail. And in that gesture — the most banal thing on any website — an attacker with no username and no password walks away with the contents of files on the server, starting with the process environment: the key that signs your sessions, the database one, the cloud storage ones. That is CVE-2026-66066, a 9.5 published on 29 July. What we find interesting is not the score. It is where the flaw sits: not in the code you commissioned, not exactly in Rails, but in which formats a C library considers safe to read — a library nobody at your company chose, knows is running, or could name. A library that has, moreover, published since 2022 which of its operations it has not verified against hostile input, and ships a switch to block them.

The facts, undecorated

As the official Rails advisory publishes them. Before any opinion, the data:

  • CVE-2026-66066, published on 29 July 2026. CVSS 4.0 of 9.5, critical. Category: arbitrary file read and possible remote code execution in Active Storage variant processing.
  • Affected versions: activestorage before 7.2.3.2, the 8.0 branch before 8.0.5.1 and the 8.1 branch before 8.1.3.1. Fixed in those three. Rails 6.x only applies if Active Storage is configured outside its defaults — and there comes the detail that strands anyone on that branch: Rails has published no fixed releases for anything earlier than 7.2, so on 6.x there is no patch, only mitigation or migration.
  • Three conditions must hold at once, according to the researchers: the application processes image variants with libvips, it accepts image uploads from untrusted users — through its own features or through direct uploads — and the installed libvips is linked against certain third-party libraries. With the ImageMagick (MiniMagick) processor, this one does not apply.
  • Careful with the second condition. Rapid7 warns that the documented chain needs the Active Storage direct-upload route to be reachable, and that route is present by default as soon as Active Storage is mounted, even if your application's own interface does not use direct uploads. Put differently: "we do not let people upload images" can be true on the screen and false in the router.
  • What the attacker gets: disclosure of the contents of arbitrary files accessible on the filesystem as the process user, including its environment variables — where secret_key_base, the master key, database credentials, storage credentials and third-party tokens usually live. From there, escalation to code execution and lateral movement to other systems.
  • It was reported by researchers 0xacb, s3np41k1r1t0 and castilho, of Ethiack, and RyotaK, of GMO Flatt Security. Full disclosure was scheduled for 28 August at the latest; public proofs of concept appeared ahead of that and Rails ended up publishing the details and a forensic tool in late July, in a dedicated repository.

One detail changes the size of the problem: since Rails 7.0 the default variant processor is Vips — in 6.x it was ImageMagick — and libvips is also the default processor in the official Rails Docker images and in Debian and Ubuntu setups. The vulnerable configuration is not an oddity somebody switched on deliberately: it is what ships as standard. With one nuance worth checking before assuming anything: that default is tied to config.load_defaults and not to the gem's version number, so an application migrated to 7.2 that still declares load_defaults 6.1 is still using ImageMagick.

The label was already there

Here is the part worth reading twice, and the reason we are giving a whole article to a CVE in a framework you may not even use. The advisory says it itself: libvips reads and writes formats through loaders and savers, many of them backed by third-party libraries, and it marks some of those operations as "unfuzzed" — not fuzz-tested, meaning not exercised with hostile input and therefore unsafe for untrusted content. What failed is that Active Storage did not disable them.

And that mechanism is not from this summer: it arrives with libvips 8.13, released on 28 May 2022, whose announcement explained that with the VIPS_BLOCK_UNTRUSTED variable any operation tagged as untrusted is prevented from running, so you can deploy "confident that unfuzzed code is not being exposed to internet data". Four years. The security information existed: published, in the right place, under the right name, marked operation by operation by the people who knew the code best, and with the switch already fitted. What did not exist was the decision to flip it at the next link. This is not an exotic memory overflow nobody could foresee; it is a warning label the layer above did not consume. And the distinction matters, because labels can be read and overflows cannot be guessed.

In case there is any doubt that this reading is not a quirk of ours: the category assigned to the flaw is CWE-1188, "initialization of a resource with an insecure default". Not "out-of-bounds file read", not "improper input validation". The official name of the problem points at the default.

Now count the links in the chain that ends on your server. Your company commissioned an application. Whoever built it chose Rails. Rails chose Active Storage. Active Storage chose libvips as the default processor. And libvips calls different third-party libraries for each image format. Five decisions, and your company only made the first. Your application's attack surface was decided by four projects you have never heard of, in good faith and with good judgement, solving problems that were not yours.

None of this is a reproach: neither libvips nor Rails did badly here. libvips labelled what it had not verified, which is exactly what you want from a serious library. Rails patched three branches at once, offered an interim mitigation requiring no code changes and, when public exploits forced its calendar, published the details and a forensic tool rather than staying quiet. That is doing it right. The problem is not in any link: it is that nobody owns the joint between two of them.

Does it affect you? Ten minutes will tell

Before the alarm, the check. It is entirely possible this one is not about you, and saying so is part of the job: a critical advisory that does not apply to your case, handled as if it did, costs you a week and your credibility. Four checks, run inside the machine or container serving production right now — with docker exec or kubectl exec if needed — not on the developer's laptop and not on the repository's main branch:

# 1) The REAL activestorage version in the environment serving production
bundle list | grep -i activestorage
grep "^    activestorage " Gemfile.lock

# 2) Configured variant processor: vips (affected) or mini_magick (not)
grep -rn "variant_processor" config/

# 3) Which defaults version the app loads (it decides the default above)
grep -rn "load_defaults" config/application.rb

# 4) The REAL libvips version. If the binary is missing, ask the library
vips --version 2>/dev/null || bundle exec ruby -e 'require "vips"; puts Vips::LIBRARY_VERSION'

Two traps in those four lines, and both push towards a false "not affected". First: if check 2 returns nothing you are not safe, you are on the default — and with load_defaults at 7.0 or later that default is Vips. Second: vips not existing as a command does not mean you do not have libvips. The binary lives in a separate package (libvips-tools on Debian and Ubuntu) and ruby-vips talks to the library directly, so the typical Rails application image has libvips and does not have vips. Hence the second half of line 4.

The last question is answered by a human, not a command: can someone outside upload an image? A contact form with an attachment, the avatar in a private area, the portal where customers upload delivery notes. And remember the direct-upload route: the right answer is not the screen's, it is the router's. One honest caveat to close: these checks tell you what you have, not whether anyone got in already. For that there is the forensic tool Rails published, and it needs logs many applications never keep.

What you can deploy today is not the patch

Every headline says "patch now", and they are right in substance. On the ordering, not entirely. Because in a bespoke business application, upgrading the framework and changing an environment variable do not cost the same or take the same time, and whoever decides needs to know that:

  1. Today: the interim mitigation. The advisory offers two ways to block the unverified operations, and the difference between them is not cosmetic. The VIPS_BLOCK_UNTRUSTED environment variable is a runtime change: it deploys without touching the application code, which is exactly what you need when somebody else maintains that code. The Vips.block_untrusted(true) call does the same but lives in an initializer, which means it is touching code and it does go through the release cycle. Both need libvips 8.13 or later; the second also needs ruby-vips 2.2.1 or later.
  2. This week: the actual patch. Move activestorage to 7.2.3.2, 8.0.5.1 or 8.1.3.1, and check that libvips in the container image is 8.13 or later. Rapid7 says it bluntly and it deserves underlining because it is the mistake we will see most: updating Rails or Active Storage alone is not sufficient when an older libvips version is installed. With automated tests and reproducible deployment, that is an afternoon. If the application has been deployed by hand since 2022 and there is no test suite, it is not an afternoon: it is a small project, and it is worth calling it that instead of promising Friday.
  3. On a schedule: rotate. It gets its own section below, because it is the part almost nobody does, and not out of laziness.

A warning about point one we have barely seen in the coverage: blocking unverified operations may stop some unusual format you currently process from being processed. That is not an annoying side effect, it is a decision to be made with your eyes open — and our answer is easy: we would rather a thumbnail failed than somebody read the process environment. But test it in staging first and watch the logs on day one.

Rotating is the expensive part. That is why it does not happen

The advisory is explicit: change every secret the process could read. secret_key_base, master keys, service and database credentials, third-party tokens. It is the right thing and it is the least-followed one, for a very practical reason almost nobody mentions: secret_key_base is what signs and encrypts a Rails application's session cookies. Changing it is not editing a line: it is deciding what happens to every open session of every user at that moment.

And the remaining credentials usually live in more places than the inventory admits: in the system that deploys, in the password manager of the supplier who built the application, in a config file on the laptop of somebody who left. If you run a CI/CD server, a good part of what needs rotating today also sits inside it, and rotating in one place and not the other is not having rotated.

We have no figure for how many Rails applications run in Spanish SMEs, nor what share will rotate anything this month. We have not measured it and we will not invent it. What we do see, when we sit down to look at this with someone, is that the question "how long would it take you to change every credential in this application?" almost never has an answer in hours or days; it gets a long pause and an awkward look. And that is the honest measure of your exposure: not "are we patched?", but how long you would take to rotate. That number tells you what a leak would cost you, this CVE's or the next one's.

Where your maintenance contract ends

Here the topic stops being about Rails. A normal IT maintenance contract covers servers, backups, antivirus, the network and the workplace. And it stops, quite naturally, exactly where the business application begins: because "the application belongs to the supplier who built it". That supplier, for their part, delivered, invoiced and closed the project three years ago. The result: the one piece of software in the building that answers requests from the internet, processes files from strangers and holds the database credentials is the only one not on any patching calendar.

Nobody is acting in bad faith. It is a contractual gap, and it closes by writing it down. When we do this inventory with a customer, the "applications" column gets five things, and none of them is glamorous:

  1. Which applications exist, what they run on and who wrote each one.
  2. Which exact version runs today, in production. Not the repository one: the image actually serving requests. We explained why that distinction is not a quirk of ours when we wrote that "latest" is not a version.
  3. What comes in from outside: forms, file uploads, integrations, webhooks.
  4. Which credentials the process can read and from where they get rotated.
  5. Who finds out about an advisory like this, through which channel and within how many days.

That is, in practice, what we do under data and applications: treating the business application as a production system rather than a closed deliverable, with the same boring discipline you demand of a server. And to be honest about it: we do not develop in Rails, so we are not here to sell you a migration. The part about deciding what gets patched first, with what real exposure and which credentials inside, is ordinary cybersecurity — the kind that never makes the brochure.

A 9.5 is not your risk. But here there is a public exploit

We said it with the 622 patches of a single July Tuesday and we repeat it here because nothing has changed: CVSS is a worst-case technical severity label, not a measure of your risk. It measures what happens if the conditions hold, not whether you meet them. That is why the ten-minute check comes before the alarm.

And here we should be precise, because the headlines blend two things that are not the same. Real exploitation, as far as anyone knows, there is none: Rapid7 said on 30 July it was not aware of any, and this is not in CISA's KEV catalogue. What has expired is "no public exploit": code claiming to exploit it is circulating, though — Rapid7's own caveat — it is unclear how closely it corresponds to the full chain the researchers handed privately to Rails. And Rapid7 itself has verified the escalation to remote execution and proposed a Metasploit module for it.

That distinction is not a technicality: it is the difference between having days and having weeks. The researchers had held the technical chain back until late August precisely to give people room, and the room evaporated on its own. When the entry condition is "accepts image uploads", a Metasploit module turns a difficult chain into a button, and that is the moment the number of people able to try it multiplies. You do not need to paint mass exploitation that does not exist today to justify looking at this during the week.

Three years running without trouble

The sentence we hear most often about a business application is that one: "it has been running for three years without trouble". And it is true, and it is exactly why nobody looks at it. A server that gives no trouble still shows up on the month's patch list; an application that gives no trouble disappears from the radar entirely, because giving trouble was its only sign of life.

Our suggestion is more modest than a security plan and fits in a half-hour meeting. Take the application your business depends on most and answer three things in writing: which framework version runs in production today, who decides when it gets updated, and how long you would take to change all its passwords. If the answers come out, congratulations, you do not need this article. If they do not — and normally they do not — you now have the first three lines of the inventory, and you have them without anything having happened to you. Which is, by a distance, the cheapest moment to write them. And if you would rather we looked for those three answers with you, here we are.

Sources (verified on 3 August 2026): the vulnerability details come from the official Rails security advisory GHSA-xr9x-r78c-5hrm, published on 29 July 2026 (CVE-2026-66066; CVSS 4.0 of 9.5; affected and fixed versions 7.2.3.2, 8.0.5.1 and 8.1.3.1; the explanation of libvips loaders and savers backed by third-party libraries and of the operations marked "unfuzzed", unsafe for untrusted content, which Active Storage failed to disable; the arbitrary file read impact including the process environment with secret_key_base and credentials; the VIPS_BLOCK_UNTRUSTED and Vips.block_untrusted(true) mitigations with libvips 8.13 or later and ruby-vips 2.2.1 or later; the instruction to change every secret; and credit to 0xacb, s3np41k1r1t0 and castilho, of Ethiack, and RyotaK, of GMO Flatt Security). The disclosure calendar — details scheduled for 28 August at the latest, public proofs of concept arriving early, and the early release of the details and the forensic tool — plus the nuance that Rails 6.x only applies if Active Storage is configured outside its defaults, come from BleepingComputer's coverage of 1 August 2026. That it affects only the Vips processor and not applications using Magick, and the three conditions (libvips, uploads from untrusted users through features or direct uploads, and a libvips build linked against certain third-party libraries), are stated in the note from the Ethiack researchers, who deliberately withheld the technical chain and the PoC. From Rapid7's analysis we take the CWE-1188 classification ("initialization of a resource with an insecure default"), that as of 30 July 2026 it was not aware of exploitation in the wild, that public code claiming to exploit it exists but it is unclear how closely it corresponds to the full chain reported privately to Rails, that the Active Storage direct-upload route must be reachable and is present by default even if the application interface does not use direct uploads, that Rails has published no fixed releases for branches earlier than 7.2, that updating Rails or Active Storage alone is not sufficient with an older libvips, and the verified escalation to remote execution with the proposed Metasploit module. That config.active_storage.variant_processor became :vips by default in Rails 7.0 (it was :mini_magick in 6.x), tied to config.load_defaults, is in the Rails configuration guide; that libvips is the default processor in the official Rails Docker images and on Debian and Ubuntu, in BleepingComputer. The blocking mechanism and its date — VIPS_BLOCK_UNTRUSTED introduced in libvips 8.13, released on 28 May 2022 — including the quote about deploying "confident that unfuzzed code is not being exposed to internet data", come from libvips' own announcement. Ours, not our sources': that what matters about this case is that the warning ("unfuzzed") was already published and what was missing was consuming it at the next link; the count of the five decisions in the dependency chain; the ordering mitigate today / patch this week / rotate on a schedule, and the warning that blocking unverified operations may break processing of some format; that the honest measure of exposure is how long you would take to rotate rather than whether you are patched; and the reading of the gap between the maintenance contract and the supplier who developed the application, with the five-point inventory list. We have not counted how many Rails applications run in production in Spanish SMEs, nor how many will rotate credentials, and we have no incidents of our own with this CVE: we have not measured it and we do not invent it. everyWAN does not develop in Ruby on Rails. Cover photo: "Man Working in Photographic Laboratory", State Government Photographer, Wikimedia Commons, CC0 1.0.

Who patches your business applications?

We build the inventory of your business applications: the real version in production, what comes in from outside, which credentials each process can reach and how quickly they can be rotated. We are vendor-agnostic and we do not develop in Rails: there is no migration waiting to be sold to you at the end of the report.

Talk to everyWAN

Was it useful? Share it

Subscribe to our newsletter

To receive IT stories, everyWAN news and exclusive subscriber offers, sign up to our mailing list

Minorisa de Sistemas Informaticos y Gestión S.L. © 2026
everyWAN
everyWAN