From 24f195c7e5b4a1df8d2aedcfef7eb5703bc6ebdd Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 20 Sep 2026 17:16:45 +0300 Subject: [PATCH] normalize extension --- .../active_storage_blobs_proxy_controller.rb | 2 +- ...e_storage_blobs_proxy_legacy_controller.rb | 2 +- app/controllers/user_initials_controller.rb | 6 ++---- app/controllers/user_signatures_controller.rb | 6 ++---- lib/filename_utils.rb | 21 +++++++++++++++++++ lib/submitters.rb | 16 ++------------ lib/submitters/normalize_values.rb | 8 +++---- 7 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 lib/filename_utils.rb diff --git a/app/controllers/api/active_storage_blobs_proxy_controller.rb b/app/controllers/api/active_storage_blobs_proxy_controller.rb index 4198f380..0cddd994 100644 --- a/app/controllers/api/active_storage_blobs_proxy_controller.rb +++ b/app/controllers/api/active_storage_blobs_proxy_controller.rb @@ -23,7 +23,7 @@ module Api blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid) - if Submitters::DANGEROUS_EXTENSIONS.include?(blob.filename.extension.to_s.downcase) + if FilenameUtils.dangerous_extension(blob.filename) Rollbar.error('Dangerous extension') if defined?(Rollbar) return head :unprocessable_content diff --git a/app/controllers/api/active_storage_blobs_proxy_legacy_controller.rb b/app/controllers/api/active_storage_blobs_proxy_legacy_controller.rb index 8bac4ce9..3df2dae2 100644 --- a/app/controllers/api/active_storage_blobs_proxy_legacy_controller.rb +++ b/app/controllers/api/active_storage_blobs_proxy_legacy_controller.rb @@ -19,7 +19,7 @@ module Api return head :not_found unless blob - if Submitters::DANGEROUS_EXTENSIONS.include?(blob.filename.extension.to_s.downcase) + if FilenameUtils.dangerous_extension(blob.filename) Rollbar.error('Dangerous extension') if defined?(Rollbar) return head :unprocessable_content diff --git a/app/controllers/user_initials_controller.rb b/app/controllers/user_initials_controller.rb index f6b87daa..29562e64 100644 --- a/app/controllers/user_initials_controller.rb +++ b/app/controllers/user_initials_controller.rb @@ -11,11 +11,9 @@ class UserInitialsController < ApplicationController return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank? - extension = File.extname(file.original_filename).delete_prefix('.').downcase + extension = FilenameUtils.dangerous_extension(file.original_filename) - if Submitters::DANGEROUS_EXTENSIONS.include?(extension) - raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed." - end + raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed." if extension blob = ActiveStorage::Blob.create_and_upload!(io: file.open, filename: file.original_filename, diff --git a/app/controllers/user_signatures_controller.rb b/app/controllers/user_signatures_controller.rb index f6511d00..da2974ea 100644 --- a/app/controllers/user_signatures_controller.rb +++ b/app/controllers/user_signatures_controller.rb @@ -11,11 +11,9 @@ class UserSignaturesController < ApplicationController return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank? - extension = File.extname(file.original_filename).delete_prefix('.').downcase + extension = FilenameUtils.dangerous_extension(file.original_filename) - if Submitters::DANGEROUS_EXTENSIONS.include?(extension) - raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed." - end + raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed." if extension blob = ActiveStorage::Blob.create_and_upload!(io: file.open, filename: file.original_filename, diff --git a/lib/filename_utils.rb b/lib/filename_utils.rb new file mode 100644 index 00000000..7fceca4a --- /dev/null +++ b/lib/filename_utils.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module FilenameUtils + DANGEROUS_EXTENSIONS = %w[ + exe com bat cmd scr pif vbs vbe js jse wsf wsh msi msp + hta cpl jar app deb rpm dmg pkg mpkg dll so dylib sys + inf reg ps1 psm1 psd1 ps1xml psc1 pssc vb vba + sh bash zsh fish run out bin elf gadget workflow lnk scf + url desktop application action apk ipa xap appx + appxbundle msix msixbundle diagcab diagpkg msc ocx + drv ins isp mst paf prf shb shs slk ws wsc inf1 inf2 + ].freeze + + DANGEROUS_EXTENSIONS_REGEXP = /\.(#{Regexp.union(DANGEROUS_EXTENSIONS).source})\W*\z/i + + module_function + + def dangerous_extension(filename) + ActiveStorage::Filename.wrap(filename).sanitized[DANGEROUS_EXTENSIONS_REGEXP, 1]&.downcase + end +end diff --git a/lib/submitters.rb b/lib/submitters.rb index b8de2bde..55bb9ca4 100644 --- a/lib/submitters.rb +++ b/lib/submitters.rb @@ -16,16 +16,6 @@ module Submitters MaliciousFileExtension = Class.new(StandardError) ParamsError = Class.new(StandardError) - DANGEROUS_EXTENSIONS = Set.new(%w[ - exe com bat cmd scr pif vbs vbe js jse wsf wsh msi msp - hta cpl jar app deb rpm dmg pkg mpkg dll so dylib sys - inf reg ps1 psm1 psd1 ps1xml psc1 pssc bat cmd vb vba - sh bash zsh fish run out bin elf gadget workflow lnk scf - url desktop application action workflow apk ipa xap appx - appxbundle msix msixbundle diagcab diagpkg cpl msc ocx - drv scr ins isp mst paf prf shb shs slk ws wsc inf1 inf2 - ].freeze) - FILES_TTL = 5.minutes module_function @@ -127,11 +117,9 @@ module Submitters def create_attachment!(submitter, file, metadata: {}) raise ParamsError, 'file param is missing' if file.blank? - extension = File.extname(file.original_filename).delete_prefix('.').downcase + extension = FilenameUtils.dangerous_extension(file.original_filename) - if DANGEROUS_EXTENSIONS.include?(extension) - raise MaliciousFileExtension, "File type '.#{extension}' is not allowed." - end + raise MaliciousFileExtension, "File type '.#{extension}' is not allowed." if extension blob = ActiveStorage::Blob.create_and_upload!(io: file.tap(&:rewind).open, filename: file.original_filename, diff --git a/lib/submitters/normalize_values.rb b/lib/submitters/normalize_values.rb index 2162e970..f0512c43 100644 --- a/lib/submitters/normalize_values.rb +++ b/lib/submitters/normalize_values.rb @@ -247,7 +247,7 @@ module Submitters detected_extensions = Marcel::TYPE_EXTS[mime_type].to_a.map(&:downcase) - if detected_extensions.any? { |e| Submitters::DANGEROUS_EXTENSIONS.include?(e) } + if detected_extensions.any? { |e| FilenameUtils::DANGEROUS_EXTENSIONS.include?(e) } raise InvalidDefaultValue, "File type '.#{detected_extensions.first}' is not allowed." end @@ -276,11 +276,9 @@ module Submitters def find_or_create_blob_from_url(account, url) filename = Addressable::URI.parse(url).path.split('/').last.to_s - extension = File.extname(filename).delete_prefix('.').downcase + extension = FilenameUtils.dangerous_extension(filename) - if Submitters::DANGEROUS_EXTENSIONS.include?(extension) - raise InvalidDefaultValue, "File type '.#{extension}' is not allowed." - end + raise InvalidDefaultValue, "File type '.#{extension}' is not allowed." if extension cache_key = [account.id, url].join(':') checksum = CHECKSUM_CACHE_STORE.fetch(cache_key)