From 90c71aee91d2c05063ffd9f42f4901fff3c71582 Mon Sep 17 00:00:00 2001 From: mc_rocha Date: Tue, 7 Oct 2025 16:46:40 -0400 Subject: [PATCH 1/4] Add policy violations to vulnerability graph api This MR adds a new argument to VulnerabilitiesResolver, to filter vulnerabilities dismissed by security policies EE: true Changelog: added --- .../policy_violations_es_filter.yml | 10 ++++ doc/api/graphql/reference/_index.md | 9 +++ .../vulnerability_elastic_base_finder.rb | 7 +++ .../resolvers/vulnerabilities_resolver.rb | 8 +++ .../resolvers/vulnerability_filterable.rb | 19 ++++++- .../policy_violations_enum.rb | 13 +++++ .../search/elastic/vulnerability_filters.rb | 20 +++++++ .../elastic/vulnerability_query_builder.rb | 5 ++ .../vulnerabilities_resolver_spec.rb | 57 +++++++++++++++++++ .../policy_violations_enum_spec.rb | 11 ++++ .../elastic/vulnerability_filters_spec.rb | 41 +++++++++++++ .../vulnerability_query_builder_spec.rb | 31 ++++++++++ 12 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 config/feature_flags/gitlab_com_derisk/policy_violations_es_filter.yml create mode 100644 ee/app/graphql/types/security_orchestration/policy_violations_enum.rb create mode 100644 ee/spec/graphql/types/security_orchestration/policy_violations_enum_spec.rb diff --git a/config/feature_flags/gitlab_com_derisk/policy_violations_es_filter.yml b/config/feature_flags/gitlab_com_derisk/policy_violations_es_filter.yml new file mode 100644 index 00000000000000..07d346568e3ce2 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/policy_violations_es_filter.yml @@ -0,0 +1,10 @@ +--- +name: policy_violations_es_filter +description: +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/561739 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/207947 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/577591 +milestone: '18.6' +group: group::security policies +type: gitlab_com_derisk +default_enabled: false diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 5577fa57ae3c2b..a17f3dedc3f26f 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -1993,6 +1993,7 @@ four standard [pagination arguments](#pagination-arguments): | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations.To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | @@ -32876,6 +32877,7 @@ four standard [pagination arguments](#pagination-arguments): | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations.To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | @@ -41748,6 +41750,7 @@ four standard [pagination arguments](#pagination-arguments): | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations.To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | @@ -50732,6 +50735,12 @@ Types of security policy project created status. | `RUNNING` | Represents a running policy violation. | | `WARNING` | Represents a policy violation warning. | +### `PolicyViolations` + +| Value | Description | +| ----- | ----------- | +| `DISMISSED_IN_MR` | Dismissed in Merge request bypass reason. | + ### `PrincipalType` Types of principal that can have secret permissions. diff --git a/ee/app/finders/security/vulnerability_elastic_base_finder.rb b/ee/app/finders/security/vulnerability_elastic_base_finder.rb index 14fbb26559255d..5c5d3d337992d3 100644 --- a/ee/app/finders/security/vulnerability_elastic_base_finder.rb +++ b/ee/app/finders/security/vulnerability_elastic_base_finder.rb @@ -77,6 +77,7 @@ def initialize_search_params identifier_name: params[:identifier_name], reachability: reachability, validity_check: validity_check, + policy_violations: policy_violations, sort: sort } end @@ -142,5 +143,11 @@ def es_search_options def root_ancestor_ids [vulnerable.root_ancestor.id] end + + def policy_violations + return unless params[:policy_violations] + + ::Security::PolicyViolations::VIOLATIONS_TYPES.slice(*params[:policy_violations]).values + end end end diff --git a/ee/app/graphql/resolvers/vulnerabilities_resolver.rb b/ee/app/graphql/resolvers/vulnerabilities_resolver.rb index 9e232ee3669d85..eddca4a602c263 100644 --- a/ee/app/graphql/resolvers/vulnerabilities_resolver.rb +++ b/ee/app/graphql/resolvers/vulnerabilities_resolver.rb @@ -110,6 +110,14 @@ class VulnerabilitiesResolver < VulnerabilitiesBaseResolver experiment: { milestone: '18.5' }, description: 'Filter vulnerabilities by validity check.' + argument :policy_violations, [::Types::SecurityOrchestration::PolicyViolationsEnum], + required: false, + experiment: { milestone: '18.6' }, + description: 'Filter by security policy violations.' \ + 'To use this argument, you must have Elasticsearch configured and the ' \ + '`advanced_vulnerability_management` feature flag enabled. ' \ + 'Not supported on Instance Security Dashboard queries.' + def resolve_with_lookahead(**args) return Vulnerability.none unless vulnerable&.feature_available?(:security_dashboard) diff --git a/ee/app/graphql/resolvers/vulnerability_filterable.rb b/ee/app/graphql/resolvers/vulnerability_filterable.rb index 672a1f1efe5177..6462dcd526136b 100644 --- a/ee/app/graphql/resolvers/vulnerability_filterable.rb +++ b/ee/app/graphql/resolvers/vulnerability_filterable.rb @@ -8,7 +8,7 @@ module VulnerabilityFilterable private - ADVANCED_FILTERS = [:owasp_top_10_2021, :identifier_name, :reachability, :validity_check].freeze + ADVANCED_FILTERS = [:owasp_top_10_2021, :identifier_name, :reachability, :validity_check, :policy_violations].freeze def validate_filters(filters) # identifier_name is also supported on postgres @@ -19,6 +19,7 @@ def validate_filters(filters) validate_reachability!(vulnerable) if filters[:reachability].present? validate_validity_check!(vulnerable) if filters[:validity_check].present? + validate_policy_violations!(vulnerable) if filters[:policy_violations].present? # Identifier validation should only run for # 1. GitLab .com and Dedicated if ES is not available @@ -90,5 +91,21 @@ def validate_validity_check!(vulnerable) 'The \'validity_check\' argument is not currently supported on security center dashboard or ' \ 'the required migrations are not completed.' end + + def validate_policy_violations!(vulnerable) + group = + if vulnerable.is_a?(Project) + vulnerable.group + elsif vulnerable.is_a?(Group) + vulnerable + end + + return unless group + + return if Feature.enabled?(:policy_violations_es_filter, group) + + raise ::Gitlab::Graphql::Errors::ArgumentError, + 'The \'policy_violations\' argument is not currently supported on security center dashboard' + end end end diff --git a/ee/app/graphql/types/security_orchestration/policy_violations_enum.rb b/ee/app/graphql/types/security_orchestration/policy_violations_enum.rb new file mode 100644 index 00000000000000..0d8b71b3e20149 --- /dev/null +++ b/ee/app/graphql/types/security_orchestration/policy_violations_enum.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + module SecurityOrchestration # rubocop:disable Gitlab/BoundedContexts -- Existing module + class PolicyViolationsEnum < BaseEnum + graphql_name 'PolicyViolations' + + value 'DISMISSED_IN_MR', + description: 'Dismissed in Merge request bypass reason.', + value: :dismissed_in_mr + end + end +end diff --git a/ee/lib/search/elastic/vulnerability_filters.rb b/ee/lib/search/elastic/vulnerability_filters.rb index 93adeaa2b0fad1..c33dfa27859413 100644 --- a/ee/lib/search/elastic/vulnerability_filters.rb +++ b/ee/lib/search/elastic/vulnerability_filters.rb @@ -433,6 +433,26 @@ def by_validity_check(query_hash:, options:) end end + def by_policy_violations(query_hash:, options:) + policy_violations = options[:policy_violations] + return query_hash if policy_violations.blank? + + unless policy_violations.to_set.subset?(::Security::PolicyViolations::VIOLATIONS_TYPES.values.to_set) + return query_hash + end + + context.name(:filters) do + add_filter(query_hash, :query, :bool, :filter) do + { + terms: { + _name: context.name(:policy_violations), + policy_violations: policy_violations + } + } + end + end + end + private def valid_owasp_values?(owasp_values, regex_constant) diff --git a/ee/lib/search/elastic/vulnerability_query_builder.rb b/ee/lib/search/elastic/vulnerability_query_builder.rb index 81c703cee08273..3f38aca86536a5 100644 --- a/ee/lib/search/elastic/vulnerability_query_builder.rb +++ b/ee/lib/search/elastic/vulnerability_query_builder.rb @@ -54,6 +54,11 @@ def build # rubocop:disable Metrics/AbcSize -- need all the filters in one place ) end + if ::Elastic::DataMigrationService.migration_has_finished?(:add_policy_violations_field_to_vulnerability) + query_hash = ::Search::Elastic::VulnerabilityFilters.by_policy_violations( + query_hash: query_hash, options: options) + end + query_hash = ::Search::Elastic::VulnerabilityAggregations.by_severity_counts( query_hash: query_hash, options: options) query_hash = ::Search::Elastic::VulnerabilityAggregations.by_identifiers_search( diff --git a/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb b/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb index a47fe6dbd48fa2..576fff965356f5 100644 --- a/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb +++ b/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb @@ -290,6 +290,17 @@ end end end + + context 'when filtering vulnerabilities with policy_violations', :elastic do + let(:params) { { policy_violations: ['DISMISSED_IN_MR'] } } + let(:error_msg) { "Feature is not supported for InstanceSecurityDashboard" } + + it 'raises an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, s_(error_msg)) do + resolved + end + end + end end context 'when image is given' do @@ -574,6 +585,52 @@ def create_vuln_with_status(severity:, status: nil) end end + context 'when filtering vulnerabilities with policy_violations', :elastic do + let(:params) { { policy_violations: ['DISMISSED_IN_MR'] } } + + context 'without elasticsearch' do + before do + allow(::Search::Elastic::VulnerabilityIndexingHelper).to receive(:vulnerability_indexing_allowed?).and_return(false) + end + + it_behaves_like 'raises ES errors' + end + + context 'with advanced_vulnerability_management FF disabled' do + before do + allow(::Search::Elastic::VulnerabilityIndexingHelper).to receive(:vulnerability_indexing_allowed?).and_return(true) + stub_feature_flags(advanced_vulnerability_management: false) + end + + it_behaves_like 'raises ES errors' + end + + context 'with elastic search' do + let_it_be(:dismissed_vulnerability_read) { create(:vulnerability_read, project: project) } + + let_it_be(:non_dismissed_vulnerability_read) { create(:vulnerability_read, project: project) } + + before do + stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true) + + create(:policy_dismissal, :preserved, project: project, security_findings_uuids: [dismissed_vulnerability_read.uuid]) + + Elastic::ProcessBookkeepingService.track!(dismissed_vulnerability_read, non_dismissed_vulnerability_read) + ensure_elasticsearch_index! + + allow(current_user).to receive(:can?).with(:access_advanced_vulnerability_management, vulnerable).and_return(true) + end + + it 'only returns vulnerabilities with matching policy_violations types' do + expect(Gitlab::Search::Client).to receive(:execute_search).and_call_original + + results = resolved.to_a + + expect(results).to match_array([dismissed_vulnerability_read].map(&:vulnerability)) + end + end + end + context 'when identifer_name is given' do let_it_be(:identifier_name) { 'CVE-2024-1234' } diff --git a/ee/spec/graphql/types/security_orchestration/policy_violations_enum_spec.rb b/ee/spec/graphql/types/security_orchestration/policy_violations_enum_spec.rb new file mode 100644 index 00000000000000..6c7738f43dc83e --- /dev/null +++ b/ee/spec/graphql/types/security_orchestration/policy_violations_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PolicyViolations'], feature_category: :security_policy_management do + specify { expect(described_class.graphql_name).to eq('PolicyViolations') } + + it 'exposes all policy violations types' do + expect(described_class.values.keys).to include(*%w[DISMISSED_IN_MR]) + end +end diff --git a/ee/spec/lib/search/elastic/vulnerability_filters_spec.rb b/ee/spec/lib/search/elastic/vulnerability_filters_spec.rb index 4a695face28d8c..ef3d3fb56d0ffb 100644 --- a/ee/spec/lib/search/elastic/vulnerability_filters_spec.rb +++ b/ee/spec/lib/search/elastic/vulnerability_filters_spec.rb @@ -867,4 +867,45 @@ end end end + + describe '.by_policy_violations' do + subject(:by_policy_violations) { described_class.by_policy_violations(query_hash: query_hash, options: options) } + + context 'when options[:policy_violations] is empty' do + let(:options) { {} } + + it_behaves_like 'does not modify the query_hash' + + context 'with invalid by_policy_violations value' do + let(:options) { { policy_violations: [::Security::PolicyViolations::VIOLATIONS_TYPES.values.last + 1] } } + + it_behaves_like 'does not modify the query_hash' + end + + context 'when options[:policy_violations] contains a valid policy violation' do + let(:options) { { policy_violations: [::Security::PolicyViolations::VIOLATIONS_TYPES.each_value.first] } } + let(:expected_query) do + { + query: { + bool: { + filter: [{ + terms: { + _name: "filters:policy_violations", + policy_violations: [0] + } + }], + must_not: [], + must: [], + should: [] + } + } + } + end + + it 'adds the policy_violations filter to query_hash' do + expect(by_policy_violations).to eq(expected_query) + end + end + end + end end diff --git a/ee/spec/lib/search/elastic/vulnerability_query_builder_spec.rb b/ee/spec/lib/search/elastic/vulnerability_query_builder_spec.rb index 28ebd7d905a31f..945176ff9b3591 100644 --- a/ee/spec/lib/search/elastic/vulnerability_query_builder_spec.rb +++ b/ee/spec/lib/search/elastic/vulnerability_query_builder_spec.rb @@ -293,6 +293,37 @@ end end + describe 'policy_violations' do + let(:options) do + base_options.merge(policy_violations: [::Security::PolicyViolations::VIOLATIONS_TYPES[:dismissed_in_mr]]) + end + + context 'with finished migrations' do + before do + set_elasticsearch_migration_to(:add_policy_violations_field_to_vulnerability) + end + + it 'does add filter' do + assert_names_in_query(build, with: %w[ + filters:archived_projects + filters:policy_violations + ]) + end + end + + context 'without finished migrations' do + before do + set_elasticsearch_migration_to(:add_policy_violations_field_to_vulnerability, including: false) + end + + it 'does not add reachability filter' do + assert_names_in_query(build, with: %w[ + filters:archived_projects + ]) + end + end + end + describe 'by_vulnerabilities_over_time' do let(:current_day) { Time.current.beginning_of_day } let(:current_day_minus_1) { current_day - 1.day } -- GitLab From 1bfd826450859f1fb7514140e2cea0b54a113d3d Mon Sep 17 00:00:00 2001 From: mc_rocha Date: Mon, 20 Oct 2025 10:16:33 -0400 Subject: [PATCH 2/4] Address reviewer suggestions --- doc/api/graphql/reference/_index.md | 6 +++--- ee/app/graphql/resolvers/vulnerabilities_resolver.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index a17f3dedc3f26f..e8b6aedef8e7e4 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -1993,7 +1993,7 @@ four standard [pagination arguments](#pagination-arguments): | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | -| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations.To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | @@ -32877,7 +32877,7 @@ four standard [pagination arguments](#pagination-arguments): | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | -| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations.To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | @@ -41750,7 +41750,7 @@ four standard [pagination arguments](#pagination-arguments): | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | -| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations.To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | diff --git a/ee/app/graphql/resolvers/vulnerabilities_resolver.rb b/ee/app/graphql/resolvers/vulnerabilities_resolver.rb index eddca4a602c263..b98b220fb199cd 100644 --- a/ee/app/graphql/resolvers/vulnerabilities_resolver.rb +++ b/ee/app/graphql/resolvers/vulnerabilities_resolver.rb @@ -113,7 +113,7 @@ class VulnerabilitiesResolver < VulnerabilitiesBaseResolver argument :policy_violations, [::Types::SecurityOrchestration::PolicyViolationsEnum], required: false, experiment: { milestone: '18.6' }, - description: 'Filter by security policy violations.' \ + description: 'Filter by security policy violations. ' \ 'To use this argument, you must have Elasticsearch configured and the ' \ '`advanced_vulnerability_management` feature flag enabled. ' \ 'Not supported on Instance Security Dashboard queries.' -- GitLab From 93fc02fc3ecc1fa06259e466dacb12e12550ed07 Mon Sep 17 00:00:00 2001 From: mc_rocha Date: Mon, 20 Oct 2025 13:37:19 -0400 Subject: [PATCH 3/4] Add missing spec --- .../resolvers/vulnerabilities_resolver_spec.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb b/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb index 576fff965356f5..91e48f3f13a467 100644 --- a/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb +++ b/ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb @@ -605,7 +605,7 @@ def create_vuln_with_status(severity:, status: nil) it_behaves_like 'raises ES errors' end - context 'with elastic search' do + shared_examples_for 'when elasticsearch is available' do let_it_be(:dismissed_vulnerability_read) { create(:vulnerability_read, project: project) } let_it_be(:non_dismissed_vulnerability_read) { create(:vulnerability_read, project: project) } @@ -629,6 +629,18 @@ def create_vuln_with_status(severity:, status: nil) expect(results).to match_array([dismissed_vulnerability_read].map(&:vulnerability)) end end + + context 'when vulnerable is a project' do + let(:vulnerable) { project } + + it_behaves_like 'when elasticsearch is available' + end + + context 'when vulnerable is a group' do + let(:vulnerable) { group } + + it_behaves_like 'when elasticsearch is available' + end end context 'when identifer_name is given' do -- GitLab From 7ca5ff8d143d71a36c0a15038c34a6f4d70bab5a Mon Sep 17 00:00:00 2001 From: mc_rocha Date: Tue, 21 Oct 2025 14:30:33 -0400 Subject: [PATCH 4/4] Add policy_violations to vulnerabilities severity count --- doc/api/graphql/reference/_index.md | 3 +++ .../resolvers/vulnerability_filterable.rb | 4 +-- ...vulnerability_severities_count_resolver.rb | 8 ++++++ ...rability_severities_count_resolver_spec.rb | 26 +++++++++++++++++++ ...ulnerability_filterable_shared_examples.rb | 16 ++++++++++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index e8b6aedef8e7e4..03639d2fd31318 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -32975,6 +32975,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | @@ -34051,6 +34052,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | @@ -41835,6 +41837,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). | `image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | `owaspTopTen` | [`[VulnerabilityOwaspTop10!]`](#vulnerabilityowasptop10) | Filter vulnerabilities by OWASP Top 10 2017 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 values. | | `owaspTopTen2021` {{< icon name="warning-solid" >}} | [`[VulnerabilityOwasp2021Top10!]`](#vulnerabilityowasp2021top10) | **Introduced** in GitLab 18.1. **Status**: Experiment. Filter vulnerabilities by OWASP Top 10 2021 category. Wildcard value `NONE` is also supported but it cannot be combined with other OWASP top 10 2021 values. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | +| `policyViolations` {{< icon name="warning-solid" >}} | [`[PolicyViolations!]`](#policyviolations) | **Introduced** in GitLab 18.6. **Status**: Experiment. Filter by security policy violations. To use this argument, you must have Elasticsearch configured and the `advanced_vulnerability_management` feature flag enabled. Not supported on Instance Security Dashboard queries. | | `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. | | `reachability` {{< icon name="warning-solid" >}} | [`ReachabilityType`](#reachabilitytype) | **Introduced** in GitLab 18.2. **Status**: Experiment. Filter vulnerabilities by reachability. | | `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. | diff --git a/ee/app/graphql/resolvers/vulnerability_filterable.rb b/ee/app/graphql/resolvers/vulnerability_filterable.rb index 6462dcd526136b..48a47dbb9656df 100644 --- a/ee/app/graphql/resolvers/vulnerability_filterable.rb +++ b/ee/app/graphql/resolvers/vulnerability_filterable.rb @@ -100,9 +100,7 @@ def validate_policy_violations!(vulnerable) vulnerable end - return unless group - - return if Feature.enabled?(:policy_violations_es_filter, group) + return if group && Feature.enabled?(:policy_violations_es_filter, group) raise ::Gitlab::Graphql::Errors::ArgumentError, 'The \'policy_violations\' argument is not currently supported on security center dashboard' diff --git a/ee/app/graphql/resolvers/vulnerability_severities_count_resolver.rb b/ee/app/graphql/resolvers/vulnerability_severities_count_resolver.rb index 6ca4bdcd851a38..b2b5f84215c939 100644 --- a/ee/app/graphql/resolvers/vulnerability_severities_count_resolver.rb +++ b/ee/app/graphql/resolvers/vulnerability_severities_count_resolver.rb @@ -107,6 +107,14 @@ class VulnerabilitySeveritiesCountResolver < VulnerabilitiesBaseResolver experiment: { milestone: '18.5' }, description: 'Filter vulnerabilities by token status.' + argument :policy_violations, [::Types::SecurityOrchestration::PolicyViolationsEnum], + required: false, + experiment: { milestone: '18.6' }, + description: 'Filter by security policy violations. ' \ + 'To use this argument, you must have Elasticsearch configured and the ' \ + '`advanced_vulnerability_management` feature flag enabled. ' \ + 'Not supported on Instance Security Dashboard queries.' + def resolve(**args) return Vulnerability.none unless vulnerable diff --git a/ee/spec/graphql/resolvers/vulnerability_severities_count_resolver_spec.rb b/ee/spec/graphql/resolvers/vulnerability_severities_count_resolver_spec.rb index ad1bdc4156ae53..eba5f363e5ece0 100644 --- a/ee/spec/graphql/resolvers/vulnerability_severities_count_resolver_spec.rb +++ b/ee/spec/graphql/resolvers/vulnerability_severities_count_resolver_spec.rb @@ -558,6 +558,32 @@ def create_vuln_with_status(severity:, status: nil) end end end + + context 'with policy_violations as parameter', :elastic do + let(:filters) { { policy_violations: ['DISMISSED_IN_MR'] } } + let(:expected_result) { { 'critical' => 1 } } + let_it_be(:dismissed_vulnerability_read) { create(:vulnerability_read, severity: :critical, project: project) } + let_it_be(:non_dismissed_vulnerability_read) { create(:vulnerability_read, severity: :low, project: project) } + + before do + stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true) + + create(:policy_dismissal, :preserved, project: project, security_findings_uuids: [dismissed_vulnerability_read.uuid]) + + Elastic::ProcessBookkeepingService.track!(dismissed_vulnerability_read, non_dismissed_vulnerability_read) + ensure_elasticsearch_index! + + allow(current_user).to receive(:can?).with(:admin_all_resources).and_call_original + allow(current_user).to receive(:can?) + .with(:access_advanced_vulnerability_management, vulnerable) + .and_return(true) + end + + it 'only considers vulnerabilities with matching policy_violations values' do + expect(Gitlab::Search::Client).to receive(:execute_search).and_call_original + is_expected.to eq(expected_result) + end + end end context 'when resolving vulnerabilities for an instance security dashboard' do diff --git a/ee/spec/support/shared_examples/graphql/resolvers/vulnerability_filterable_shared_examples.rb b/ee/spec/support/shared_examples/graphql/resolvers/vulnerability_filterable_shared_examples.rb index b5bf249009f750..3b421f2b4d772d 100644 --- a/ee/spec/support/shared_examples/graphql/resolvers/vulnerability_filterable_shared_examples.rb +++ b/ee/spec/support/shared_examples/graphql/resolvers/vulnerability_filterable_shared_examples.rb @@ -54,6 +54,22 @@ end end end + + context 'with policy_violations' do + let(filter_key) { { policy_violations: [Security::FindingTokenStatus.statuses.each_key.first.upcase] } } + + context 'when `policy_violations_es_filter` feature flag is disabled' do + before do + stub_feature_flags(policy_violations_es_filter: false) + end + + it 'raises an ArgumentError' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + subject + end + end + end + end end end -- GitLab