TL;DR
- Rails advisory GHSA-xr9x-r78c-5hrm (July 29) patches CVE-2026-66066 in Active Storage. CVSS 4.0 9.5. Fixed in activestorage 7.2.3.2, 8.0.5.1, 8.1.3.1.
- You are exposed when the app uses
config.active_storage.variant_processor = :vips(Rails 7.0+ default) and accepts image uploads from untrusted users. The advisory is explicit: generating variants is not a separate requirement. An app that has never produced a thumbnail is still in scope if it serves representations. - Root cause is a two-layer type split: Rails gates processing on the client-declared
content_typefrom direct uploads; libvips picks a decoder from magic bytes and can route into libmatio loaders Rails never disabled.reference/the-attack.mdin the forensics repo traces the full call chain. - libvips >= 8.13 is mandatory for the patch to work. Older libvips cannot block "unfuzzed" loaders; patched Rails refuses to boot if libvips is too old. Upgrading the gem alone is not enough.
- After upgrade: rotate
secret_key_base,RAILS_MASTER_KEY, DB creds, storage keys, and any env secret the worker could read. Run the forensics repo'skr2s-was-i-vulnerable/kr2s-was-i-exploitedskills against a copy of production data if you were exposed during the window.
What KindaRails2Shell actually is
Researchers dubbed it KindaRails2Shell (Ethiack, GMO Flatt Security). The name is fair: arbitrary file read from the app process, with secret_key_base and other env vars on the prize shelf, and a plausible path to RCE from there.
The Rails advisory describes the class cleanly. libvips exposes many format loaders; some are marked "unfuzzed" (unsafe on untrusted bytes). Active Storage never blocked those operations. An attacker who can upload a crafted file and trigger Active Storage to process it may invoke one.
That last clause is where teams mis-scoped the blast radius. The patched advisory says variant generation is not required for exposure. If your app accepts user image uploads and ever serves an Active Storage representation URL (avatar, attachment preview, url_for on a variant), you meet the bar. You do not need background jobs that pre-generate thumbnails.
The mechanism Rails published on July 31
Rails held attack details until August 28 originally, then published early on July 31 after PoCs appeared. The rails-forensics-CVE-2026-66066 repo documents the chain from GET /rails/active_storage/representations/... to an arbitrary file read.
Four junctions matter:
- Direct uploads trust the client
content_type.create_before_direct_upload!never runs Marcel sniffing, so a blob can stayimage/pngin the database while the bytes are something else. Blob#variable?only checks that stored type againstActiveStorage.variable_content_types. No Ruby code reads file bytes at this gate.Transformers::Vipsdoes not overridevalidate_transformation. ImageMagick's transformer uses an allowlist; Vips inherits the base class that only rejectscombine_options. rails/rails#56995 would tighten this for escalations; it is not what enables the file read.Vips::Image.new_from_filelets libvips pick the loader. A crafted file can satisfymatload's sniffer (MATLAB 5.0in the first ten bytes) whilelibmatiodispatches as MAT 7.3 from bytes 124–125, reaching HDF5 External File List reads of an attacker-chosen path.
The forensics write-up's punchline: Rails and libvips disagree about what the file is; libvips and matio disagree about which MAT version it is. Blocking untrusted loaders (VIPS_BLOCK_UNTRUSTED or Vips.block_untrusted(true)) stops the handoff. Adding another content-type sniffer at the Rails layer does not fix the disagreement below it.
Patch, libvips floor, and breaking changes
Upgrade activestorage to 7.2.3.2, 8.0.5.1, or 8.1.3.1. Confirm the host has libvips >= 8.13. Below that floor there is no supported workaround except removing libvips from the app; patched Rails will raise at boot rather than run in an unsecurable configuration.
If you cannot upgrade immediately on libvips 8.13+, set VIPS_BLOCK_UNTRUSTED in the worker environment, or call Vips.block_untrusted(true) from an initializer when ruby-vips >= 2.2.1.
The security release also carries a behavior break: variant generation for BMP, ICO, and PSD now raises Vips::Error. SVG, JPEG XL, JPEG 2000, and Netpbm no longer record width/height on analyze. Plan for that if you relied on those formats in user uploads.
Forensics and secret rotation
Upgrading closes the hole; it does not un-leak secret_key_base, config/credentials.yml.enc, storage service keys, or DB passwords if someone already read the process environment. The advisory's rotation list is long on purpose. Changing secret_key_base expires sessions and invalidates signed cookies, signed global IDs, and Active Storage URLs.
The forensics repo ships bin/kr2s_scan_active_storage_blobs.rb and agent skills to answer was I vulnerable? and was I exploited? Run them against a copy of your Active Storage database and object store, not against the tool repo itself. 37signals used the same detector on production data and matched the researchers' test uploads.
Sources
- Rails security advisory GHSA-xr9x-r78c-5hrm / CVE-2026-66066 — impact, affected versions, libvips floor, workarounds, secret rotation.
- Rails forensics repo:
reference/the-attack.md— call chain from representation request through libmatio HDF5 external reads. - Rails security forum: attack details and forensics tooling (July 31, 2026)
- Ethiack: KindaRails2Shell overview — naming, affected matrix, remediation summary.
- RubySec advisory CVE-2026-66066