diff --git a/Gemfile.lock b/Gemfile.lock index 6b12f2ead..cf3505a23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,29 +3,29 @@ GEM specs: action_text-trix (2.1.19) railties - actioncable (8.1.4) - actionpack (= 8.1.4) - activesupport (= 8.1.4) + actioncable (8.1.4.1) + actionpack (= 8.1.4.1) + activesupport (= 8.1.4.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.4) - actionpack (= 8.1.4) - activejob (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + actionmailbox (8.1.4.1) + actionpack (= 8.1.4.1) + activejob (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) mail (>= 2.8.0) - actionmailer (8.1.4) - actionpack (= 8.1.4) - actionview (= 8.1.4) - activejob (= 8.1.4) - activesupport (= 8.1.4) + actionmailer (8.1.4.1) + actionpack (= 8.1.4.1) + actionview (= 8.1.4.1) + activejob (= 8.1.4.1) + activesupport (= 8.1.4.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.4) - actionview (= 8.1.4) - activesupport (= 8.1.4) + actionpack (8.1.4.1) + actionview (= 8.1.4.1) + activesupport (= 8.1.4.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -33,36 +33,36 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.4) + actiontext (8.1.4.1) action_text-trix (~> 2.1.15) - actionpack (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + actionpack (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.4) - activesupport (= 8.1.4) + actionview (8.1.4.1) + activesupport (= 8.1.4.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (8.1.4) - activesupport (= 8.1.4) + rails-html-sanitizer (~> 1.6, >= 1.7.2) + activejob (8.1.4.1) + activesupport (= 8.1.4.1) globalid (>= 0.3.6) - activemodel (8.1.4) - activesupport (= 8.1.4) - activerecord (8.1.4) - activemodel (= 8.1.4) - activesupport (= 8.1.4) + activemodel (8.1.4.1) + activesupport (= 8.1.4.1) + activerecord (8.1.4.1) + activemodel (= 8.1.4.1) + activesupport (= 8.1.4.1) timeout (>= 0.4.0) - activestorage (8.1.4) - actionpack (= 8.1.4) - activejob (= 8.1.4) - activerecord (= 8.1.4) - activesupport (= 8.1.4) + activestorage (8.1.4.1) + actionpack (= 8.1.4.1) + activejob (= 8.1.4.1) + activerecord (= 8.1.4.1) + activesupport (= 8.1.4.1) marcel (~> 1.0) - activesupport (8.1.4) + activesupport (8.1.4.1) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -293,9 +293,9 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.25.2) + loofah (2.25.3) crass (~> 1.0.2) - nokogiri (>= 1.12.0) + nokogiri (>= 1.19.5) mail (2.9.1) logger mini_mime (>= 0.1.1) @@ -323,15 +323,15 @@ GEM net-smtp (0.5.2) net-protocol nio4r (2.7.5) - nokogiri (1.19.4-aarch64-linux-gnu) + nokogiri (1.19.7-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.4-aarch64-linux-musl) + nokogiri (1.19.7-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.19.4-arm64-darwin) + nokogiri (1.19.7-arm64-darwin) racc (~> 1.4) - nokogiri (1.19.4-x86_64-linux-gnu) + nokogiri (1.19.7-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.4-x86_64-linux-musl) + nokogiri (1.19.7-x86_64-linux-musl) racc (~> 1.4) numo-narray-alt (0.10.3) onnxruntime (0.10.1-aarch64-linux) @@ -373,7 +373,7 @@ GEM puma (8.0.2) nio4r (~> 2.0) racc (1.8.1) - rack (3.2.7) + rack (3.2.8) rack-proxy (0.7.7) rack rack-session (2.1.2) @@ -383,33 +383,33 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.1.4) - actioncable (= 8.1.4) - actionmailbox (= 8.1.4) - actionmailer (= 8.1.4) - actionpack (= 8.1.4) - actiontext (= 8.1.4) - actionview (= 8.1.4) - activejob (= 8.1.4) - activemodel (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + rails (8.1.4.1) + actioncable (= 8.1.4.1) + actionmailbox (= 8.1.4.1) + actionmailer (= 8.1.4.1) + actionpack (= 8.1.4.1) + actiontext (= 8.1.4.1) + actionview (= 8.1.4.1) + activejob (= 8.1.4.1) + activemodel (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) bundler (>= 1.15.0) - railties (= 8.1.4) + railties (= 8.1.4.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.7.1) - loofah (~> 2.25, >= 2.25.2) + rails-html-sanitizer (1.7.2) + loofah (~> 2.25, >= 2.25.3) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-i18n (8.1.0) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.1.4) - actionpack (= 8.1.4) - activesupport (= 8.1.4) + railties (8.1.4.1) + actionpack (= 8.1.4.1) + activesupport (= 8.1.4.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -535,7 +535,7 @@ GEM thor (1.5.0) timeout (0.6.1) trailblazer-option (0.1.2) - trilogy (2.12.2) + trilogy (2.13.3) bigdecimal tsort (0.2.0) turbo-rails (2.0.23) diff --git a/README.md b/README.md index e2f6e8b92..48f840582 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d ☁️ Try in Cloud -[](https://demo.docuseal.tech) +[](https://demo.docuseal.tech) ## Features - PDF form fields builder (WYSIWYG) @@ -41,10 +41,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d - Automatic PDF eSignature - PDF signature verification - User management -- Mobile-optimized +- Mobile-optimized signing - 7 UI languages with signing available in 14 languages - API and Webhooks for integrations - Easy to deploy in minutes +- Mobile apps for [iOS](https://apps.apple.com/us/app/docuseal-e-signature/id6823564236) and [Android](https://play.google.com/store/apps/details?id=com.docuseal.app) ## Pro Features - Company logo and white-label diff --git a/SECURITY.md b/SECURITY.md index c61204cc4..ba39e5911 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,6 +10,7 @@ Out of scope vulnerabilities: - DNSSEC, CAA, CSP headers - DNS or email security related - SSRF +- DoS - Rate Limiting Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program. diff --git a/app/controllers/api/active_storage_blobs_proxy_controller.rb b/app/controllers/api/active_storage_blobs_proxy_controller.rb index e55c1cb3d..5696830ec 100644 --- a/app/controllers/api/active_storage_blobs_proxy_controller.rb +++ b/app/controllers/api/active_storage_blobs_proxy_controller.rb @@ -34,6 +34,12 @@ module Api @record = attachment.record @record = @record.record if @record.is_a?(ActiveStorage::Attachment) + if DetectBrowserDevice.search_bot?(request.user_agent) + Rollbar.warning("Search bot: #{@record.class.name} #{@record.id}") if defined?(Rollbar) + + raise ActionController::RoutingError, 'Not Found' + end + authorization_check!(attachment, @record, exp) if request.headers['Range'].present? diff --git a/app/controllers/api/submission_documents_controller.rb b/app/controllers/api/submission_documents_controller.rb index a26c5d86e..01531b633 100644 --- a/app/controllers/api/submission_documents_controller.rb +++ b/app/controllers/api/submission_documents_controller.rb @@ -22,7 +22,7 @@ module Api render json: { id: @submission.id, documents: documents.map do |attachment| - { name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) } + { name: FilenameUtils.basename(attachment.blob[:filename]), url: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) } end } end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 33257be89..6dc2914f5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base :true_ability, :form_link_host, :svg_icon, + :browser_os, :turbo_native_ios?, :turbo_native_android? @@ -58,6 +59,10 @@ class ApplicationController < ActionController::Base request.session[:impersonated_user_id] = user.uuid end + def browser_os + @browser_os ||= DetectBrowserDevice.os(request.user_agent) + end + def pagy_auto(collection, **keyword_args) if current_ability.can?(:manage, :countless) pagy(:countless, collection, **keyword_args) diff --git a/app/controllers/mcp/create_template_controller.rb b/app/controllers/mcp/create_template_controller.rb index 3d91686d6..fb4e5a365 100644 --- a/app/controllers/mcp/create_template_controller.rb +++ b/app/controllers/mcp/create_template_controller.rb @@ -66,7 +66,9 @@ module Mcp @template.save! documents, = Templates::CreateAttachments.call(@template, { files: [file] }, extract_fields: true) - schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } } + schema = documents.map do |doc| + { attachment_uuid: doc.uuid, name: FilenameUtils.basename(doc.blob[:filename]) } + end if @template.fields.blank? @template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents) diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb index 530b3b7be..74daf0d4b 100644 --- a/app/controllers/start_form_controller.rb +++ b/app/controllers/start_form_controller.rb @@ -92,8 +92,11 @@ class StartFormController < ApplicationController blank_fields = required_fields.select { |key| submitter_params[key].blank? } + is_invalid_email = required_fields.include?('email') && submitter_params[:email].present? && + !submitter_params[:email].match?(User::VALIDATE_EMAIL_REGEXP) + submitter = - if blank_fields.present? + if blank_fields.present? || is_invalid_email Submitter.new(submitter_params) else Submitters::StartForm.find_or_initialize_submitter( @@ -104,6 +107,12 @@ class StartFormController < ApplicationController blank_fields.each { |key| submitter.errors.add(key.to_sym, :blank) } + if is_invalid_email + Rollbar.warning("Invalid start form email: #{submitter_params[:email]}") if defined?(Rollbar) + + submitter.errors.add(:email, :invalid) + end + submitter end diff --git a/app/controllers/submission_events_controller.rb b/app/controllers/submission_events_controller.rb index b3b892f11..9188bd01a 100644 --- a/app/controllers/submission_events_controller.rb +++ b/app/controllers/submission_events_controller.rb @@ -12,6 +12,7 @@ class SubmissionEventsController < ApplicationController 'send_2fa_sms' => '2fa', 'send_2fa_email' => '2fa', 'send_sms' => 'send', + 'fail_sms' => 'alert_circle', 'phone_verified' => 'phone_check', 'email_verified' => 'email_check', 'click_sms' => 'hand_click', diff --git a/app/controllers/submissions_export_controller.rb b/app/controllers/submissions_export_controller.rb index 7a42d6a5c..1c83545a7 100644 --- a/app/controllers/submissions_export_controller.rb +++ b/app/controllers/submissions_export_controller.rb @@ -26,6 +26,6 @@ class SubmissionsExportController < ApplicationController end def new - render layout: turbo_native_android? ? 'plain' : true + render layout: 'plain' end end diff --git a/app/controllers/submissions_filters_controller.rb b/app/controllers/submissions_filters_controller.rb index ecebfefca..37e8ed914 100644 --- a/app/controllers/submissions_filters_controller.rb +++ b/app/controllers/submissions_filters_controller.rb @@ -14,6 +14,6 @@ class SubmissionsFiltersController < ApplicationController def show return head :not_found unless ALLOWED_NAMES.include?(params[:name]) - render params[:name], layout: turbo_native_android? ? 'plain' : true + render params[:name], layout: 'plain' end end diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb index f2d95e713..662ec8dee 100644 --- a/app/controllers/submit_form_controller.rb +++ b/app/controllers/submit_form_controller.rb @@ -8,6 +8,7 @@ class SubmitFormController < ApplicationController skip_authorization_check before_action :load_submitter, only: %i[show update completed] + before_action :maybe_render_not_found_for_search_bot, only: %i[show completed] before_action :maybe_redirect_delegated, only: %i[show completed] before_action :maybe_render_locked_page, only: :show before_action :maybe_require_link_2fa, only: %i[show] @@ -111,6 +112,14 @@ class SubmitFormController < ApplicationController private + def maybe_render_not_found_for_search_bot + return unless DetectBrowserDevice.search_bot?(request.user_agent) + + Rollbar.warning("Search bot: submitter #{@submitter&.id}") if defined?(Rollbar) + + raise ActionController::RoutingError, I18n.t('not_found') + end + def maybe_require_link_2fa return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request) diff --git a/app/controllers/template_documents_controller.rb b/app/controllers/template_documents_controller.rb index 6aa95da02..f8056c8fc 100644 --- a/app/controllers/template_documents_controller.rb +++ b/app/controllers/template_documents_controller.rb @@ -31,7 +31,7 @@ class TemplateDocumentsController < ApplicationController documents, = Templates::CreateAttachments.call(@template, params, extract_fields: true) schema = documents.map do |doc| - { attachment_uuid: doc.uuid, name: doc.filename.base } + { attachment_uuid: doc.uuid, name: FilenameUtils.basename(doc.blob[:filename]) } end render json: { diff --git a/app/controllers/template_folders_controller.rb b/app/controllers/template_folders_controller.rb index 8b24d2020..2ef30c202 100644 --- a/app/controllers/template_folders_controller.rb +++ b/app/controllers/template_folders_controller.rb @@ -42,7 +42,7 @@ class TemplateFoldersController < ApplicationController end def edit - render layout: turbo_native_android? ? 'plain' : true + render layout: 'plain' end def update diff --git a/app/controllers/templates_clone_controller.rb b/app/controllers/templates_clone_controller.rb index 8d5f62546..d524a886a 100644 --- a/app/controllers/templates_clone_controller.rb +++ b/app/controllers/templates_clone_controller.rb @@ -8,7 +8,7 @@ class TemplatesCloneController < ApplicationController @template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})") - render layout: turbo_native_android? ? 'plain' : true + render layout: 'plain' end def create diff --git a/app/controllers/templates_debug_controller.rb b/app/controllers/templates_debug_controller.rb index 551d167f5..e05d2591c 100644 --- a/app/controllers/templates_debug_controller.rb +++ b/app/controllers/templates_debug_controller.rb @@ -67,7 +67,7 @@ class TemplatesDebugController < ApplicationController schema = documents.map do |doc| { attachment_uuid: doc.uuid, - name: doc.filename.base, + name: FilenameUtils.basename(doc.blob[:filename]), dynamic: dynamic_documents.find { |e| e.uuid == doc.uuid }.present? } end diff --git a/app/controllers/templates_folders_controller.rb b/app/controllers/templates_folders_controller.rb index 6f22669b0..b62a9d406 100644 --- a/app/controllers/templates_folders_controller.rb +++ b/app/controllers/templates_folders_controller.rb @@ -4,7 +4,7 @@ class TemplatesFoldersController < ApplicationController load_and_authorize_resource :template def edit - render layout: turbo_native_android? ? 'plain' : true + render layout: 'plain' end def update diff --git a/app/controllers/templates_uploads_controller.rb b/app/controllers/templates_uploads_controller.rb index 8ba069805..b4ad28bf3 100644 --- a/app/controllers/templates_uploads_controller.rb +++ b/app/controllers/templates_uploads_controller.rb @@ -17,7 +17,7 @@ class TemplatesUploadsController < ApplicationController save_template!(@template, url_params) documents, = Templates::CreateAttachments.call(@template, url_params || params, extract_fields: true) - schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } } + schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: FilenameUtils.basename(doc.blob[:filename]) } } if @template.fields.blank? @template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents) diff --git a/app/controllers/user_initials_controller.rb b/app/controllers/user_initials_controller.rb index 1306444cc..1f78092c7 100644 --- a/app/controllers/user_initials_controller.rb +++ b/app/controllers/user_initials_controller.rb @@ -5,7 +5,7 @@ class UserInitialsController < ApplicationController authorize_resource :user_config def edit - render layout: turbo_native_android? ? 'plain' : true + render layout: 'plain' end def update diff --git a/app/controllers/user_signatures_controller.rb b/app/controllers/user_signatures_controller.rb index 353e26092..aa6833b03 100644 --- a/app/controllers/user_signatures_controller.rb +++ b/app/controllers/user_signatures_controller.rb @@ -5,7 +5,7 @@ class UserSignaturesController < ApplicationController authorize_resource :user_config def edit - render layout: turbo_native_android? ? 'plain' : true + render layout: 'plain' end def update diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6298c0815..2e52b103c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -32,11 +32,11 @@ class UsersController < ApplicationController end def new - render layout: turbo_native_android? ? 'plain' : true + render layout: turbo_native_app? ? 'plain' : true end def edit - render layout: turbo_native_android? ? 'plain' : true + render layout: turbo_native_app? ? 'plain' : true end def create diff --git a/app/controllers/webhook_settings_controller.rb b/app/controllers/webhook_settings_controller.rb index 6b6cf9140..125392653 100644 --- a/app/controllers/webhook_settings_controller.rb +++ b/app/controllers/webhook_settings_controller.rb @@ -13,7 +13,9 @@ class WebhookSettingsController < ApplicationController else @webhook_events = @webhook_url.webhook_events - @webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status]) + if %w[success error].include?(params[:status]) + @webhook_events = @webhook_events.where(WebhookEvent.arel_table[:status].eq(params[:status])) + end @pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc)) @@ -24,7 +26,9 @@ class WebhookSettingsController < ApplicationController def show @webhook_events = @webhook_url.webhook_events - @webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status]) + if %w[success error].include?(params[:status]) + @webhook_events = @webhook_events.where(WebhookEvent.arel_table[:status].eq(params[:status])) + end @pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc)) end diff --git a/app/javascript/template_builder/conditions_modal.vue b/app/javascript/template_builder/conditions_modal.vue index c625f43e5..4e29c8de8 100644 --- a/app/javascript/template_builder/conditions_modal.vue +++ b/app/javascript/template_builder/conditions_modal.vue @@ -7,7 +7,7 @@ class="absolute top-0 bottom-0 right-0 left-0" @click.prevent="$emit('close')" /> -