From 89f94eefce504a0afafeea14b853005c98a2773e Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Fri, 14 Nov 2025 00:18:37 +0200 Subject: [PATCH 1/3] Add cascading setting for duo sast fp detection * implement relevant migration * implement cascading setting for instance / project Changelog: added EE: true --- app/helpers/projects_helper.rb | 5 ++ app/models/project_setting.rb | 2 +- .../duo_sast_fp_detection_enabled.yml | 11 +++++ .../lock_duo_sast_fp_detection_enabled.yml | 11 +++++ ...p_detection_enabled_to_project_settings.rb | 9 ++++ ..._fp_detection_enabled_cascading_setting.rb | 16 +++++++ db/schema_migrations/20251113120025 | 1 + db/schema_migrations/20251113120026 | 1 + db/structure.sql | 5 ++ doc/api/openapi/openapi_v2.yaml | 34 +++++++++++++ ee/app/controllers/ee/projects_controller.rb | 37 +++++++++++--- .../helpers/ee/application_settings_helper.rb | 2 + ee/app/helpers/ee/groups/settings_helper.rb | 4 +- ee/app/helpers/ee/projects_helper.rb | 1 + ee/app/models/ee/application_setting.rb | 17 ++++++- ee/app/models/ee/group.rb | 1 + ee/app/models/ee/namespace_setting.rb | 17 ++++++- ee/app/models/ee/project.rb | 2 + ee/app/models/ee/project_setting.rb | 3 +- ee/app/models/ee/vulnerability.rb | 1 + .../admin/ai_configuration_presenter.rb | 2 + .../ai/cascade_duo_settings_service.rb | 2 +- .../ee/application_settings/update_service.rb | 2 +- ee/app/services/ee/groups/update_service.rb | 2 +- .../assign_attributes_service.rb | 8 ++++ ee/lib/ee/api/entities/project.rb | 1 + ee/lib/ee/api/groups.rb | 5 ++ ee/lib/ee/api/helpers/groups_helpers.rb | 1 + ee/lib/ee/api/helpers/projects_helpers.rb | 7 +++ .../ee/projects_controller_spec.rb | 45 ++++++++++++++++- .../helpers/ee/groups/settings_helper_spec.rb | 3 ++ ee/spec/helpers/projects_helper_spec.rb | 1 + ee/spec/lib/ee/api/entities/project_spec.rb | 37 ++++++++++++++ ee/spec/models/application_setting_spec.rb | 21 ++++++++ ee/spec/models/ee/project_setting_spec.rb | 5 ++ ee/spec/models/ee/vulnerability_spec.rb | 33 ++++++++++--- ee/spec/models/namespace_setting_spec.rb | 20 ++++++++ .../admin/ai_configuration_presenter_spec.rb | 2 + ee/spec/requests/api/groups_spec.rb | 48 +++++++++++++++++++ ee/spec/requests/api/projects_spec.rb | 45 +++++++++++++++++ .../update_service_spec.rb | 8 ++++ .../services/groups/update_service_spec.rb | 4 ++ 42 files changed, 460 insertions(+), 22 deletions(-) create mode 100644 config/application_setting_columns/duo_sast_fp_detection_enabled.yml create mode 100644 config/application_setting_columns/lock_duo_sast_fp_detection_enabled.yml create mode 100644 db/migrate/20251113120025_add_duo_sast_fp_detection_enabled_to_project_settings.rb create mode 100644 db/migrate/20251113120026_add_duo_sast_fp_detection_enabled_cascading_setting.rb create mode 100644 db/schema_migrations/20251113120025 create mode 100644 db/schema_migrations/20251113120026 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e253037aaa09cd..64139bc2615aa4 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -1040,6 +1040,11 @@ def gitlab_duo_settings_data(project) :duo_foundational_flows_enabled, project, method(:edit_group_path) + ), + duo_sast_fp_detection_cascading_settings: project_cascading_namespace_settings_tooltip_data( + :duo_sast_fp_detection_enabled, + project, + method(:edit_group_path) ) } end diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb index e8cceb53102d0c..5cb9a202f240d0 100644 --- a/app/models/project_setting.rb +++ b/app/models/project_setting.rb @@ -9,7 +9,7 @@ class ProjectSetting < ApplicationRecord include AfterCommitQueue include SafelyChangeColumnDefault - columns_changing_default :auto_duo_code_review_enabled, :duo_remote_flows_enabled + columns_changing_default :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_sast_fp_detection_enabled ALLOWED_TARGET_PLATFORMS = %w[ios osx tvos watchos android].freeze diff --git a/config/application_setting_columns/duo_sast_fp_detection_enabled.yml b/config/application_setting_columns/duo_sast_fp_detection_enabled.yml new file mode 100644 index 00000000000000..d106459f2385f0 --- /dev/null +++ b/config/application_setting_columns/duo_sast_fp_detection_enabled.yml @@ -0,0 +1,11 @@ +api_type: +attr: duo_sast_fp_detection_enabled +clusterwide: false +column: duo_sast_fp_detection_enabled +db_type: boolean +default: 'true' +description: +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/config/application_setting_columns/lock_duo_sast_fp_detection_enabled.yml b/config/application_setting_columns/lock_duo_sast_fp_detection_enabled.yml new file mode 100644 index 00000000000000..27cddfdac274db --- /dev/null +++ b/config/application_setting_columns/lock_duo_sast_fp_detection_enabled.yml @@ -0,0 +1,11 @@ +api_type: +attr: lock_duo_sast_fp_detection_enabled +clusterwide: false +column: lock_duo_sast_fp_detection_enabled +db_type: boolean +default: 'false' +description: +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/db/migrate/20251113120025_add_duo_sast_fp_detection_enabled_to_project_settings.rb b/db/migrate/20251113120025_add_duo_sast_fp_detection_enabled_to_project_settings.rb new file mode 100644 index 00000000000000..026a305ce538ab --- /dev/null +++ b/db/migrate/20251113120025_add_duo_sast_fp_detection_enabled_to_project_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddDuoSastFpDetectionEnabledToProjectSettings < Gitlab::Database::Migration[2.3] + milestone '18.7' + + def change + add_column :project_settings, :duo_sast_fp_detection_enabled, :boolean, default: true, null: false + end +end diff --git a/db/migrate/20251113120026_add_duo_sast_fp_detection_enabled_cascading_setting.rb b/db/migrate/20251113120026_add_duo_sast_fp_detection_enabled_cascading_setting.rb new file mode 100644 index 00000000000000..545611dee4f627 --- /dev/null +++ b/db/migrate/20251113120026_add_duo_sast_fp_detection_enabled_cascading_setting.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddDuoSastFpDetectionEnabledCascadingSetting < Gitlab::Database::Migration[2.3] + milestone '18.7' + disable_ddl_transaction! + + include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings + + def up + add_cascading_namespace_setting :duo_sast_fp_detection_enabled, :boolean, default: true, null: false + end + + def down + remove_cascading_namespace_setting :duo_sast_fp_detection_enabled + end +end diff --git a/db/schema_migrations/20251113120025 b/db/schema_migrations/20251113120025 new file mode 100644 index 00000000000000..8c24755455583d --- /dev/null +++ b/db/schema_migrations/20251113120025 @@ -0,0 +1 @@ +cf9a366901fc6eb7da309b96ed04ffa2fffcd4fb7e179f899d042ab14009d8af \ No newline at end of file diff --git a/db/schema_migrations/20251113120026 b/db/schema_migrations/20251113120026 new file mode 100644 index 00000000000000..aaef99cd82bb91 --- /dev/null +++ b/db/schema_migrations/20251113120026 @@ -0,0 +1 @@ +6a350c11fdcf482427712def37ce71aba2f4564a3ac1688700cfaec3c8484546 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 56ed9c50550cc8..b087fe71547f70 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12132,6 +12132,8 @@ CREATE TABLE application_settings ( namespace_deletion_settings jsonb DEFAULT '{}'::jsonb NOT NULL, duo_foundational_flows_enabled boolean DEFAULT false NOT NULL, lock_duo_foundational_flows_enabled boolean DEFAULT false NOT NULL, + duo_sast_fp_detection_enabled boolean DEFAULT true NOT NULL, + lock_duo_sast_fp_detection_enabled boolean DEFAULT false NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), @@ -21279,6 +21281,8 @@ CREATE TABLE namespace_settings ( disable_ssh_keys boolean DEFAULT false NOT NULL, duo_foundational_flows_enabled boolean, lock_duo_foundational_flows_enabled boolean DEFAULT false NOT NULL, + duo_sast_fp_detection_enabled boolean, + lock_duo_sast_fp_detection_enabled boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)), CONSTRAINT check_d9644d516f CHECK ((char_length(step_up_auth_required_oauth_provider) <= 255)), CONSTRAINT check_namespace_settings_security_policies_is_hash CHECK ((jsonb_typeof(security_policies) = 'object'::text)), @@ -25011,6 +25015,7 @@ CREATE TABLE project_settings ( merge_request_title_regex_description text, duo_remote_flows_enabled boolean, duo_foundational_flows_enabled boolean, + duo_sast_fp_detection_enabled boolean DEFAULT true NOT NULL, CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)), CONSTRAINT check_237486989c CHECK ((char_length(merge_request_title_regex_description) <= 255)), CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)), diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index bf152cc9500b73..ffe7c60a0ba857 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -31990,6 +31990,11 @@ paths: description: Enable GitLab Duo remote flows for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_fp_detection_enabled] + description: Enable GitLab Duo SAST false positive detection for this project + type: boolean + required: false - in: formData name: override_params[spp_repository_pipeline_access] description: Grant read-only access to security policy configurations for @@ -32687,6 +32692,11 @@ paths: description: Enable GitLab Duo remote flows for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_fp_detection_enabled] + description: Enable GitLab Duo SAST false positive detection for this project + type: boolean + required: false - in: formData name: override_params[spp_repository_pipeline_access] description: Grant read-only access to security policy configurations for @@ -33463,6 +33473,11 @@ paths: description: Enable GitLab Duo remote flows for this project type: boolean required: false + - in: formData + name: override_params[duo_sast_fp_detection_enabled] + description: Enable GitLab Duo SAST false positive detection for this project + type: boolean + required: false - in: formData name: override_params[spp_repository_pipeline_access] description: Grant read-only access to security policy configurations for @@ -48058,6 +48073,9 @@ definitions: duo_foundational_flows_availability: type: boolean description: Enable GitLab foundational Duo flows for this group + duo_sast_fp_detection_availability: + type: boolean + description: Enable GitLab Duo SAST false position detection for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -48264,6 +48282,9 @@ definitions: duo_foundational_flows_availability: type: boolean description: Enable GitLab foundational Duo flows for this group + duo_sast_fp_detection_availability: + type: boolean + description: Enable GitLab Duo SAST false position detection for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -48947,6 +48968,8 @@ definitions: type: string duo_foundational_flows_enabled: type: string + duo_sast_fp_detection_enabled: + type: string web_based_commit_signing_enabled: type: string spp_repository_pipeline_access: @@ -64419,6 +64442,9 @@ definitions: duo_remote_flows_enabled: type: boolean description: Enable GitLab Duo remote flows for this project + duo_sast_fp_detection_enabled: + type: boolean + description: Enable GitLab Duo SAST false positive detection for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for @@ -64862,6 +64888,9 @@ definitions: duo_remote_flows_enabled: type: boolean description: Enable GitLab Duo remote flows for this project + duo_sast_fp_detection_enabled: + type: boolean + description: Enable GitLab Duo SAST false positive detection for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for @@ -65309,6 +65338,8 @@ definitions: type: string duo_foundational_flows_enabled: type: string + duo_sast_fp_detection_enabled: + type: string web_based_commit_signing_enabled: type: string spp_repository_pipeline_access: @@ -65788,6 +65819,9 @@ definitions: duo_remote_flows_enabled: type: boolean description: Enable GitLab Duo remote flows for this project + duo_sast_fp_detection_enabled: + type: boolean + description: Enable GitLab Duo SAST false positive detection for this project spp_repository_pipeline_access: type: boolean description: Grant read-only access to security policy configurations for diff --git a/ee/app/controllers/ee/projects_controller.rb b/ee/app/controllers/ee/projects_controller.rb index 17086285e59e45..b84adf8d540d33 100644 --- a/ee/app/controllers/ee/projects_controller.rb +++ b/ee/app/controllers/ee/projects_controller.rb @@ -51,7 +51,15 @@ def active_new_project_tab override :project_setting_attributes def project_setting_attributes - attributes = %i[ + attributes = base_project_setting_attributes + attributes += licensed_feature_attributes + attributes += duo_feature_attributes + + super + attributes + end + + def base_project_setting_attributes + %i[ prevent_merge_without_jira_issue cve_id_request_enabled product_analytics_data_collector_host @@ -59,15 +67,15 @@ def project_setting_attributes cube_api_key product_analytics_configurator_connection_string ] + end + + def licensed_feature_attributes + attributes = [] if project&.licensed_feature_available?(:external_status_checks) attributes << :only_allow_merge_if_all_status_checks_passed end - if project&.licensed_ai_features_available? && ::Feature.enabled?(:use_duo_context_exclusion, project) - attributes << { duo_context_exclusion_settings: { exclusion_rules: [] } } - end - if project&.licensed_feature_available?(:security_orchestration_policies) attributes << :spp_repository_pipeline_access end @@ -77,12 +85,29 @@ def project_setting_attributes end add_duo_workflow_attributes(attributes) + attributes + end + + def duo_feature_attributes + attributes = [] + + if duo_context_exclusion_available? + attributes << { duo_context_exclusion_settings: { exclusion_rules: [] } } + end unless project&.project_setting&.duo_features_enabled_locked? attributes << :duo_features_enabled end - super + attributes + unless project&.project_setting&.duo_sast_fp_detection_enabled_locked? + attributes << :duo_sast_fp_detection_enabled + end + + attributes + end + + def duo_context_exclusion_available? + project&.licensed_ai_features_available? && ::Feature.enabled?(:use_duo_context_exclusion, project) end def add_duo_workflow_attributes(attributes) diff --git a/ee/app/helpers/ee/application_settings_helper.rb b/ee/app/helpers/ee/application_settings_helper.rb index 5c3d1dfe4b86cf..f54ff51be3c5c2 100644 --- a/ee/app/helpers/ee/application_settings_helper.rb +++ b/ee/app/helpers/ee/application_settings_helper.rb @@ -87,6 +87,8 @@ def visible_attributes :duo_remote_flows_availability, :duo_foundational_flows_enabled, :duo_foundational_flows_availability, + :duo_sast_fp_detection_enabled, + :duo_sast_fp_detection_availability, :enabled_expanded_logging, :foundational_agents_default_enabled, # Add all Zoekt settings automatically diff --git a/ee/app/helpers/ee/groups/settings_helper.rb b/ee/app/helpers/ee/groups/settings_helper.rb index cb4821d31a13ce..d5220535d522f7 100644 --- a/ee/app/helpers/ee/groups/settings_helper.rb +++ b/ee/app/helpers/ee/groups/settings_helper.rb @@ -59,11 +59,12 @@ def group_ai_settings_helper_data duo_availability_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_features_enabled, @group, method(:edit_group_path))[:tooltip_data] duo_remote_flows_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_remote_flows_enabled, @group, method(:edit_group_path))[:tooltip_data] duo_foundational_flows_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_foundational_flows_enabled, @group, method(:edit_group_path))[:tooltip_data] - + duo_sast_fp_detection_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_sast_fp_detection_enabled, @group, method(:edit_group_path))[:tooltip_data] { duo_availability_cascading_settings: duo_availability_cascading_settings, duo_availability: @group.namespace_settings.duo_availability.to_s, duo_remote_flows_cascading_settings: duo_remote_flows_cascading_settings, + duo_sast_fp_detection_cascading_settings: duo_sast_fp_detection_cascading_settings, are_duo_settings_locked: @group.namespace_settings.duo_features_enabled_locked?.to_s, experiment_features_enabled: @group.namespace_settings.experiment_features_enabled.to_s, duo_core_features_enabled: @group.namespace_settings.duo_core_features_enabled.to_s, @@ -76,6 +77,7 @@ def group_ai_settings_helper_data duo_workflow_available: (@group.root? && current_user.can?(:admin_duo_workflow, @group)).to_s, duo_workflow_mcp_enabled: @group.duo_workflow_mcp_enabled.to_s, duo_remote_flows_availability: @group.namespace_settings.duo_remote_flows_availability.to_s, + duo_sast_fp_detection_availability: @group.namespace_settings.duo_sast_fp_detection_availability.to_s, foundational_agents_default_enabled: @group.foundational_agents_default_enabled.to_s, duo_foundational_flows_cascading_settings: duo_foundational_flows_cascading_settings, duo_foundational_flows_availability: @group.namespace_settings.duo_foundational_flows_availability.to_s, diff --git a/ee/app/helpers/ee/projects_helper.rb b/ee/app/helpers/ee/projects_helper.rb index 456fe83055a097..222c351859f245 100644 --- a/ee/app/helpers/ee/projects_helper.rb +++ b/ee/app/helpers/ee/projects_helper.rb @@ -58,6 +58,7 @@ def gitlab_duo_settings_data(project) duoContextExclusionSettings: project.project_setting.duo_context_exclusion_settings || {}, initialDuoRemoteFlowsAvailability: project.duo_remote_flows_enabled, initialDuoFoundationalFlowsAvailability: project.duo_foundational_flows_enabled, + initialDuoSastFpDetectionEnabled: project.duo_sast_fp_detection_enabled, experimentFeaturesEnabled: experiment_features_enabled, paidDuoTier: paid_duo_tier_for_project(project) }) diff --git a/ee/app/models/ee/application_setting.rb b/ee/app/models/ee/application_setting.rb index dc361dfd1d0d97..fd4e1fe5face0f 100644 --- a/ee/app/models/ee/application_setting.rb +++ b/ee/app/models/ee/application_setting.rb @@ -371,7 +371,8 @@ module ApplicationSetting allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') } - validates :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_foundational_flows_enabled, + validates :auto_duo_code_review_enabled, :duo_remote_flows_enabled, + :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled, inclusion: { in: [true, false] } after_commit :update_personal_access_tokens_lifetime, if: :saved_change_to_max_personal_access_token_lifetime? @@ -745,6 +746,20 @@ def duo_foundational_flows_availability=(value) end end + def duo_sast_fp_detection_availability + duo_sast_fp_detection_enabled + end + + def duo_sast_fp_detection_availability=(value) + self.duo_sast_fp_detection_enabled = value + + self.lock_duo_sast_fp_detection_enabled = if value + false + else + true + end + end + def duo_never_on? duo_availability == :never_on end diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb index 8e982ecf101767..c2ebbe7de7e64c 100644 --- a/ee/app/models/ee/group.rb +++ b/ee/app/models/ee/group.rb @@ -115,6 +115,7 @@ module Group delegate :duo_availability, :duo_availability=, to: :namespace_settings delegate :duo_remote_flows_availability, :duo_remote_flows_availability=, to: :namespace_settings delegate :duo_foundational_flows_availability, :duo_foundational_flows_availability=, to: :namespace_settings + delegate :duo_sast_fp_detection_availability, :duo_sast_fp_detection_availability=, to: :namespace_settings delegate :experiment_settings_allowed?, :prompt_cache_settings_allowed?, to: :namespace_settings delegate :user_cap_enabled?, to: :namespace_settings diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb index 9ef3484d47037a..555210033310a3 100644 --- a/ee/app/models/ee/namespace_setting.rb +++ b/ee/app/models/ee/namespace_setting.rb @@ -9,7 +9,7 @@ module NamespaceSetting DORMANT_REVIEW_PERIOD = 18.hours.ago cascading_attr :duo_features_enabled, :model_prompt_cache_enabled, :auto_duo_code_review_enabled, - :duo_remote_flows_enabled, :duo_foundational_flows_enabled + :duo_remote_flows_enabled, :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled scope :requiring_dormant_member_review, ->(limit) do # look for settings that have not been reviewed in more than @@ -184,6 +184,19 @@ def duo_foundational_flows_availability=(value) end end + def duo_sast_fp_detection_availability + duo_sast_fp_detection_enabled + end + + def duo_sast_fp_detection_availability=(value) + self.duo_sast_fp_detection_enabled = value + self.lock_duo_sast_fp_detection_enabled = if value + false + else + true + end + end + def duo_availability if duo_features_enabled && !duo_features_enabled_locked?(include_self: true) :default_on @@ -295,6 +308,8 @@ def experiment_features_allowed auto_duo_code_review_enabled duo_remote_flows_enabled duo_remote_flows_availability + duo_sast_fp_detection_enabled + lock_duo_sast_fp_detection_enabled enterprise_users_extensions_marketplace_opt_in_status allow_enterprise_bypass_placeholder_confirmation web_based_commit_signing_enabled diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb index 8b0ec1cf619665..214e773e90b2d7 100644 --- a/ee/app/models/ee/project.rb +++ b/ee/app/models/ee/project.rb @@ -550,6 +550,8 @@ def lock_for_confirmation!(id) :duo_remote_flows_enabled=, :duo_foundational_flows_enabled, :duo_foundational_flows_enabled=, + :duo_sast_fp_detection_enabled, + :duo_sast_fp_detection_enabled=, to: :project_setting with_options prefix: :delegated, to: :project_setting do delegate :require_reauthentication_to_approve= diff --git a/ee/app/models/ee/project_setting.rb b/ee/app/models/ee/project_setting.rb index 85cc4ce3c88200..b8e3c67465e812 100644 --- a/ee/app/models/ee/project_setting.rb +++ b/ee/app/models/ee/project_setting.rb @@ -7,7 +7,8 @@ module ProjectSetting prepended do cascading_attr :duo_features_enabled, :spp_repository_pipeline_access, :model_prompt_cache_enabled, - :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_foundational_flows_enabled + :auto_duo_code_review_enabled, :duo_remote_flows_enabled, :duo_foundational_flows_enabled, + :duo_sast_fp_detection_enabled belongs_to :push_rule diff --git a/ee/app/models/ee/vulnerability.rb b/ee/app/models/ee/vulnerability.rb index d038afd3ddfcc7..c9efff8d162ea7 100644 --- a/ee/app/models/ee/vulnerability.rb +++ b/ee/app/models/ee/vulnerability.rb @@ -381,6 +381,7 @@ def self.es_type def trigger_false_positive_detection return unless ::Feature.enabled?(:enable_vulnerability_fp_detection, group) return unless sast? + return unless project.duo_sast_fp_detection_enabled run_after_commit_or_now do ::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker.perform_async(id) diff --git a/ee/app/presenters/admin/ai_configuration_presenter.rb b/ee/app/presenters/admin/ai_configuration_presenter.rb index b417c67086fe15..f03e02c3b7137f 100644 --- a/ee/app/presenters/admin/ai_configuration_presenter.rb +++ b/ee/app/presenters/admin/ai_configuration_presenter.rb @@ -10,6 +10,7 @@ class AiConfigurationPresenter :duo_foundational_flows_availability, :duo_chat_expiration_column, :duo_chat_expiration_days, + :duo_sast_fp_detection_availability, :enabled_expanded_logging, :gitlab_dedicated_instance?, :instance_level_ai_beta_features_enabled, @@ -39,6 +40,7 @@ def settings duo_chat_expiration_days: duo_chat_expiration_days, duo_core_features_enabled: duo_core_features_enabled?, duo_pro_visible: active_duo_add_ons_exist?, + duo_sast_fp_detection_availability: duo_sast_fp_detection_availability, enabled_expanded_logging: enabled_expanded_logging, experiment_features_enabled: instance_level_ai_beta_features_enabled, on_general_settings_page: false, diff --git a/ee/app/services/ai/cascade_duo_settings_service.rb b/ee/app/services/ai/cascade_duo_settings_service.rb index 1c90909fd5d0cd..940f380af470aa 100644 --- a/ee/app/services/ai/cascade_duo_settings_service.rb +++ b/ee/app/services/ai/cascade_duo_settings_service.rb @@ -3,7 +3,7 @@ module Ai class CascadeDuoSettingsService DUO_SETTINGS = %w[duo_features_enabled duo_remote_flows_enabled auto_duo_code_review_enabled - duo_foundational_flows_enabled].freeze + duo_foundational_flows_enabled duo_sast_fp_detection_enabled].freeze def initialize(setting_attributes) @setting_attributes = setting_attributes.stringify_keys diff --git a/ee/app/services/ee/application_settings/update_service.rb b/ee/app/services/ee/application_settings/update_service.rb index a736f7a7dfe511..42ea9a844d67a9 100644 --- a/ee/app/services/ee/application_settings/update_service.rb +++ b/ee/app/services/ee/application_settings/update_service.rb @@ -97,7 +97,7 @@ def filter_auto_duo_code_review_param def cascade_duo_features_settings previous_changes = application_setting.previous_changes cascading_ai_settings = [:duo_features_enabled, :duo_remote_flows_enabled, :auto_duo_code_review_enabled, - :duo_foundational_flows_enabled] + :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled] # Collect all changed AI settings and their values changed_ai_settings = cascading_ai_settings.filter_map do |setting| diff --git a/ee/app/services/ee/groups/update_service.rb b/ee/app/services/ee/groups/update_service.rb index ec7af544e91097..f9b009e63686fb 100644 --- a/ee/app/services/ee/groups/update_service.rb +++ b/ee/app/services/ee/groups/update_service.rb @@ -167,7 +167,7 @@ def update_cascading_settings previous_changes = group.namespace_settings.previous_changes cascading_ai_settings = [:duo_features_enabled, :duo_remote_flows_enabled, :auto_duo_code_review_enabled, - :duo_foundational_flows_enabled] + :duo_foundational_flows_enabled, :duo_sast_fp_detection_enabled] # Collect all changed AI settings and their values changed_ai_settings = cascading_ai_settings.filter_map do |setting| if previous_changes.include?(setting) diff --git a/ee/app/services/ee/namespace_settings/assign_attributes_service.rb b/ee/app/services/ee/namespace_settings/assign_attributes_service.rb index ebbca91b36a856..374282654297cd 100644 --- a/ee/app/services/ee/namespace_settings/assign_attributes_service.rb +++ b/ee/app/services/ee/namespace_settings/assign_attributes_service.rb @@ -47,6 +47,14 @@ def execute param_key: :lock_duo_foundational_flows_enabled, user_policy: :admin_group ) + validate_settings_param_for_admin( + param_key: :duo_sast_fp_detection_enabled, + user_policy: :admin_group + ) + validate_settings_param_for_admin( + param_key: :lock_duo_sast_fp_detection_enabled, + user_policy: :admin_group + ) validate_settings_param_for_root_group( param_key: :disable_invite_members, user_policy: :owner_access diff --git a/ee/lib/ee/api/entities/project.rb b/ee/lib/ee/api/entities/project.rb index 8d0cbebb8c7248..2f5c0c78a8ca33 100644 --- a/ee/lib/ee/api/entities/project.rb +++ b/ee/lib/ee/api/entities/project.rb @@ -67,6 +67,7 @@ def preload_relation(projects_relation, options = {}) expose :auto_duo_code_review_enabled, if: ->(project, _) { project.namespace.has_active_add_on_purchase?(:duo_enterprise) } expose :duo_remote_flows_enabled, if: ->(_, _) { ::Ai::DuoWorkflow.enabled? } expose :duo_foundational_flows_enabled, if: ->(_, _) { ::Ai::DuoWorkflow.enabled? } + expose :duo_sast_fp_detection_enabled, if: ->(project, _) { project.licensed_feature_available?(:ai_features) && ::Feature.enabled?(:ai_experiment_sast_fp_detection, project) } expose :web_based_commit_signing_enabled, if: ->(project, options) do ::Gitlab::Saas.feature_available?(:repositories_web_based_commit_signing) && Ability.allowed?(options[:current_user], :admin_project, project) diff --git a/ee/lib/ee/api/groups.rb b/ee/lib/ee/api/groups.rb index 8dc7b705f8b903..9dbbead74cb680 100644 --- a/ee/lib/ee/api/groups.rb +++ b/ee/lib/ee/api/groups.rb @@ -63,6 +63,11 @@ def update_group(group) params.delete(:service_access_tokens_expiration_enforced) unless group.root? && can?(current_user, :admin_service_accounts, group) + unless group.licensed_feature_available?(:ai_features) && ::Feature.enabled?( + :ai_experiment_sast_fp_detection, group) + params.delete(:duo_sast_fp_detection_availability) + end + unless group.unique_project_download_limit_enabled? %i[ unique_project_download_limit diff --git a/ee/lib/ee/api/helpers/groups_helpers.rb b/ee/lib/ee/api/helpers/groups_helpers.rb index 67dbb4d4963326..8023f9e34c75f8 100644 --- a/ee/lib/ee/api/helpers/groups_helpers.rb +++ b/ee/lib/ee/api/helpers/groups_helpers.rb @@ -17,6 +17,7 @@ module GroupsHelpers optional :duo_availability, type: String, values: %w[default_on default_off never_on], desc: 'Duo availability. One of `default_on`, `default_off` or `never_on`' optional :duo_remote_flows_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab Duo remote flows for this group' optional :duo_foundational_flows_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab foundational Duo flows for this group' + optional :duo_sast_fp_detection_availability, type: ::Grape::API::Boolean, desc: 'Enable GitLab Duo SAST false position detection for this group' optional :amazon_q_auto_review_enabled, type: ::Grape::API::Boolean, desc: 'Enable Amazon Q auto review for merge request' optional :experiment_features_enabled, type: ::Grape::API::Boolean, desc: 'Enable experiment features for this group' optional :model_prompt_cache_enabled, type: ::Grape::API::Boolean, desc: 'Enable model prompt cache for this group' diff --git a/ee/lib/ee/api/helpers/projects_helpers.rb b/ee/lib/ee/api/helpers/projects_helpers.rb index 338355b225e38a..d1dcc00b5f4d16 100644 --- a/ee/lib/ee/api/helpers/projects_helpers.rb +++ b/ee/lib/ee/api/helpers/projects_helpers.rb @@ -25,6 +25,7 @@ module ProjectsHelpers optional :prevent_merge_without_jira_issue, type: Grape::API::Boolean, desc: 'Require an associated issue from Jira' optional :auto_duo_code_review_enabled, type: Grape::API::Boolean, desc: 'Enable automatic reviews by GitLab Duo on merge requests' optional :duo_remote_flows_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo remote flows for this project' + optional :duo_sast_fp_detection_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo SAST false positive detection for this project' optional :spp_repository_pipeline_access, type: Grape::API::Boolean, desc: 'Grant read-only access to security policy configurations for enforcement in linked CI/CD projects' end @@ -53,6 +54,7 @@ module ProjectsHelpers type: ::Grape::API::Boolean, desc: 'Enable web based commit signing for this project' optional :duo_remote_flows_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo remote flows for this project' + optional :duo_sast_fp_detection_enabled, type: Grape::API::Boolean, desc: 'Enable GitLab Duo SAST false positive detection for this project' optional :spp_repository_pipeline_access, type: Grape::API::Boolean, desc: 'Grant read-only access to security policy configurations for enforcement in linked CI/CD projects' end @@ -73,6 +75,7 @@ def update_params_at_least_one_of super.concat [ :auto_duo_code_review_enabled, :duo_remote_flows_enabled, + :duo_sast_fp_detection_enabled, :allow_pipeline_trigger_approve_deployment, :only_allow_merge_if_all_status_checks_passed, :approvals_before_merge, @@ -117,6 +120,10 @@ def filter_attributes_using_license!(attrs) attrs.delete(:auto_duo_code_review_enabled) unless ::License.feature_available?(:review_merge_request) attrs.delete(:duo_remote_flows_enabled) unless License.feature_available?(:ai_workflows) + unless License.feature_available?(:ai_features) && ::Feature.enabled?(:ai_experiment_sast_fp_detection, current_user) + attrs.delete(:duo_sast_fp_detection_enabled) + end + return if ::License.feature_available?(:security_orchestration_policies) attrs.delete(:spp_repository_pipeline_access) diff --git a/ee/spec/controllers/ee/projects_controller_spec.rb b/ee/spec/controllers/ee/projects_controller_spec.rb index 879f3a77e3b75c..5ea12e09fdbd69 100644 --- a/ee/spec/controllers/ee/projects_controller_spec.rb +++ b/ee/spec/controllers/ee/projects_controller_spec.rb @@ -914,7 +914,50 @@ it 'updates duo_foundational_flows_enabled' do expect { request } .to change { project.reload.project_setting.duo_foundational_flows_enabled } - .from(false).to(true) + .from(false).to(true) + end + end + end + end + + context 'when duo_sast_fp_detection_enabled param is specified' do + let(:params) { { project_setting_attributes: { duo_sast_fp_detection_enabled: true } } } + + let(:request) do + put :update, params: { namespace_id: project.namespace, id: project, project: params } + end + + it 'updates duo_sast_fp_detection_enabled' do + project.project_setting.duo_sast_fp_detection_enabled = false + project.project_setting.save! + + request + + expect(project.reload.project_setting.duo_sast_fp_detection_enabled).to eq(true) + end + + context 'when duo sast fp detection is locked by the ancestor' do + before do + project.project_setting.duo_sast_fp_detection_enabled = false + project.project_setting.save! + + project.namespace.namespace_settings.lock_duo_sast_fp_detection_enabled = true + project.namespace.namespace_settings.duo_sast_fp_detection_enabled = false + project.namespace.namespace_settings.save! + end + + it 'does not update duo sast fp detection' do + expect { request }.not_to change { project.reload.project_setting.duo_sast_fp_detection_enabled }.from(false) + end + + context 'with more params passed' do + let(:params) do + { project_setting_attributes: { duo_sast_fp_detection_enabled: true }, description: 'Settings test' } + end + + it 'does not update duo sast fp detection, but updates other attributes' do + expect { request }.not_to change { project.reload.project_setting.duo_sast_fp_detection_enabled }.from(false) + expect(project.description).to eq('Settings test') end end end diff --git a/ee/spec/helpers/ee/groups/settings_helper_spec.rb b/ee/spec/helpers/ee/groups/settings_helper_spec.rb index c863cb5d0f6284..d2033a5ac47141 100644 --- a/ee/spec/helpers/ee/groups/settings_helper_spec.rb +++ b/ee/spec/helpers/ee/groups/settings_helper_spec.rb @@ -93,6 +93,9 @@ duo_foundational_flows_cascading_settings: "{\"locked_by_application_setting\":false,\"locked_by_ancestor\":false}", duo_foundational_flows_availability: group.namespace_settings.duo_foundational_flows_availability.to_s, + duo_sast_fp_detection_cascading_settings: "{\"locked_by_application_setting\":false," \ + "\"locked_by_ancestor\":false}", + duo_sast_fp_detection_availability: group.namespace_settings.duo_sast_fp_detection_availability.to_s, duo_core_features_enabled: group.namespace_settings.duo_core_features_enabled.to_s, are_duo_settings_locked: group.namespace_settings.duo_features_enabled_locked?.to_s, experiment_features_enabled: group.namespace_settings.experiment_features_enabled.to_s, diff --git a/ee/spec/helpers/projects_helper_spec.rb b/ee/spec/helpers/projects_helper_spec.rb index c2de79ece5a944..a45193451d62bd 100644 --- a/ee/spec/helpers/projects_helper_spec.rb +++ b/ee/spec/helpers/projects_helper_spec.rb @@ -702,6 +702,7 @@ duoFeaturesLocked: false, initialDuoRemoteFlowsAvailability: true, initialDuoFoundationalFlowsAvailability: false, + initialDuoSastFpDetectionEnabled: true, experimentFeaturesEnabled: false } end diff --git a/ee/spec/lib/ee/api/entities/project_spec.rb b/ee/spec/lib/ee/api/entities/project_spec.rb index 6fce576fad0085..644d4f0c97d99a 100644 --- a/ee/spec/lib/ee/api/entities/project_spec.rb +++ b/ee/spec/lib/ee/api/entities/project_spec.rb @@ -204,6 +204,43 @@ def mock_available end end + describe 'duo_sast_fp_detection_enabled' do + context 'when project is licensed to use ai_features and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'returns a boolean value' do + expect(subject[:duo_sast_fp_detection_enabled]).to be_in([true, false]) + end + end + + context 'when project is licensed to use ai_features but feature flag is disabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: false) + end + + it 'returns nil' do + expect(subject[:duo_sast_fp_detection_enabled]).to be_nil + end + end + + context 'when project is not licensed to use ai_features' do + let(:current_user) { developer } + + before do + stub_licensed_features(ai_features: false) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'returns nil' do + expect(subject[:duo_sast_fp_detection_enabled]).to be_nil + end + end + end + describe 'web_based_commit_signing_enabled' do before do stub_saas_features(repositories_web_based_commit_signing: repositories_web_based_commit_signing) diff --git a/ee/spec/models/application_setting_spec.rb b/ee/spec/models/application_setting_spec.rb index fd2035c5b6d00c..166f3259384c80 100644 --- a/ee/spec/models/application_setting_spec.rb +++ b/ee/spec/models/application_setting_spec.rb @@ -718,6 +718,27 @@ end end + describe '#duo_sast_fp_detection_availability=' do + using RSpec::Parameterized::TableSyntax + + where(:duo_sast_fp_detection_availability, :duo_sast_fp_detection_enabled_expectation, + :lock_duo_sast_fp_detection_enabled_expectation) do + true | true | false + false | false | true + end + + with_them do + before do + setting.duo_sast_fp_detection_availability = duo_sast_fp_detection_availability + end + + it 'returns the expected response' do + expect(setting.duo_sast_fp_detection_enabled).to be duo_sast_fp_detection_enabled_expectation + expect(setting.lock_duo_sast_fp_detection_enabled).to be lock_duo_sast_fp_detection_enabled_expectation + end + end + end + describe '#enabled_expanded_logging' do it "updates ::Ai::Settings.instance.enabled_instance_verbose_ai_logs" do ::Ai::Setting.instance.update!(enabled_instance_verbose_ai_logs: false) diff --git a/ee/spec/models/ee/project_setting_spec.rb b/ee/spec/models/ee/project_setting_spec.rb index 62db86f32e97d3..6fe35fe1fc2e04 100644 --- a/ee/spec/models/ee/project_setting_spec.rb +++ b/ee/spec/models/ee/project_setting_spec.rb @@ -174,4 +174,9 @@ it_behaves_like 'a cascading project setting boolean attribute', settings_attribute_name: :duo_foundational_flows_enabled end + + describe '#duo_sast_fp_detection_enabled' do + it_behaves_like 'a cascading project setting boolean attribute', + settings_attribute_name: :duo_sast_fp_detection_enabled + end end diff --git a/ee/spec/models/ee/vulnerability_spec.rb b/ee/spec/models/ee/vulnerability_spec.rb index aba17cd48ecc06..5667df5b1c9855 100644 --- a/ee/spec/models/ee/vulnerability_spec.rb +++ b/ee/spec/models/ee/vulnerability_spec.rb @@ -1539,15 +1539,34 @@ context 'when feature flag is enabled' do context 'when vulnerability is of type sast' do - it 'triggers false positive detection workflow' do - expect_next_instance_of(::Vulnerability) do |instance| - expect(instance).to receive(:run_after_commit_or_now).and_yield + context 'when duo_sast_fp_detection_enabled is true' do + before do + project.update!(duo_sast_fp_detection_enabled: true) end - expect(::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker) - .to receive(:perform_async) - .with(anything) - create(:vulnerability, :sast, author: user, project: project) + it 'triggers false positive detection workflow' do + expect_next_instance_of(::Vulnerability) do |instance| + expect(instance).to receive(:run_after_commit_or_now).and_yield + end + expect(::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker) + .to receive(:perform_async) + .with(anything) + + create(:vulnerability, :sast, author: user, project: project) + end + end + + context 'when duo_sast_fp_detection_enabled is false' do + before do + project.update!(duo_sast_fp_detection_enabled: false) + end + + it 'does not trigger false positive detection workflow' do + expect(::Vulnerabilities::TriggerFalsePositiveDetectionWorkflowWorker) + .not_to receive(:perform_async) + + create(:vulnerability, :sast, author: user, project: project) + end end end diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb index 15c0a7274cc145..412634795d8088 100644 --- a/ee/spec/models/namespace_setting_spec.rb +++ b/ee/spec/models/namespace_setting_spec.rb @@ -654,6 +654,26 @@ end end + describe '#duo_sast_fp_detection_availability=' do + using RSpec::Parameterized::TableSyntax + + where(:duo_sast_fp_detection_availability, :duo_sast_fp_detection_enabled_expectation, :lock_duo_sast_fp_detection_enabled_expectation) do + true | true | false + false | false | true + end + + with_them do + before do + setting.duo_sast_fp_detection_availability = duo_sast_fp_detection_availability + end + + it 'returns the expected response' do + expect(setting.duo_sast_fp_detection_enabled).to be duo_sast_fp_detection_enabled_expectation + expect(setting.lock_duo_sast_fp_detection_enabled).to be lock_duo_sast_fp_detection_enabled_expectation + end + end + end + describe 'validating new_user_signup_cap' do using RSpec::Parameterized::TableSyntax diff --git a/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb b/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb index 1da955bbb05751..85418502eb8324 100644 --- a/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb +++ b/ee/spec/presenters/admin/ai_configuration_presenter_spec.rb @@ -12,6 +12,7 @@ duo_availability: 'default_off', duo_remote_flows_availability: true, duo_foundational_flows_availability: false, + duo_sast_fp_detection_availability: true, duo_chat_expiration_column: 'last_updated_at', duo_chat_expiration_days: '30', enabled_expanded_logging: true, @@ -73,6 +74,7 @@ duo_availability: 'default_off', duo_remote_flows_availability: 'true', duo_foundational_flows_availability: 'false', + duo_sast_fp_detection_availability: 'true', duo_chat_expiration_column: 'last_updated_at', duo_chat_expiration_days: '30', duo_core_features_enabled: 'true', diff --git a/ee/spec/requests/api/groups_spec.rb b/ee/spec/requests/api/groups_spec.rb index 76f6bc32b0946a..4f2941f00614ed 100644 --- a/ee/spec/requests/api/groups_spec.rb +++ b/ee/spec/requests/api/groups_spec.rb @@ -846,6 +846,54 @@ end end + context 'duo_sast_fp_detection_availability' do + context 'when licence is available and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'updates duo_sast_fp_detection_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_fp_detection_availability: false } + end.to change { group.reload.namespace_settings.duo_sast_fp_detection_enabled }.from(true).to(false) + .and change { group.reload.namespace_settings.lock_duo_sast_fp_detection_enabled }.from(false).to(true) + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when licence is available but feature flag is disabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: false) + end + + it 'does not update duo_sast_fp_detection_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_fp_detection_availability: false } + end.not_to change { group.reload.namespace_settings.duo_sast_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when licence is not available' do + before do + stub_licensed_features(ai_features: false) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'does not update duo_sast_fp_detection_enabled field of namespace settings' do + expect do + put api("/groups/#{group.id}", user), params: { duo_sast_fp_detection_availability: false } + end.not_to change { group.reload.namespace_settings.duo_sast_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + context 'auto_duo_code_review_enabled', :saas do using RSpec::Parameterized::TableSyntax diff --git a/ee/spec/requests/api/projects_spec.rb b/ee/spec/requests/api/projects_spec.rb index f7ccee13f0dd2d..980739efdff0b4 100644 --- a/ee/spec/requests/api/projects_spec.rb +++ b/ee/spec/requests/api/projects_spec.rb @@ -2186,6 +2186,51 @@ def decode_cursor(cursor) end end + context 'when setting duo_sast_fp_detection_enabled' do + let(:project_params) { { duo_sast_fp_detection_enabled: false } } + + context 'when licence is available and feature flag is enabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'updates the value' do + expect { subject }.to change { project.reload.duo_sast_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_fp_detection_enabled']).to eq false + end + end + + context 'when licence is available but feature flag is disabled' do + before do + stub_licensed_features(ai_features: true) + stub_feature_flags(ai_experiment_sast_fp_detection: false) + end + + it 'does not update the value and does not expose it in response' do + expect { subject }.not_to change { project.reload.duo_sast_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_fp_detection_enabled']).to be_nil + end + end + + context 'when licence is not available' do + before do + stub_feature_flags(ai_experiment_sast_fp_detection: true) + end + + it 'does not update the value and does not expose it in response' do + expect { subject }.not_to change { project.reload.duo_sast_fp_detection_enabled } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['duo_sast_fp_detection_enabled']).to be_nil + end + end + end + context 'updating web_based_commit_signing_enabled' do using RSpec::Parameterized::TableSyntax diff --git a/ee/spec/services/application_settings/update_service_spec.rb b/ee/spec/services/application_settings/update_service_spec.rb index 774a11a9d84d86..17758e0660cfba 100644 --- a/ee/spec/services/application_settings/update_service_spec.rb +++ b/ee/spec/services/application_settings/update_service_spec.rb @@ -375,6 +375,14 @@ it_behaves_like 'when updating duo settings', :duo_foundational_flows_enabled, true end + context 'when updating duo_sast_fp_detection_enabled' do + before do + setting.update!(duo_sast_fp_detection_enabled: true) + end + + it_behaves_like 'when updating duo settings', :duo_sast_fp_detection_enabled, false + end + context 'when updating auto_duo_code_review_enabled' do let(:params) { { auto_duo_code_review_enabled: true } } let(:service) { described_class.new(setting, user, params) } diff --git a/ee/spec/services/groups/update_service_spec.rb b/ee/spec/services/groups/update_service_spec.rb index 679eebe7a972ef..f68991ef556b52 100644 --- a/ee/spec/services/groups/update_service_spec.rb +++ b/ee/spec/services/groups/update_service_spec.rb @@ -637,6 +637,10 @@ def update_file_template_project_id(id) it_behaves_like 'when updating duo settings', :duo_foundational_flows_enabled, false end + context 'when updating duo_sast_fp_detection_enabled' do + it_behaves_like 'when updating duo settings', :duo_sast_fp_detection_enabled, false + end + context 'when updating lock_duo_features_enabled' do let_it_be_with_reload(:user) { create(:user) } let_it_be_with_reload(:group) { create(:group, :public) } -- GitLab From 958616ab9d1c3429b7e05e8a4caaf8493ef01b95 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Fri, 14 Nov 2025 14:36:26 +0200 Subject: [PATCH 2/3] Apply 3 suggestion(s) to 2 file(s) Co-authored-by: Hitesh Raghuvanshi --- doc/api/openapi/openapi_v2.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index ffe7c60a0ba857..2ddedd13b44f87 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -48075,7 +48075,7 @@ definitions: description: Enable GitLab foundational Duo flows for this group duo_sast_fp_detection_availability: type: boolean - description: Enable GitLab Duo SAST false position detection for this group + description: Enable GitLab Duo SAST false positive detection for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -48284,7 +48284,7 @@ definitions: description: Enable GitLab foundational Duo flows for this group duo_sast_fp_detection_availability: type: boolean - description: Enable GitLab Duo SAST false position detection for this group + description: Enable GitLab Duo SAST false positive detection for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request -- GitLab From d36dff00489c7d601aa0a42cb24d1f9c4eb51778 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Fri, 14 Nov 2025 15:01:10 +0200 Subject: [PATCH 3/3] Add missing delegations * improve --- doc/api/openapi/openapi_v2.yaml | 4 +- .../controllers/concerns/ee/groups/params.rb | 3 +- ee/app/helpers/ee/groups/settings_helper.rb | 61 +++++++++++-------- ee/app/models/ee/namespace.rb | 1 + .../namespace_setting_changes_auditor_spec.rb | 3 +- ee/spec/models/ee/namespace_spec.rb | 6 ++ spec/requests/api/project_attributes.yml | 3 +- 7 files changed, 49 insertions(+), 32 deletions(-) diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index 2ddedd13b44f87..ffe7c60a0ba857 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -48075,7 +48075,7 @@ definitions: description: Enable GitLab foundational Duo flows for this group duo_sast_fp_detection_availability: type: boolean - description: Enable GitLab Duo SAST false positive detection for this group + description: Enable GitLab Duo SAST false position detection for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request @@ -48284,7 +48284,7 @@ definitions: description: Enable GitLab foundational Duo flows for this group duo_sast_fp_detection_availability: type: boolean - description: Enable GitLab Duo SAST false positive detection for this group + description: Enable GitLab Duo SAST false position detection for this group amazon_q_auto_review_enabled: type: boolean description: Enable Amazon Q auto review for merge request diff --git a/ee/app/controllers/concerns/ee/groups/params.rb b/ee/app/controllers/concerns/ee/groups/params.rb index 822deab1c4eea0..48b571c26abee5 100644 --- a/ee/app/controllers/concerns/ee/groups/params.rb +++ b/ee/app/controllers/concerns/ee/groups/params.rb @@ -70,7 +70,8 @@ def group_params_ee if licensed_ai_features_available? params_ee.push(%i[duo_features_enabled duo_core_features_enabled lock_duo_features_enabled duo_availability duo_remote_flows_availability prompt_cache_enabled duo_remote_flows_enabled - lock_duo_remote_flows_enabled duo_foundational_flows_enabled lock_duo_foundational_flows_enabled]) + lock_duo_remote_flows_enabled duo_foundational_flows_enabled lock_duo_foundational_flows_enabled + duo_sast_fp_detection_enabled lock_duo_sast_fp_detection_enabled]) end params_ee << :disable_personal_access_tokens if current_group&.disable_personal_access_tokens_available? diff --git a/ee/app/helpers/ee/groups/settings_helper.rb b/ee/app/helpers/ee/groups/settings_helper.rb index d5220535d522f7..c7c3962553fd7e 100644 --- a/ee/app/helpers/ee/groups/settings_helper.rb +++ b/ee/app/helpers/ee/groups/settings_helper.rb @@ -56,15 +56,41 @@ def group_ai_configuration_settings_helper_data end def group_ai_settings_helper_data - duo_availability_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_features_enabled, @group, method(:edit_group_path))[:tooltip_data] - duo_remote_flows_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_remote_flows_enabled, @group, method(:edit_group_path))[:tooltip_data] - duo_foundational_flows_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_foundational_flows_enabled, @group, method(:edit_group_path))[:tooltip_data] - duo_sast_fp_detection_cascading_settings = cascading_namespace_settings_tooltip_data(:duo_sast_fp_detection_enabled, @group, method(:edit_group_path))[:tooltip_data] + duo_cascading_settings_data.merge(duo_feature_settings_data) + end + + def group_amazon_q_settings_view_model_data + { + group_id: @group.id.to_s, + init_availability: @group.namespace_settings.duo_availability.to_s, + init_auto_review_enabled: @group.amazon_q_integration&.auto_review_enabled.present?, + are_duo_settings_locked: @group.namespace_settings.duo_features_enabled_locked?, + duo_availability_cascading_settings: cascading_namespace_settings_tooltip_raw_data(:duo_features_enabled, @group, method(:edit_group_path)) + } + end + + def group_amazon_q_settings_view_model_json + ::Gitlab::Json.generate(group_amazon_q_settings_view_model_data.deep_transform_keys { |k| k.to_s.camelize(:lower) }) + end + + def seat_control_disabled_help_text + _("Restricted access and user cap cannot be turned on. The group or one of its subgroups or projects is shared externally.") + end + + private + + def duo_cascading_settings_data + { + duo_availability_cascading_settings: cascading_tooltip_data(:duo_features_enabled), + duo_remote_flows_cascading_settings: cascading_tooltip_data(:duo_remote_flows_enabled), + duo_foundational_flows_cascading_settings: cascading_tooltip_data(:duo_foundational_flows_enabled), + duo_sast_fp_detection_cascading_settings: cascading_tooltip_data(:duo_sast_fp_detection_enabled) + } + end + + def duo_feature_settings_data { - duo_availability_cascading_settings: duo_availability_cascading_settings, duo_availability: @group.namespace_settings.duo_availability.to_s, - duo_remote_flows_cascading_settings: duo_remote_flows_cascading_settings, - duo_sast_fp_detection_cascading_settings: duo_sast_fp_detection_cascading_settings, are_duo_settings_locked: @group.namespace_settings.duo_features_enabled_locked?.to_s, experiment_features_enabled: @group.namespace_settings.experiment_features_enabled.to_s, duo_core_features_enabled: @group.namespace_settings.duo_core_features_enabled.to_s, @@ -79,33 +105,16 @@ def group_ai_settings_helper_data duo_remote_flows_availability: @group.namespace_settings.duo_remote_flows_availability.to_s, duo_sast_fp_detection_availability: @group.namespace_settings.duo_sast_fp_detection_availability.to_s, foundational_agents_default_enabled: @group.foundational_agents_default_enabled.to_s, - duo_foundational_flows_cascading_settings: duo_foundational_flows_cascading_settings, duo_foundational_flows_availability: @group.namespace_settings.duo_foundational_flows_availability.to_s, is_saas: saas?.to_s, show_foundational_agents_availability: show_foundational_agents_availability?.to_s } end - def group_amazon_q_settings_view_model_data - { - group_id: @group.id.to_s, - init_availability: @group.namespace_settings.duo_availability.to_s, - init_auto_review_enabled: @group.amazon_q_integration&.auto_review_enabled.present?, - are_duo_settings_locked: @group.namespace_settings.duo_features_enabled_locked?, - duo_availability_cascading_settings: cascading_namespace_settings_tooltip_raw_data(:duo_features_enabled, @group, method(:edit_group_path)) - } - end - - def group_amazon_q_settings_view_model_json - ::Gitlab::Json.generate(group_amazon_q_settings_view_model_data.deep_transform_keys { |k| k.to_s.camelize(:lower) }) + def cascading_tooltip_data(setting_key) + cascading_namespace_settings_tooltip_data(setting_key, @group, method(:edit_group_path))[:tooltip_data] end - def seat_control_disabled_help_text - _("Restricted access and user cap cannot be turned on. The group or one of its subgroups or projects is shared externally.") - end - - private - def show_foundational_agents_availability? ::Feature.enabled?(:duo_foundational_agents_availability, @group) && saas? && @group.root? end diff --git a/ee/app/models/ee/namespace.rb b/ee/app/models/ee/namespace.rb index 0112035c48ff9e..e8e52273176b46 100644 --- a/ee/app/models/ee/namespace.rb +++ b/ee/app/models/ee/namespace.rb @@ -196,6 +196,7 @@ module Namespace :auto_duo_code_review_enabled, :auto_duo_code_review_enabled=, :duo_remote_flows_enabled, :lock_duo_remote_flows_enabled, :duo_foundational_flows_enabled, :lock_duo_foundational_flows_enabled, + :duo_sast_fp_detection_enabled, :lock_duo_sast_fp_detection_enabled, to: :namespace_settings, allow_nil: true delegate :duo_agent_platform_request_count, :duo_default_on?, :duo_default_off?, :experiment_features_enabled?, to: :namespace_settings diff --git a/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb b/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb index 1202dd649f52a9..60c8b412588a80 100644 --- a/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb +++ b/ee/spec/lib/namespaces/namespace_setting_changes_auditor_spec.rb @@ -130,7 +130,8 @@ web_based_commit_signing_enabled allow_enterprise_bypass_placeholder_confirmation enterprise_bypass_expires_at allow_personal_snippets lock_auto_duo_code_review_enabled auto_duo_code_review_enabled lock_duo_remote_flows_enabled duo_remote_flows_enabled duo_agent_platform_request_count - lock_duo_foundational_flows_enabled duo_foundational_flows_enabled] + lock_duo_foundational_flows_enabled duo_foundational_flows_enabled + lock_duo_sast_fp_detection_enabled duo_sast_fp_detection_enabled] columns_to_audit = Namespaces::NamespaceSettingChangesAuditor::EVENT_NAME_PER_COLUMN.keys.map(&:to_s) diff --git a/ee/spec/models/ee/namespace_spec.rb b/ee/spec/models/ee/namespace_spec.rb index f7d858e5fc9a39..ac50834a0a60b8 100644 --- a/ee/spec/models/ee/namespace_spec.rb +++ b/ee/spec/models/ee/namespace_spec.rb @@ -50,6 +50,12 @@ it { is_expected.to delegate_method(:duo_features_enabled).to(:namespace_settings) } it { is_expected.to delegate_method(:lock_duo_features_enabled).to(:namespace_settings) } it { is_expected.to delegate_method(:duo_availability).to(:namespace_settings) } + it { is_expected.to delegate_method(:auto_duo_code_review_enabled).to(:namespace_settings).allow_nil } + it { is_expected.to delegate_method(:auto_duo_code_review_enabled=).to(:namespace_settings).with_arguments(:args).allow_nil } + it { is_expected.to delegate_method(:duo_remote_flows_enabled).to(:namespace_settings).allow_nil } + it { is_expected.to delegate_method(:lock_duo_remote_flows_enabled).to(:namespace_settings).allow_nil } + it { is_expected.to delegate_method(:duo_sast_fp_detection_enabled).to(:namespace_settings).allow_nil } + it { is_expected.to delegate_method(:lock_duo_sast_fp_detection_enabled).to(:namespace_settings).allow_nil } it { is_expected.to delegate_method(:duo_default_on?).to(:namespace_settings) } it { is_expected.to delegate_method(:duo_default_off?).to(:namespace_settings) } it { is_expected.to delegate_method(:experiment_features_enabled?).to(:namespace_settings) } diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index 0422ce34b1cfca..1f7c04118489d1 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -120,7 +120,6 @@ ci_cd_settings: computed_attributes: - restrict_user_defined_variables - build_import_state: # import_state unexposed_attributes: - id @@ -205,7 +204,7 @@ project_setting: - duo_context_exclusion_settings - duo_remote_flows_enabled - duo_foundational_flows_enabled - + - duo_sast_fp_detection_enabled build_service_desk_setting: # service_desk_setting unexposed_attributes: - project_id -- GitLab