diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index 653b0c535..1a03c0d57 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -45,10 +45,32 @@ module ActiveStorageProxyRangeLimit end end +# Block the default Rails direct-upload route. Dashboard and widget uploads both go +# through the scoped, authenticated /api/v1/... endpoints, so the bare route has no +# legitimate caller; leaving it open allows anonymous blob creation. Scoped subclasses +# call super and are exempt via the instance_of? check. +module ActiveStorageBareDirectUploadGuard + extend ActiveSupport::Concern + + included do + before_action :reject_bare_direct_upload + end + + private + + def reject_bare_direct_upload + head :forbidden if instance_of?(ActiveStorage::DirectUploadsController) + end +end + Rails.application.config.to_prepare do unless ActiveStorage::DirectUploadsController < ActiveStorageDirectUploadMetadataFilter ActiveStorage::DirectUploadsController.prepend(ActiveStorageDirectUploadMetadataFilter) end + unless ActiveStorage::DirectUploadsController.include?(ActiveStorageBareDirectUploadGuard) + ActiveStorage::DirectUploadsController.include(ActiveStorageBareDirectUploadGuard) + end + ActiveStorage::Streaming.prepend(ActiveStorageProxyRangeLimit) unless ActiveStorage::Streaming < ActiveStorageProxyRangeLimit end diff --git a/spec/requests/active_storage/direct_uploads_spec.rb b/spec/requests/active_storage/direct_uploads_spec.rb new file mode 100644 index 000000000..5aeb9b654 --- /dev/null +++ b/spec/requests/active_storage/direct_uploads_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +# The default Rails direct-upload route has no authenticated caller: the dashboard +# and widget both upload through the scoped /api/v1/... endpoints. It is blocked so +# it cannot be used to create blobs anonymously. +RSpec.describe '/rails/active_storage/direct_uploads', type: :request do + let(:params) do + { + blob: { + filename: 'avatar.png', + byte_size: '1234', + checksum: 'dsjbsdhbfif3874823mnsdbf', + content_type: 'image/png' + } + } + end + + describe 'POST /rails/active_storage/direct_uploads' do + it 'is blocked and creates no blob' do + expect do + post rails_direct_uploads_url, params: params + end.not_to change(ActiveStorage::Blob, :count) + + expect(response).to have_http_status(:forbidden) + end + end +end