Active Storage CVE-2026-66066: are you affected, and what to run
CVE-2026-66066 carries a CVSS 4.0 base score of 9.5, needs no login, and the configuration it
exposes is the one load_defaults 7.0 hands you. An application running activestorage below
7.2.3.2, 8.0.5.1 or 8.1.3.1 that accepts image uploads and processes them with libvips can have
arbitrary files read off the server by an uploaded file, including the process environment holding
secret_key_base.
The upgrade is a version bump. Deciding whether you needed it, and what you owe afterwards, is the part that takes an afternoon. This page is the defensive half only.
Decide in one minute whether you are affected
Four conditions have to hold together, and the third one is where most people guess wrong.
bundle list | grep -E "activestorage|ruby-vips|image_processing"
bin/rails runner 'puts ActiveStorage.variant_processor'
vips --version
The first command gives you your activestorage version and tells you whether ruby-vips is in the
bundle at all. The second prints vips or mini_magick: ActiveStorage.variant_processor is set
in the engine's after_initialize from config.active_storage.variant_processor, so this is the
value your booted application actually uses, not the one you remember writing. The third matters for
the workaround, not for the exposure.
You are in scope when all four are true: activestorage is below the patched version for your line,
ruby-vips is installed, the variant processor is :vips, and untrusted users can get a file into an
Active Storage attachment. On the fourth, "untrusted" means anyone who is not you. A logged-in
customer uploading an avatar is untrusted for this purpose, because the advisory's impact does not
depend on who the attacker is, only on a file reaching libvips.
What is not a check: whether the attachment is displayed anywhere, whether the upload form has a
accept="image/*" attribute, and whether your validations reject non-images by content type.
CVE-2026-33173 below exists precisely because content type is a claim, not a fact.
The three patched releases
Rails shipped 7.2.3.2, 8.0.5.1 and 8.1.3.1 on 29 July 2026, and the advisory lists the affected
ranges as activestorage < 7.2.3.2, >= 8.0.0.beta1, < 8.0.5.1, and >= 8.1.0.beta1, < 8.1.3.1.
Bumping the rails gem is enough, since activestorage ships in lockstep with it.
Nothing shipped on 7.1, 7.0 or 6.x. The affected range for those lines reads < 7.2.3.2, which is
not a typo: the release announcement says "Older versions of Rails are unsupported, and users are
recommended to upgrade to at least the 7.2 series." State that cost honestly to whoever is asking
you for an ETA. Going from 7.1 to 7.2.3.2 is a framework upgrade with a deprecation pass in it, not
a patch bump you merge on a Friday, and the interim between now and that merge is the window the
next section is about.
When you cannot upgrade today
Three mitigations reach the same end state as the patch, and all three need libvips 8.13 or later.
- Set the
VIPS_BLOCK_UNTRUSTEDenvironment variable in the process running your app and jobs. - Call
Vips.block_untrusted(true)from an initializer, which needs ruby-vips 2.2.1 or later. - Remove ruby-vips from the Gemfile entirely. The advisory calls this out for applications that declared it only for image analysis and never generate variants.
On libvips older than 8.13 the advisory is blunt that there is no workaround other than removing the
dependency. That version floor is not arbitrary: Vips.block_untrusted is the switch, and 8.13 is
the first release that has it.
Then rotate. The advisory's remediation covers secret_key_base, the master key, storage service
credentials, database credentials and third-party tokens, because the stated impact is a read of the
process environment and anything the process could open. An unexploited vulnerability does not need
a rotation; one you cannot prove was unexploited does, and on a public upload endpoint you usually
cannot prove it from access logs alone.
A default assigned in the 7.0 branch and never revisited
config.active_storage.variant_processor defaults to :mini_magick in the engine, and then
railties overwrites it. In railties 8.1.3.1, lib/rails/application/configuration.rb assigns
active_storage.variant_processor = :vips at line 255, inside the when "7.0" branch that starts
at line 219 and ends when when "7.1" starts at line 269. No later branch reassigns it. Any
application on load_defaults 7.0 or higher processes variants with libvips whether or not anyone
on the team ever typed the word vips.
That is why the CWE on this advisory is CWE-1188, "Initialization of a Resource with an Insecure Default", rather than a memory-safety class. Nothing here is a bug in code somebody wrote. It is two correct-looking decisions composing into an exposure: Rails picked a faster image processor as a framework default, and never told that processor to refuse the formats its own maintainers flag as unsafe for untrusted input.
Running Rails 8 without Redis is the same shape from the correctness
side rather than the security side. A rate limit counter ends up in PostgreSQL because
rate_limit's store: default and config.cache_store agree with each other, and nobody decided
that either. Defaults that compose are the ones worth auditing, because no line in your repository
records the decision.
What libvips means by unfuzzed
libvips reads a long list of image formats, and delegates some of them to other libraries. The Active Storage CHANGELOG entry for 8.1.3.1 states the distinction directly: libvips "flags some of its loaders and savers as 'unfuzzed' or 'untrusted', meaning they are only safe for trusted content."
The class of bug is worth understanding even if you patch and move on. A file arriving at a format dispatcher decides which parser runs, because that is what format dispatch is for. The set of parsers reachable that way was larger than the set anybody had hardened, and the layer above it, Active Storage, never narrowed the set. Your own validations sat above that, and they were checking a content type the uploader supplied. At no point did a component hold an allowlist of what it was willing to parse, and each layer was reasonable in isolation.
The fix is exactly the narrowing that was missing, and it is one statement.
lib/active_storage/vips.rb in the patched gem calls Vips.block_untrusted(true) at load time.
The ordering in that file is the interesting part: it requires image_processing/vips first, on
purpose, with a comment explaining that letting image_processing load later would disable the
loaders again after an application's initializers had deliberately re-enabled one.
Variants are not the only way a file reaches libvips
Grepping for .variant( is the check almost everyone runs first, and it is the wrong turn on this
advisory. Analysis reaches libvips on its own.
# activestorage-8.1.3.1/lib/active_storage/analyzer/image_analyzer/vips.rb
def self.accept?(blob)
super && ActiveStorage.variant_processor == :vips
end
def read_image
download_blob_to_tempfile do |file|
image = instrument("vips") do
::Vips::Image.new_from_file(file.path, access: :sequential)
ActiveStorage::Attachment calls blob.analyze_later unless blob.analyzed? when a file is attached,
and ActiveStorage::Analyzer::ImageAnalyzer::Vips is first in the default config.active_storage.analyzers
array. So attaching an image is enough to open it with a libvips loader, in a background job, with
no variant requested anywhere in your codebase. The CHANGELOG confirms the analyzer was running
those loaders, because it lists what stops working after the patch: "analysis of these and other
types such as SVG, JPEG XL, JPEG 2000, and Netpbm will no longer record width and height."
Your test suite will not catch this either. A request spec that attaches a fixture and asserts a 302 stays green whether the analyze job ran, raised, or was never enqueued, and in most test setups the job never runs at all.
The Rails boilerplate this site sells is the case that looks safe and
still has to be checked twice: its Gemfile.lock pins rails (8.1.3.1) on the patched line, and its
only Active Storage declaration is has_many_attached :attachments on Message, with no .variant
call anywhere in the tree. The version pin is what mattered, since the absence of .variant would
not have been sufficient on its own.
What the patch breaks
Taking this upgrade costs you image formats, and the advisory says so rather than hiding it. After
7.2.3.2, 8.0.5.1 or 8.1.3.1, variant transformation of BMP, ICO and PSD attachments raises
Vips::Error. Analysis of those, plus SVG, JPEG XL, JPEG 2000 and Netpbm, stops recording width
and height. Requesting an unfuzzed output format, typically FITS or JXL, also raises. Attaching,
storing and downloading are unchanged.
Where that failure surfaces depends on when you transform. An application generating variants in a background job gets failed jobs it can see in a dashboard. An application transforming during a request turns the same exception into a 500 for a user, which is the case Rails singles out and the one to check first. The documented escape is to stop treating those types as variable at all.
Rails.application.config.active_storage.variable_content_types -=
%w[ image/bmp image/vnd.microsoft.icon image/vnd.adobe.photoshop ]
There is a second breaking change that has nothing to do with formats. The patched gem raises at boot when it cannot disable the loaders, with a message worth recognising before you meet it at three in the morning: "libvips's unfuzzed operations are not safe to use with untrusted content, and Active Storage cannot disable them." The minimum is now libvips 8.13 and ruby-vips 2.2.1, and a container image pinned to an older base can absolutely trip this on deploy rather than in CI.
Applications on the :mini_magick processor are not off the hook for the boot check. Their
attachments process unchanged, but the CHANGELOG notes the loaders are disabled process-wide
whenever ruby-vips is installed, and the version minimums still apply. Removing ruby-vips from the
Gemfile avoids both.
Take the breakage anyway. Losing PSD thumbnails is a support ticket, and the alternative is an
unauthenticated read of secret_key_base.
The four March advisories
Active Storage had a worse year than usual. Rails shipped 7.2.3.1, 8.0.4.1 and 8.1.2.1 on 23 March 2026, and four of the CVEs in that batch were Active Storage. If you are upgrading for 66066 now, you clear all four on the way.
- CVE-2026-33202, glob injection in
DiskService.DiskService#delete_prefixedpassed blob keys straight toDir.globwithout escaping glob metacharacters, so a key carrying*,?,[or{}could match and delete files beyond its own prefix. CVSS 4.0 base 6.6, CWE-74. Reachable only if your blob keys carry attacker-controlled input or come from a custom key generator. - CVE-2026-33173, content type bypass in direct uploads.
DirectUploadsControlleraccepted arbitrary metadata from the client and persisted it on the blob, and the internalidentifiedandanalyzedflags live in that same hash. Setting them skips MIME detection, so a validation trusting Active Storage's own identification could be told what to believe. CVSS 4.0 base 5.3. - CVE-2026-33174, unbounded Range DoS in proxy mode.
Blobs::ProxyControllerloaded the whole requested byte range into memory before sending it, soRange: bytes=0-on a large blob allocated memory proportional to the file. - CVE-2026-33658, multi-range DoS in proxy mode. The same controller put no limit on the number of byte ranges in a Range header, and thousands of small ranges cost disproportionate CPU.
The two DoS entries only apply in proxy delivery mode. An application on redirect mode, which is the default, never runs that controller for blob bytes.
What is not on this page
No reproduction is here, and that is deliberate. There is no crafted file, no payload, no step-by-step chain and no link to a proof of concept, including ones that are easy to find. The class of bug is genuinely useful to understand and a working chain is not something this site hands out.
Also not covered: how to tell whether you were already hit, which is a forensics question that depends on your logs and your storage service; the ImageMagick side of variant processing, which has its own history and is not what this advisory is about; and non-image attachments, which the analyzer path does not touch. For the authoritative text, read the advisory on discuss.rubyonrails.org and GHSA-xr9x-r78c-5hrm rather than this page or any other summary.
Comments
No comments yet. Be the first.