diff --git a/config/feature_flags/wip/duo_secret_detection_false_positive.yml b/config/feature_flags/wip/duo_secret_detection_false_positive.yml new file mode 100644 index 0000000000000000000000000000000000000000..1ccdcdc857f351be26bd119a530e81b66cac0d13 --- /dev/null +++ b/config/feature_flags/wip/duo_secret_detection_false_positive.yml @@ -0,0 +1,8 @@ +--- +name: duo_secret_detection_false_positive +description: Enable Duo false positive detection for secret detection vulnerabilities +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/216844 +milestone: '18.8' +group: group::threat insights +type: wip +default_enabled: false diff --git a/ee/app/models/ee/vulnerability.rb b/ee/app/models/ee/vulnerability.rb index fbdb6fdb5589ce166856afd70fcc489b57df1cca..00885608aa538fc3480b964d5d011416e5ed62e5 100644 --- a/ee/app/models/ee/vulnerability.rb +++ b/ee/app/models/ee/vulnerability.rb @@ -387,8 +387,19 @@ def self.es_type def trigger_false_positive_detection return unless ::Feature.enabled?(:enable_vulnerability_fp_detection, group) - return unless sast? return unless high_or_critical_severity? + return if resolved? || dismissed? + + if sast? + trigger_sast_false_positive_detection + elsif secret_detection? + trigger_secret_detection_false_positive_detection + end + end + + private + + def trigger_sast_false_positive_detection return unless project.duo_sast_fp_detection_enabled user = project.first_owner || author @@ -399,7 +410,16 @@ def trigger_false_positive_detection end end - private + def trigger_secret_detection_false_positive_detection + return unless ::Feature.enabled?(:duo_secret_detection_false_positive, project) + + user = project.first_owner || author + return unless Ability.allowed?(user, :duo_workflow, project) + + run_after_commit_or_now do + ::Vulnerabilities::TriggerSecretDetectionFalsePositiveDetectionWorkflowWorker.perform_async(id) + end + end def high_or_critical_severity? severity.in?(%w[critical high]) diff --git a/ee/app/workers/all_queues.yml b/ee/app/workers/all_queues.yml index 73f03bdec9f9987974697cae0754bf1f7694eab5..8897657bb5dfb030d1f0eb2dec0557968c30493d 100644 --- a/ee/app/workers/all_queues.yml +++ b/ee/app/workers/all_queues.yml @@ -4800,6 +4800,9 @@ - :name: vulnerabilities_trigger_false_positive_detection_workflow :worker_name: Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker :feature_category: :vulnerability_management +- :name: vulnerabilities_trigger_secret_detection_false_positive_detection_workflow + :worker_name: Vulnerabilities::TriggerSecretDetectionFalsePositiveDetectionWorkflowWorker + :feature_category: :vulnerability_management :has_external_dependencies: false :urgency: :throttled :resource_boundary: :unknown diff --git a/ee/app/workers/vulnerabilities/trigger_secret_detection_false_positive_detection_workflow_worker.rb b/ee/app/workers/vulnerabilities/trigger_secret_detection_false_positive_detection_workflow_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..fd8d39cba7da5ca9ec074e2042a1cccdf4dc77e8 --- /dev/null +++ b/ee/app/workers/vulnerabilities/trigger_secret_detection_false_positive_detection_workflow_worker.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Vulnerabilities + class TriggerSecretDetectionFalsePositiveDetectionWorkflowWorker + include ApplicationWorker + + feature_category :vulnerability_management + urgency :low + + def perform(vulnerability_id) + # no-op implementation + # This worker will be implemented as part of the Secret Scanning FP detection workflow + end + end +end diff --git a/ee/spec/models/ee/vulnerability_spec.rb b/ee/spec/models/ee/vulnerability_spec.rb index a709fc30364ca8f37392984343a515527a313a28..31e1b6c36ae84df5dabcd34c70fb97b8dcf94f7e 100644 --- a/ee/spec/models/ee/vulnerability_spec.rb +++ b/ee/spec/models/ee/vulnerability_spec.rb @@ -1625,6 +1625,76 @@ end end end + + context 'when secret detection false positive detection is enabled' do + using RSpec::Parameterized::TableSyntax + + where(:report_type, :severity, :duo_secret_detection_fp_detection_enabled, :state, :should_trigger) do + :secret_detection | :critical | true | :detected | true + :secret_detection | :high | true | :detected | true + :secret_detection | :medium | true | :detected | false + :secret_detection | :low | true | :detected | false + :secret_detection | :info | true | :detected | false + :secret_detection | :unknown | true | :detected | false + :secret_detection | :high | false | :detected | false + :secret_detection | :high | true | :dismissed | false + :secret_detection | :high | true | :resolved | false + :dast | :high | true | :detected | false + end + + with_them do + before do + project.update!(duo_secret_detection_fp_detection_enabled: duo_secret_detection_fp_detection_enabled) + allow(Ability).to receive(:allowed?).with(project.first_owner, :duo_workflow, project).and_return(true) + end + + it 'triggers or does not trigger secret detection false positive detection workflow based on conditions' do + if should_trigger + expect_next_instance_of(::Vulnerability) do |instance| + expect(instance).to receive(:run_after_commit_or_now).and_yield + end + expect(::Vulnerabilities::TriggerSecretDetectionFalsePositiveDetectionWorkflowWorker) + .to receive(:perform_async).with(anything) + else + expect(::Vulnerabilities::TriggerSecretDetectionFalsePositiveDetectionWorkflowWorker) + .not_to receive(:perform_async) + end + + create(:vulnerability, report_type, severity, state, author: user, project: project) + end + end + + context 'when project first_owner does not have duo_workflow permission' do + before do + project.update!(duo_secret_detection_fp_detection_enabled: true) + allow(Ability).to receive(:allowed?).with(project.first_owner, :duo_workflow, project).and_return(false) + end + + it 'does not trigger secret detection false positive detection workflow' do + expect(::Vulnerabilities::TriggerSecretDetectionFalsePositiveDetectionWorkflowWorker) + .not_to receive(:perform_async) + + create(:vulnerability, :secret_detection, :critical, author: user, project: project) + end + end + + context 'when project first_owner has duo_workflow permission' do + before do + project.update!(duo_secret_detection_fp_detection_enabled: true) + allow(Ability).to receive(:allowed?).with(project.first_owner, :duo_workflow, project).and_return(true) + end + + it 'triggers secret detection false positive detection workflow for high severity secret detection vulnerability' do + expect_next_instance_of(::Vulnerability) do |instance| + expect(instance).to receive(:run_after_commit_or_now).and_yield + end + expect(::Vulnerabilities::TriggerSecretDetectionFalsePositiveDetectionWorkflowWorker) + .to receive(:perform_async).with(anything) + + create(:vulnerability, :secret_detection, :high, author: user, project: project) + end + end + end end context 'when updating an existing vulnerability' do