From e633987b8e45b0cd6cd8452b5aa0200e89147e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20C=CC=8Cavoj?= Date: Fri, 12 Dec 2025 10:27:07 +0100 Subject: [PATCH 1/3] Add auto dismissal checker for batch preloading of policy matches To display a badge for findings in the MR security widget that will be automatically dismissed by a auto-dismiss policy, we need to preload the information for security findings whether or not they match any auto-dismiss policies. --- .../policy_auto_dismissable.rb | 37 ++++ ee/app/models/security/finding.rb | 1 + ee/app/models/vulnerabilities/finding.rb | 1 + .../findings/policy_auto_dismissal_checker.rb | 46 +++++ ee/spec/models/security/finding_spec.rb | 21 ++ .../models/vulnerabilities/finding_spec.rb | 12 ++ .../policy_auto_dismissal_checker_spec.rb | 183 ++++++++++++++++++ ...policy_auto_dismissable_shared_examples.rb | 161 +++++++++++++++ 8 files changed, 462 insertions(+) create mode 100644 ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb create mode 100644 ee/app/services/security/findings/policy_auto_dismissal_checker.rb create mode 100644 ee/spec/services/security/findings/policy_auto_dismissal_checker_spec.rb create mode 100644 ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb diff --git a/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb b/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb new file mode 100644 index 00000000000000..87c3de0adb0629 --- /dev/null +++ b/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Vulnerabilities + module PolicyAutoDismissable + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + included do + attr_accessor :policy_dismissal_precomputed + end + + class_methods do + def preload_auto_dismissal_checks!(project, findings) + return findings if Feature.disabled?(:auto_dismiss_vulnerability_policies, project.group) + return findings if findings.empty? + + checker = Security::Findings::PolicyAutoDismissalChecker.new(project) + auto_dismissal_map = checker.check_batch(findings) + + findings.each do |finding| + finding.policy_dismissal_precomputed = auto_dismissal_map[finding.uuid] + end + + findings + end + end + + def matches_auto_dismiss_policy? + return policy_dismissal_precomputed unless policy_dismissal_precomputed.nil? + return false unless project + + checker = Security::Findings::PolicyAutoDismissalChecker.new(project) + checker.check(self) + end + strong_memoize_attr :matches_auto_dismiss_policy? + end +end diff --git a/ee/app/models/security/finding.rb b/ee/app/models/security/finding.rb index 83edb837ea8045..90c594f26f67fd 100644 --- a/ee/app/models/security/finding.rb +++ b/ee/app/models/security/finding.rb @@ -14,6 +14,7 @@ class Finding < ::SecApplicationRecord include EachBatch include Presentable include PartitionedTable + include ::Vulnerabilities::PolicyAutoDismissable MAX_PARTITION_SIZE = 100.gigabytes ATTRIBUTES_DELEGATED_TO_FINDING_DATA = %i[name description solution location identifiers links false_positive? diff --git a/ee/app/models/vulnerabilities/finding.rb b/ee/app/models/vulnerabilities/finding.rb index 20408cb6d7ad79..acffe75c45bea4 100644 --- a/ee/app/models/vulnerabilities/finding.rb +++ b/ee/app/models/vulnerabilities/finding.rb @@ -8,6 +8,7 @@ class Finding < ::SecApplicationRecord include ::VulnerabilityFindingHelpers include EachBatch include SafelyChangeColumnDefault + include PolicyAutoDismissable columns_changing_default :detected_at diff --git a/ee/app/services/security/findings/policy_auto_dismissal_checker.rb b/ee/app/services/security/findings/policy_auto_dismissal_checker.rb new file mode 100644 index 00000000000000..ae4e8dd7a626fa --- /dev/null +++ b/ee/app/services/security/findings/policy_auto_dismissal_checker.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Security + module Findings + class PolicyAutoDismissalChecker + include Gitlab::Utils::StrongMemoize + + def initialize(project) + @project = project + end + + def check(finding) + rules.any? { |rule| rule.match?(finding) } + end + + def check_batch(findings) + return {} if policies.empty? + + findings.each_with_object({}) do |finding, result| + result[finding.uuid] = check(finding) + end + end + + private + + attr_reader :project + + def policies + return [] if Feature.disabled?(:auto_dismiss_vulnerability_policies, project.group) + return [] unless project.licensed_feature_available?(:security_orchestration_policies) + + project + .vulnerability_management_policies + .auto_dismiss_policies.including_rules + end + strong_memoize_attr :policies + + def rules + policies + .flat_map(&:vulnerability_management_policy_rules) + .select(&:type_detected?) + end + strong_memoize_attr :rules + end + end +end diff --git a/ee/spec/models/security/finding_spec.rb b/ee/spec/models/security/finding_spec.rb index 59c6d30ee87ff8..0f56610db59eb7 100644 --- a/ee/spec/models/security/finding_spec.rb +++ b/ee/spec/models/security/finding_spec.rb @@ -871,4 +871,25 @@ it { is_expected.to be_nil } end end + + it_behaves_like 'policy auto-dismissable' do + let_it_be(:policy_rule_attributes) { { file_path: 'test/**/*' } } + let_it_be(:scan) { create(:security_scan, project: project) } + + let!(:matching_finding) do + create(:security_finding, :with_finding_data, scan: scan, location: { file: 'test/spec/example_spec.rb' }) + end + + let!(:non_matching_finding) do + create(:security_finding, :with_finding_data, scan: scan, location: { file: 'src/main.c' }) + end + + context 'when project is nil (delegated via scan)' do + let(:finding_without_project) { build(:security_finding, :with_finding_data, scan: nil) } + + it 'returns false' do + expect(finding_without_project.matches_auto_dismiss_policy?).to be false + end + end + end end diff --git a/ee/spec/models/vulnerabilities/finding_spec.rb b/ee/spec/models/vulnerabilities/finding_spec.rb index b51a18d4619327..5b198de4834938 100644 --- a/ee/spec/models/vulnerabilities/finding_spec.rb +++ b/ee/spec/models/vulnerabilities/finding_spec.rb @@ -2028,4 +2028,16 @@ def create_finding(state) end end end + + it_behaves_like 'policy auto-dismissable' do + let_it_be(:policy_rule_attributes) { { file_path: 'test/**/*' } } + + let!(:matching_finding) do + create(:vulnerabilities_finding, :with_dependency_scanning_metadata, project: project, file: 'test/spec/example_spec.rb') + end + + let!(:non_matching_finding) do + create(:vulnerabilities_finding, :with_dependency_scanning_metadata, project: project, file: 'src/main.c') + end + end end diff --git a/ee/spec/services/security/findings/policy_auto_dismissal_checker_spec.rb b/ee/spec/services/security/findings/policy_auto_dismissal_checker_spec.rb new file mode 100644 index 00000000000000..a0af971eb25133 --- /dev/null +++ b/ee/spec/services/security/findings/policy_auto_dismissal_checker_spec.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::Findings::PolicyAutoDismissalChecker, feature_category: :vulnerability_management do + let_it_be(:project) { create(:project) } + let_it_be(:scan) { create(:security_scan, project: project) } + + subject(:checker) { described_class.new(project) } + + before do + stub_licensed_features(security_orchestration_policies: true) + end + + describe '#check' do + context 'when there are no policies' do + it 'returns false' do + finding = create(:security_finding, :with_finding_data, scan: scan) + + result = checker.check(finding) + + expect(result).to be false + end + end + + context 'when there are policies but no rules' do + let_it_be(:policy) do + create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) + end + + it 'returns false' do + finding = create(:security_finding, :with_finding_data, scan: scan) + + result = checker.check(finding) + + expect(result).to be false + end + end + + context 'when there are policies with detected rules' do + let_it_be(:policy) do + create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) + end + + let_it_be(:rule) do + create(:vulnerability_management_policy_rule, :detected_file_path, + security_policy: policy, + file_path: 'test/**/*' + ) + end + + subject(:check) { checker.check(finding) } + + context 'when finding matches the rule' do + let(:finding) do + create(:security_finding, :with_finding_data, scan: scan, + location: { file: 'test/spec/example_spec.rb' }) + end + + it { is_expected.to be true } + + context 'when feature is not licensed' do + before do + stub_licensed_features(security_orchestration_policies: false) + end + + it { is_expected.to be false } + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(auto_dismiss_vulnerability_policies: false) + end + + it { is_expected.to be false } + end + end + + context 'when finding does not match the rule' do + let(:finding) do + create(:security_finding, :with_finding_data, scan: scan, location: { file: 'src/main.rb' }) + end + + it { is_expected.to be false } + end + + context 'with multiple rules' do + let_it_be(:rule2) do + create(:vulnerability_management_policy_rule, :detected_identifier, + security_policy: policy, identifier: 'CWE-99') + end + + context 'when any rule matches' do + let(:finding) do + create(:security_finding, :with_finding_data, scan: scan, + location: { file: 'test/spec/example_spec.rb' }, + identifiers: [create(:ci_reports_security_identifier, name: 'CWE-79').to_hash]) + end + + it { is_expected.to be true } + end + + context 'when no rules match' do + let(:finding) do + create(:security_finding, :with_finding_data, scan: scan, + location: { file: 'src/main.c' }, + identifiers: [create(:ci_reports_security_identifier, name: 'CWE-78').to_hash]) + end + + it { is_expected.to be false } + end + end + end + end + + describe '#check_batch' do + context 'when there are no policies' do + it 'returns an empty hash' do + findings = [create(:security_finding, :with_finding_data, scan: scan)] + + result = checker.check_batch(findings) + + expect(result).to eq({}) + end + end + + context 'when there are policies but no rules' do + let_it_be(:policy) do + create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) + end + + it 'returns a hash with all findings mapped to false' do + finding1 = create(:security_finding, :with_finding_data, scan: scan) + finding2 = create(:security_finding, :with_finding_data, scan: scan) + findings = [finding1, finding2] + + result = checker.check_batch(findings) + + expect(result).to eq({ + finding1.uuid => false, + finding2.uuid => false + }) + end + end + + context 'when there are policies with detected rules' do + let_it_be(:policy) do + create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) + end + + let_it_be(:rule) do + create(:vulnerability_management_policy_rule, :detected_file_path, + security_policy: policy, + file_path: 'test/**/*' + ) + end + + it 'returns a hash mapping finding UUIDs to match results' do + matching_finding = create(:security_finding, :with_finding_data, scan: scan, + location: { file: 'test/spec/example_spec.rb' }) + non_matching_finding = create(:security_finding, :with_finding_data, scan: scan, + location: { file: 'src/main.rb' }) + + findings = [matching_finding, non_matching_finding] + + result = checker.check_batch(findings) + + expect(result).to eq({ + matching_finding.uuid => true, + non_matching_finding.uuid => false + }) + end + end + + context 'with empty findings array' do + it 'returns an empty hash' do + result = checker.check_batch([]) + + expect(result).to eq({}) + end + end + end +end diff --git a/ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb b/ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb new file mode 100644 index 00000000000000..a96aa64c3f61a6 --- /dev/null +++ b/ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'policy auto-dismissable' do + let_it_be(:project) { create(:project) } + + before do + stub_licensed_features(security_orchestration_policies: true) + end + + describe '#matches_auto_dismiss_policy?' do + context 'when policy_dismissal_precomputed is set' do + it 'returns the precomputed value when true' do + finding = matching_finding.dup + finding.policy_dismissal_precomputed = true + expect(finding.matches_auto_dismiss_policy?).to be true + end + + it 'returns the precomputed value when false' do + finding = matching_finding.dup + finding.policy_dismissal_precomputed = false + expect(finding.matches_auto_dismiss_policy?).to be false + end + end + + context 'when there are no auto-dismiss policies' do + it 'returns false' do + expect(matching_finding.matches_auto_dismiss_policy?).to be false + end + end + + context 'when there are auto-dismiss policies' do + let_it_be(:policy) do + create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) + end + + let_it_be(:policy_rule_attributes) { {} } + let_it_be(:rule) do + create(:vulnerability_management_policy_rule, :detected_file_path, + security_policy: policy, + **policy_rule_attributes) + end + + context 'when finding matches policy criteria' do + it 'returns true' do + expect(matching_finding.matches_auto_dismiss_policy?).to be true + end + + describe 'memoization' do + it 'caches the result' do + expect_next_instance_of(Security::Findings::PolicyAutoDismissalChecker) do |checker| + expect(checker).to receive(:check).once.and_call_original + end + + matching_finding.matches_auto_dismiss_policy? + matching_finding.matches_auto_dismiss_policy? + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(auto_dismiss_vulnerability_policies: false) + end + + it 'returns false' do + expect(matching_finding.matches_auto_dismiss_policy?).to be false + end + end + + context 'when the feature is not licensed' do + before do + stub_licensed_features(security_orchestration_policies: false) + end + + it 'returns false' do + expect(matching_finding.matches_auto_dismiss_policy?).to be false + end + end + end + + context 'when finding does not match policy criteria' do + it 'returns false' do + expect(non_matching_finding.matches_auto_dismiss_policy?).to be false + end + end + end + end + + describe '.preload_auto_dismissal_checks!' do + context 'when feature flag is disabled' do + before do + stub_feature_flags(auto_dismiss_vulnerability_policies: false) + end + + it 'returns nil without processing' do + result = described_class.preload_auto_dismissal_checks!(project, [matching_finding, non_matching_finding]) + expect(result).to match_array([matching_finding, non_matching_finding]) + end + end + + context 'when findings list is empty' do + it 'returns the empty list' do + result = described_class.preload_auto_dismissal_checks!(project, []) + expect(result).to eq([]) + end + end + + context 'when there are no auto-dismiss policies' do + it 'sets policy_dismissal_precomputed to nil for all findings' do + findings = [matching_finding, non_matching_finding] + described_class.preload_auto_dismissal_checks!(project, findings) + + expect(matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be_nil + expect(non_matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be_nil + end + end + + context 'when there are auto-dismiss policies' do + let_it_be(:policy) do + create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) + end + + let_it_be(:rule) do + create(:vulnerability_management_policy_rule, :detected, + security_policy: policy, + content: { + 'criteria' => [ + { 'type' => 'file_path', 'value' => 'test/**/*' } + ] + }) + end + + context 'when finding matches policy criteria' do + it 'sets policy_dismissal_precomputed to true' do + findings = [matching_finding] + described_class.preload_auto_dismissal_checks!(project, findings) + + expect(matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be true + end + end + + context 'when finding does not match policy criteria' do + it 'sets policy_dismissal_precomputed to false' do + findings = [non_matching_finding] + described_class.preload_auto_dismissal_checks!(project, findings) + + expect(non_matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be false + end + end + + context 'with multiple findings with mixed matches' do + it 'correctly identifies which findings match' do + findings = [matching_finding, non_matching_finding] + described_class.preload_auto_dismissal_checks!(project, findings) + + expect(matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be true + expect(non_matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be false + end + end + end + end +end -- GitLab From e2c99cadd280a0c75cf13de98af38b20421b0734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20C=CC=8Cavoj?= Date: Tue, 16 Dec 2025 10:03:33 +0100 Subject: [PATCH 2/3] Force preloading, rename to matches_auto_dismiss_policy, simplify specs --- .../policy_auto_dismissable.rb | 18 +-- ee/spec/models/security/finding_spec.rb | 8 -- ...policy_auto_dismissable_shared_examples.rb | 135 +++++------------- 3 files changed, 43 insertions(+), 118 deletions(-) diff --git a/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb b/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb index 87c3de0adb0629..b658573a1a06f4 100644 --- a/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb +++ b/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb @@ -3,35 +3,29 @@ module Vulnerabilities module PolicyAutoDismissable extend ActiveSupport::Concern - include Gitlab::Utils::StrongMemoize included do - attr_accessor :policy_dismissal_precomputed + # Property is preloaded via `preload_auto_dismissal_checks` to indicate if it matches an auto-dismiss policy + attr_accessor :matches_auto_dismiss_policy + + alias_method :matches_auto_dismiss_policy?, :matches_auto_dismiss_policy end class_methods do def preload_auto_dismissal_checks!(project, findings) return findings if Feature.disabled?(:auto_dismiss_vulnerability_policies, project.group) + return findings unless project.licensed_feature_available?(:security_orchestration_policies) return findings if findings.empty? checker = Security::Findings::PolicyAutoDismissalChecker.new(project) auto_dismissal_map = checker.check_batch(findings) findings.each do |finding| - finding.policy_dismissal_precomputed = auto_dismissal_map[finding.uuid] + finding.matches_auto_dismiss_policy = auto_dismissal_map.fetch(finding.uuid, false) end findings end end - - def matches_auto_dismiss_policy? - return policy_dismissal_precomputed unless policy_dismissal_precomputed.nil? - return false unless project - - checker = Security::Findings::PolicyAutoDismissalChecker.new(project) - checker.check(self) - end - strong_memoize_attr :matches_auto_dismiss_policy? end end diff --git a/ee/spec/models/security/finding_spec.rb b/ee/spec/models/security/finding_spec.rb index 0f56610db59eb7..6aa76505aef6f5 100644 --- a/ee/spec/models/security/finding_spec.rb +++ b/ee/spec/models/security/finding_spec.rb @@ -883,13 +883,5 @@ let!(:non_matching_finding) do create(:security_finding, :with_finding_data, scan: scan, location: { file: 'src/main.c' }) end - - context 'when project is nil (delegated via scan)' do - let(:finding_without_project) { build(:security_finding, :with_finding_data, scan: nil) } - - it 'returns false' do - expect(finding_without_project.matches_auto_dismiss_policy?).to be false - end - end end end diff --git a/ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb b/ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb index a96aa64c3f61a6..92bea4d0ec9ad2 100644 --- a/ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb +++ b/ee/spec/support/shared_examples/models/concerns/vulnerabilities/policy_auto_dismissable_shared_examples.rb @@ -2,99 +2,60 @@ RSpec.shared_examples_for 'policy auto-dismissable' do let_it_be(:project) { create(:project) } + let(:feature_licensed) { true } before do - stub_licensed_features(security_orchestration_policies: true) + stub_licensed_features(security_orchestration_policies: feature_licensed) end describe '#matches_auto_dismiss_policy?' do - context 'when policy_dismissal_precomputed is set' do + context 'when matches_auto_dismiss_policy is set' do it 'returns the precomputed value when true' do finding = matching_finding.dup - finding.policy_dismissal_precomputed = true + finding.matches_auto_dismiss_policy = true expect(finding.matches_auto_dismiss_policy?).to be true end it 'returns the precomputed value when false' do finding = matching_finding.dup - finding.policy_dismissal_precomputed = false + finding.matches_auto_dismiss_policy = false expect(finding.matches_auto_dismiss_policy?).to be false end end - context 'when there are no auto-dismiss policies' do - it 'returns false' do - expect(matching_finding.matches_auto_dismiss_policy?).to be false + context 'when the property is not set via preloading' do + it 'returns nil' do + expect(matching_finding.matches_auto_dismiss_policy?).to be_nil + expect(non_matching_finding.matches_auto_dismiss_policy?).to be_nil end end + end - context 'when there are auto-dismiss policies' do - let_it_be(:policy) do - create(:security_policy, :vulnerability_management_policy, :auto_dismiss, linked_projects: [project]) - end - - let_it_be(:policy_rule_attributes) { {} } - let_it_be(:rule) do - create(:vulnerability_management_policy_rule, :detected_file_path, - security_policy: policy, - **policy_rule_attributes) - end + describe '.preload_auto_dismissal_checks!' do + shared_examples_for 'does not process auto-dismiss' do + it 'sets matches_auto_dismiss_policy to nil for all findings without processing', :aggregate_failures do + expect(Security::Findings::PolicyAutoDismissalChecker).not_to receive(:new) - context 'when finding matches policy criteria' do - it 'returns true' do - expect(matching_finding.matches_auto_dismiss_policy?).to be true - end - - describe 'memoization' do - it 'caches the result' do - expect_next_instance_of(Security::Findings::PolicyAutoDismissalChecker) do |checker| - expect(checker).to receive(:check).once.and_call_original - end - - matching_finding.matches_auto_dismiss_policy? - matching_finding.matches_auto_dismiss_policy? - end - end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(auto_dismiss_vulnerability_policies: false) - end - - it 'returns false' do - expect(matching_finding.matches_auto_dismiss_policy?).to be false - end - end - - context 'when the feature is not licensed' do - before do - stub_licensed_features(security_orchestration_policies: false) - end - - it 'returns false' do - expect(matching_finding.matches_auto_dismiss_policy?).to be false - end - end - end + result = described_class.preload_auto_dismissal_checks!(project, [matching_finding, non_matching_finding]) + expect(result).to match_array([matching_finding, non_matching_finding]) - context 'when finding does not match policy criteria' do - it 'returns false' do - expect(non_matching_finding.matches_auto_dismiss_policy?).to be false - end + expect(matching_finding.matches_auto_dismiss_policy).to be_nil + expect(non_matching_finding.matches_auto_dismiss_policy).to be_nil end end - end - describe '.preload_auto_dismissal_checks!' do context 'when feature flag is disabled' do before do stub_feature_flags(auto_dismiss_vulnerability_policies: false) end - it 'returns nil without processing' do - result = described_class.preload_auto_dismissal_checks!(project, [matching_finding, non_matching_finding]) - expect(result).to match_array([matching_finding, non_matching_finding]) - end + it_behaves_like 'does not process auto-dismiss' + end + + context 'when the feature is not licensed' do + let(:feature_licensed) { false } + + it_behaves_like 'does not process auto-dismiss' end context 'when findings list is empty' do @@ -105,12 +66,11 @@ end context 'when there are no auto-dismiss policies' do - it 'sets policy_dismissal_precomputed to nil for all findings' do - findings = [matching_finding, non_matching_finding] - described_class.preload_auto_dismissal_checks!(project, findings) + it 'sets matches_auto_dismiss_policy to false for all findings' do + described_class.preload_auto_dismissal_checks!(project, [matching_finding, non_matching_finding]) - expect(matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be_nil - expect(non_matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be_nil + expect(matching_finding.matches_auto_dismiss_policy).to be false + expect(non_matching_finding.matches_auto_dismiss_policy).to be false end end @@ -120,41 +80,20 @@ end let_it_be(:rule) do - create(:vulnerability_management_policy_rule, :detected, - security_policy: policy, - content: { - 'criteria' => [ - { 'type' => 'file_path', 'value' => 'test/**/*' } - ] - }) + create(:vulnerability_management_policy_rule, :detected_file_path, + security_policy: policy, **policy_rule_attributes) end - context 'when finding matches policy criteria' do - it 'sets policy_dismissal_precomputed to true' do - findings = [matching_finding] - described_class.preload_auto_dismissal_checks!(project, findings) - - expect(matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be true - end + before do + described_class.preload_auto_dismissal_checks!(project, [matching_finding, non_matching_finding]) end - context 'when finding does not match policy criteria' do - it 'sets policy_dismissal_precomputed to false' do - findings = [non_matching_finding] - described_class.preload_auto_dismissal_checks!(project, findings) - - expect(non_matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be false - end + it 'sets matches_auto_dismiss_policy to true for matching findings' do + expect(matching_finding.matches_auto_dismiss_policy).to be true end - context 'with multiple findings with mixed matches' do - it 'correctly identifies which findings match' do - findings = [matching_finding, non_matching_finding] - described_class.preload_auto_dismissal_checks!(project, findings) - - expect(matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be true - expect(non_matching_finding.instance_variable_get(:@policy_dismissal_precomputed)).to be false - end + it 'sets matches_auto_dismiss_policy to false for non-matching findings' do + expect(non_matching_finding.matches_auto_dismiss_policy).to be false end end end -- GitLab From 74d1251a11267d9d823f0ed872f1f1470467a0dd Mon Sep 17 00:00:00 2001 From: Martin Cavoj Date: Wed, 17 Dec 2025 12:55:34 +0100 Subject: [PATCH 3/3] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Sashi Kumar Kumaresan --- .../models/concerns/vulnerabilities/policy_auto_dismissable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb b/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb index b658573a1a06f4..096c978f7af93b 100644 --- a/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb +++ b/ee/app/models/concerns/vulnerabilities/policy_auto_dismissable.rb @@ -13,9 +13,9 @@ module PolicyAutoDismissable class_methods do def preload_auto_dismissal_checks!(project, findings) + return findings if findings.empty? return findings if Feature.disabled?(:auto_dismiss_vulnerability_policies, project.group) return findings unless project.licensed_feature_available?(:security_orchestration_policies) - return findings if findings.empty? checker = Security::Findings::PolicyAutoDismissalChecker.new(project) auto_dismissal_map = checker.check_batch(findings) -- GitLab