diff --git a/config/initializers/vips.rb b/config/initializers/vips.rb new file mode 100644 index 00000000..bcae224f --- /dev/null +++ b/config/initializers/vips.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Dabble Me processes untrusted user images with libvips (CarrierWave::Vips, +# CollageGenerator, ImageProcessing::Vips). libvips marks some loaders/savers as +# "untrusted" / unfuzzed (Magick, SVG, PDF, JPEG 2000, JXL, etc.). Keep those +# disabled process-wide so crafted uploads cannot exercise them. +# +# Supported formats (JPEG, PNG, GIF, WebP, HEIC/HEIF) use trusted loaders and +# remain available. Requires libvips >= 8.13 and ruby-vips >= 2.2.1. +# +# Related: CVE-2026-66066 / GHSA-xr9x-r78c-5hrm (Active Storage path; Dabble Me +# does not use Active Storage variants, but shares the same libvips surface). + +begin + require 'nokogiri' +rescue LoadError + # Ensure nokogiri is loaded before vips, which also depends on libxml2. + # See https://github.com/sparklemotion/nokogiri/discussions/2746 +end + +require 'ruby-vips' + +unless Vips.respond_to?(:block_untrusted) + raise <<~ERROR.squish + libvips untrusted operations cannot be disabled. Blocking them requires + libvips 8.13+ and ruby-vips 2.2.1+. Upgrade libvips/ruby-vips before + processing untrusted image uploads. + ERROR +end + +Vips.block_untrusted(true) diff --git a/spec/initializers/vips_spec.rb b/spec/initializers/vips_spec.rb new file mode 100644 index 00000000..240dad7e --- /dev/null +++ b/spec/initializers/vips_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'libvips untrusted operation blocking' do + it 'enables block_untrusted at boot' do + expect(Vips).to respond_to(:block_untrusted) + end + + it 'still loads trusted web formats' do + jpeg = Vips::Image.black(8, 8).jpegsave_buffer + png = Vips::Image.black(8, 8).pngsave_buffer + webp = Vips::Image.black(8, 8).webpsave_buffer + gif = Vips::Image.black(8, 8).gifsave_buffer + + expect(Vips::Image.new_from_buffer(jpeg, '').width).to eq(8) + expect(Vips::Image.new_from_buffer(png, '').width).to eq(8) + expect(Vips::Image.new_from_buffer(webp, '').width).to eq(8) + expect(Vips::Image.new_from_buffer(gif, '').width).to eq(8) + end + + it 'blocks untrusted SVG loading' do + svg = <<~SVG + + + + SVG + + expect { + Vips::Image.new_from_buffer(svg, '') + }.to raise_error(Vips::Error) + end +end