RubySec

Providing security resources for the Ruby community

CVE-2026-44587 (carrierwave): CarrierWave has a denylisted_content_type bypass via Unescaped Regex Metacharacters

CarrierWave has a denylisted_content_type bypass via Unescaped Regex Metacharacters

Published: May 27, 2026

SECURITY IDENTIFIERS

GEM

carrierwave

SEVERITY

CVSS v3.x: 4.7 (Medium)

PATCHED VERSIONS

~> 2.2.7 >= 3.1.3

DESCRIPTION

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

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.

RELATED