Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions gems/carrierwave/CVE-2026-44587.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
gem: carrierwave
cve: 2026-44587
ghsa: 7g26-2qgj-chfg
url: https://www.cve.org/CVERecord?id=CVE-2026-44587
title: CarrierWave has a denylisted_content_type bypass via
Unescaped Regex Metacharacters
date: 2026-05-27
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:

```ruby
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. An attacker can upload an SVG file containing arbitrary
cvss_v3: 4.7
patched_versions:
- "~> 2.2.7"
- ">= 3.1.3"
related:
url:
- https://www.cve.org/CVERecord?id=CVE-2026-44587
- https://github.com/carrierwaveuploader/carrierwave/releases
- https://github.com/carrierwaveuploader/carrierwave/security/advisories/GHSA-7g26-2qgj-chfg
- https://github.com/advisories/GHSA-7g26-2qgj-chfg