VYPR
' > xss.svg\n\ncurl -X POST http://localhost:4567/upload \\\n -F \"file=@xss.svg;type=image/svg+xml\"\n```\n\nExpected response (denylist working):\n```\njson{ \"result\": \"blocked\", \"message\": \"...\" }\n```\n\n\nActual response:\n```\njson{ \"result\": \"VULNERABLE\", \"message\": \"SVG bypassed denylist\", \"path\": \"...\" }\n```\n### Impact\nAny 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","additionalType":"https://schema.org/SoftwareApplication","sameAs":["https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-44587"]},"keywords":"CVE-2026-44587, moderate, CWE-116, Carrierwaveuploader Carrierwave, Carrierwaveuploader Carrierwave","mentions":[{"@type":"SoftwareApplication","name":"Carrierwave","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Carrierwaveuploader"}},{"@type":"SoftwareApplication","name":"Carrierwave","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Carrierwaveuploader"}}],"isAccessibleForFree":true},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://portal.vyprsec.ai/"},{"@type":"ListItem","position":2,"name":"CVEs","item":"https://portal.vyprsec.ai/cves"},{"@type":"ListItem","position":3,"name":"CVE-2026-44587","item":"https://portal.vyprsec.ai/cves/CVE-2026-44587"}]}]}
Moderate severityGHSA Advisory· Published May 27, 2026· Updated May 27, 2026

CarrierWave has a denylisted_content_type bypass via Unescaped Regex Metacharacters

CVE-2026-44587

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 ``

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 '' > 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

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CarrierWave's `content_type_denylist` silently bypasses intended MIME-type blocks because string entries are not regex-escaped.

Vulnerability

In CarrierWave, the #content_type_denylist method in lib/carrierwave/uploader/content_type_denylist.rb:57 interpolates string denylist entries directly into a regex without Regexp.quote or anchoring. For example, the entry "image/svg+xml" becomes the regex /image\/svg+xml/, where the + is interpreted as a quantifier (one or more g) rather than a literal plus sign, causing the denylist to never match the intended MIME type. This affects all listed content types containing regex metacharacters, such as application/xhtml+xml. The issue is inconsistent with the allowlist implementation in content_type_allowlist.rb, which correctly uses Regexp.quote with a \A anchor [1][2].

Exploitation

An attacker can upload a file whose MIME type contains regex metacharacters (e.g., image/svg+xml, application/xhtml+xml) that the developer intended to block. No authentication or special network position is required if the upload endpoint is public. The attacker simply submits the file with the problematic content type; the denylist fails to detect it, and the file is accepted as allowed because the regex never matches [1][2].

Impact

The attacker bypasses the content-type denylist, potentially uploading file types the developer explicitly wanted to forbid. This can lead to stored file uploads of types that may pose risks, such as SVG files (which can contain scripts leading to XSS) or other MIME types with malicious content. The allowlist-based check is not affected; the bypass only occurs when the denylist is relied upon to block specific content types [1][2].

Mitigation

As of this advisory (May 2026), no fixed version of CarrierWave has been released. The advisory recommends developers manually apply the fix: use Regexp.quote for string entries and anchor with \A in custom code or monkey-patch the denylisted_content_type? method, mirroring the allowlist implementation. The #content_type_denylist feature is deprecated for security reasons; developers are advised to migrate to a deny-by-default approach using an allowlist instead [1][2].

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
carrierwaveRubyGems
>= 3.0.0.beta, < 3.1.33.1.3
carrierwaveRubyGems
< 2.2.72.2.7

Affected products

2

Patches

1
4c4a005775a4

Security: Fix bypass in content_type_blacklist via unescaped RegExp chars

https://github.com/carrierwaveuploader/carrierwaveMitsuhiro ShibuyaMay 23, 2026Fixed in 2.2.7via llm-release-walk
2 files changed · +26 1
  • lib/carrierwave/uploader/content_type_blacklist.rb+4 1 modified
    @@ -49,7 +49,10 @@ def check_content_type_blacklist!(new_file)
           end
     
           def blacklisted_content_type?(content_type)
    -        Array(content_type_denylist).any? { |item| content_type =~ /#{item}/ }
    +        Array(content_type_denylist).any? do |item|
    +          item = Regexp.quote(item) if item.class != Regexp
    +          content_type =~ /#{item}/
    +        end
           end
     
         end # ContentTypeBlacklist
    
  • spec/uploader/content_type_blacklist_spec.rb+22 0 modified
    @@ -36,6 +36,28 @@
               expect { uploader.cache!(ruby_file) }.to raise_error(CarrierWave::IntegrityError, 'You are not allowed to upload image/png files')
             end
     
    +        it "properly escapes metacharacters" do
    +          allow(uploader).to receive(:content_type_denylist).and_return(['image/svg+xml'])
    +
    +          sanitized_file = CarrierWave::SanitizedFile.new(ruby_file)
    +          allow(sanitized_file).to receive(:content_type).and_return('image/svg+xml')
    +
    +          expect { uploader.send(:check_content_type_blacklist!, sanitized_file) }.to raise_error(CarrierWave::IntegrityError)
    +        end
    +
    +        it "matches anywhere in the string to be more restrictive" do
    +          allow(uploader).to receive(:content_type_denylist).and_return(['html'])
    +
    +          sanitized_file = CarrierWave::SanitizedFile.new(ruby_file)
    +          allow(sanitized_file).to receive(:content_type).and_return('text/html')
    +
    +          expect { uploader.send(:check_content_type_blacklist!, sanitized_file) }.to raise_error(CarrierWave::IntegrityError)
    +
    +          allow(sanitized_file).to receive(:content_type).and_return('application/html')
    +
    +          expect { uploader.send(:check_content_type_blacklist!, sanitized_file) }.to raise_error(CarrierWave::IntegrityError)
    +        end
    +
             it "accepts content types as regular expressions" do
               allow(uploader).to receive(:content_type_denylist).and_return([/image\//])
     
    

Vulnerability mechanics

Root cause

"String denylist entries are interpolated directly into a regex without Regexp.quote, causing metacharacters like + to be interpreted as regex quantifiers rather than literal characters."

Attack vector

An attacker uploads a file whose MIME type contains regex metacharacters (e.g., `image/svg+xml` where `+` is a quantifier). The denylist entry `"image/svg+xml"` is interpolated as `/image\/svg+xml/`, which never matches the literal `+` in the real content type, so the denylist check silently passes [ref_id=1][ref_id=2]. The attacker can then upload an SVG containing arbitrary JavaScript, leading to stored XSS when the file is served to other users. No authentication or special network position is required beyond the ability to reach the upload endpoint.

Affected code

The vulnerable method is `blacklisted_content_type?` in `lib/carrierwave/uploader/content_type_blacklist.rb` (line 52 in the patched file). String denylist entries are interpolated directly into a regex without `Regexp.quote` or anchoring, unlike the allowlist implementation in `lib/carrierwave/uploader/content_type_allowlist.rb` which correctly escapes metacharacters [ref_id=1][ref_id=2].

What the fix does

The patch in `lib/carrierwave/uploader/content_type_blacklist.rb` adds `Regexp.quote(item)` for string entries before interpolation, matching the allowlist implementation [patch_id=2595564]. This escapes metacharacters like `+` so that `"image/svg+xml"` becomes the literal regex `/image\/svg\+xml/` which correctly matches the real MIME type. The patch also adds spec tests confirming that `image/svg+xml` is properly blocked and that partial substring matches (e.g., `html` matching `text/html`) still work as intended [patch_id=2595564].

Preconditions

  • configThe application uses CarrierWave's content_type_denylist (or the deprecated content_type_blacklist) with a string entry containing regex metacharacters (e.g., 'image/svg+xml').
  • networkThe attacker can reach the file upload endpoint and control the Content-Type header or file type metadata.
  • inputThe attacker provides a file whose MIME type matches the intended denylist entry (e.g., image/svg+xml).

Reproduction

1. Create a Sinatra app with a CarrierWave uploader that defines `content_type_denylist = %w[image/svg+xml]` (see the PoC `app.rb` in [ref_id=1]). 2. Run `bundle exec ruby app.rb &` to start the server. 3. Create an SVG file: `echo '

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.