diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
index e4a3f815396f4de8cf81424522be23572018f4d6..237aff1f052426118841bee7758b39772e17f67b 100644
--- a/app/graphql/mutations/security/ci_configuration/configure_sast.rb
+++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
@@ -7,6 +7,11 @@ class ConfigureSast < BaseMutation
         include FindsProject
 
         graphql_name 'ConfigureSast'
+        description <<~DESC
+          Configure SAST for a project by enabling SAST in a new or modified
+          `.gitlab-ci.yml` file in a new branch. The new branch and a URL to
+          create a Merge Request are a part of the response.
+        DESC
 
         argument :project_path, GraphQL::ID_TYPE,
           required: true,
@@ -16,12 +21,12 @@ class ConfigureSast < BaseMutation
           required: true,
           description: 'SAST CI configuration for the project.'
 
-        field :status, GraphQL::STRING_TYPE, null: false,
-          description: 'Status of creating the commit for the supplied SAST CI configuration.'
-
         field :success_path, GraphQL::STRING_TYPE, null: true,
           description: 'Redirect path to use when the response is successful.'
 
+        field :branch, GraphQL::STRING_TYPE, null: true,
+          description: 'Branch that has the new/modified `.gitlab-ci.yml` file.'
+
         authorize :push_code
 
         def resolve(project_path:, configuration:)
@@ -35,9 +40,9 @@ def resolve(project_path:, configuration:)
 
         def prepare_response(result)
           {
-            status: result[:status],
-            success_path: result[:success_path],
-            errors: Array(result[:errors])
+            branch: result.payload[:branch],
+            success_path: result.payload[:success_path],
+            errors: result.errors
           }
         end
       end
diff --git a/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb b/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32ad670edaa9745361a6511a7066b83f85a369ed
--- /dev/null
+++ b/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Mutations
+  module Security
+    module CiConfiguration
+      class ConfigureSecretDetection < BaseMutation
+        include FindsProject
+
+        graphql_name 'ConfigureSecretDetection'
+        description <<~DESC
+          Configure Secret Detection for a project by enabling Secret Detection
+          in a new or modified `.gitlab-ci.yml` file in a new branch. The new
+          branch and a URL to create a Merge Request are a part of the
+          response.
+        DESC
+
+        argument :project_path, GraphQL::ID_TYPE,
+          required: true,
+          description: 'Full path of the project.'
+
+        field :success_path, GraphQL::STRING_TYPE, null: true,
+          description: 'Redirect path to use when the response is successful.'
+
+        field :branch, GraphQL::STRING_TYPE, null: true,
+          description: 'Branch that has the new/modified `.gitlab-ci.yml` file.'
+
+        authorize :push_code
+
+        def resolve(project_path:)
+          project = authorized_find!(project_path)
+
+          result = ::Security::CiConfiguration::SecretDetectionCreateService.new(project, current_user).execute
+          prepare_response(result)
+        end
+
+        private
+
+        def prepare_response(result)
+          {
+            branch: result.payload[:branch],
+            success_path: result.payload[:success_path],
+            errors: result.errors
+          }
+        end
+      end
+    end
+  end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 5a9c7b32debdf1d382a74fd2d0f0922b831ecfcd..a820ef9471945b462c53e3aaaf3d0eabaec624f4 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -16,6 +16,7 @@ class MutationType < BaseObject
     mount_mutation Mutations::AlertManagement::HttpIntegration::ResetToken
     mount_mutation Mutations::AlertManagement::HttpIntegration::Destroy
     mount_mutation Mutations::Security::CiConfiguration::ConfigureSast
+    mount_mutation Mutations::Security::CiConfiguration::ConfigureSecretDetection
     mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create
     mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update
     mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken
diff --git a/app/services/security/ci_configuration/base_create_service.rb b/app/services/security/ci_configuration/base_create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..adb45244adbea019927b1a07313af0dc19a3c7ee
--- /dev/null
+++ b/app/services/security/ci_configuration/base_create_service.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Security
+  module CiConfiguration
+    class BaseCreateService
+      attr_reader :branch_name, :current_user, :project
+
+      def initialize(project, current_user)
+        @project = project
+        @current_user = current_user
+        @branch_name = project.repository.next_branch(next_branch)
+      end
+
+      def execute
+        project.repository.add_branch(current_user, branch_name, project.default_branch)
+
+        attributes_for_commit = attributes
+
+        result = ::Files::MultiService.new(project, current_user, attributes_for_commit).execute
+
+        return ServiceResponse.error(message: result[:message]) unless result[:status] == :success
+
+        track_event(attributes_for_commit)
+        ServiceResponse.success(payload: { branch: branch_name, success_path: successful_change_path })
+      rescue Gitlab::Git::PreReceiveError => e
+        ServiceResponse.error(message: e.message)
+      rescue StandardError
+        project.repository.rm_branch(current_user, branch_name) if project.repository.branch_exists?(branch_name)
+        raise
+      end
+
+      private
+
+      def attributes
+        {
+          commit_message: message,
+          branch_name: branch_name,
+          start_branch: branch_name,
+          actions: [action]
+        }
+      end
+
+      def existing_gitlab_ci_content
+        @gitlab_ci_yml ||= project.repository.gitlab_ci_yml_for(project.repository.root_ref_sha)
+        YAML.safe_load(@gitlab_ci_yml) if @gitlab_ci_yml
+      end
+
+      def successful_change_path
+        merge_request_params = { source_branch: branch_name, description: description }
+        Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
+      end
+
+      def track_event(attributes_for_commit)
+        action = attributes_for_commit[:actions].first
+
+        Gitlab::Tracking.event(
+          self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s
+        )
+      end
+    end
+  end
+end
diff --git a/app/services/security/ci_configuration/sast_create_service.rb b/app/services/security/ci_configuration/sast_create_service.rb
index 8fc3b8d078cc7f24f4a8f258af1de1c1bdb61aff..f495cac18f8153ec9b158af445258bc5c4976c89 100644
--- a/app/services/security/ci_configuration/sast_create_service.rb
+++ b/app/services/security/ci_configuration/sast_create_service.rb
@@ -2,64 +2,30 @@
 
 module Security
   module CiConfiguration
-    class SastCreateService < ::BaseService
+    class SastCreateService < ::Security::CiConfiguration::BaseCreateService
+      attr_reader :params
+
       def initialize(project, current_user, params)
-        @project = project
-        @current_user = current_user
+        super(project, current_user)
         @params = params
-        @branch_name = @project.repository.next_branch('set-sast-config')
-      end
-
-      def execute
-        attributes_for_commit = attributes
-        result = ::Files::MultiService.new(@project, @current_user, attributes_for_commit).execute
-
-        if result[:status] == :success
-          result[:success_path] = successful_change_path
-          track_event(attributes_for_commit)
-        else
-          result[:errors] = result[:message]
-        end
-
-        result
-
-      rescue Gitlab::Git::PreReceiveError => e
-        { status: :error, errors: e.message }
       end
 
       private
 
-      def attributes
-        actions = Security::CiConfiguration::SastBuildActions.new(@project.auto_devops_enabled?, @params, existing_gitlab_ci_content).generate
-
-        @project.repository.add_branch(@current_user, @branch_name, @project.default_branch)
-        message = _('Set .gitlab-ci.yml to enable or configure SAST')
-
-        {
-          commit_message: message,
-          branch_name: @branch_name,
-          start_branch: @branch_name,
-          actions: actions
-        }
+      def action
+        Security::CiConfiguration::SastBuildAction.new(project.auto_devops_enabled?, params, existing_gitlab_ci_content).generate
       end
 
-      def existing_gitlab_ci_content
-        gitlab_ci_yml = @project.repository.gitlab_ci_yml_for(@project.repository.root_ref_sha)
-        YAML.safe_load(gitlab_ci_yml) if gitlab_ci_yml
+      def next_branch
+        'set-sast-config'
       end
 
-      def successful_change_path
-        description = _('Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.')
-        merge_request_params = { source_branch: @branch_name, description: description }
-        Gitlab::Routing.url_helpers.project_new_merge_request_url(@project, merge_request: merge_request_params)
+      def message
+        _('Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist')
       end
 
-      def track_event(attributes_for_commit)
-        action = attributes_for_commit[:actions].first
-
-        Gitlab::Tracking.event(
-          self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s
-        )
+      def description
+        _('Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.')
       end
     end
   end
diff --git a/app/services/security/ci_configuration/sast_parser_service.rb b/app/services/security/ci_configuration/sast_parser_service.rb
index a8fe5764d193ec3cade06ecfd47bf22ec603e89c..5220525d552f9604d5514684922e456422444e53 100644
--- a/app/services/security/ci_configuration/sast_parser_service.rb
+++ b/app/services/security/ci_configuration/sast_parser_service.rb
@@ -74,7 +74,7 @@ def analyzer_enabled?(analyzer_name)
 
       def sast_excluded_analyzers
         strong_memoize(:sast_excluded_analyzers) do
-          all_analyzers = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS.split(', ') rescue []
+          all_analyzers = Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS.split(', ') rescue []
           enabled_analyzers = sast_default_analyzers.split(',').map(&:strip) rescue []
 
           excluded_analyzers = gitlab_ci_yml_attributes["SAST_EXCLUDED_ANALYZERS"] || sast_template_attributes["SAST_EXCLUDED_ANALYZERS"]
diff --git a/app/services/security/ci_configuration/secret_detection_create_service.rb b/app/services/security/ci_configuration/secret_detection_create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ff3458d36fcae5d8a16daf87924abe2cc88ea736
--- /dev/null
+++ b/app/services/security/ci_configuration/secret_detection_create_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Security
+  module CiConfiguration
+    class SecretDetectionCreateService < ::Security::CiConfiguration::BaseCreateService
+      private
+
+      def action
+        Security::CiConfiguration::SecretDetectionBuildAction.new(project.auto_devops_enabled?, existing_gitlab_ci_content).generate
+      end
+
+      def next_branch
+        'set-secret-detection-config'
+      end
+
+      def message
+        _('Configure Secret Detection in `.gitlab-ci.yml`, creating this file if it does not already exist')
+      end
+
+      def description
+        _('Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings.')
+      end
+    end
+  end
+end
diff --git a/changelogs/unreleased/321282-be-graphql-sd-settings.yml b/changelogs/unreleased/321282-be-graphql-sd-settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8ab0fa1af0e1ba6e0aa053c0ff615ce117efff5f
--- /dev/null
+++ b/changelogs/unreleased/321282-be-graphql-sd-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Add ConfigureSecretDetection graphql mutation
+merge_request: 58230
+author:
+type: added
diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml
index 3c6ef13dd1c07b3d0cc5de83fa3c1753a08ed184..f9929418a3d278714503edf5c61a0e1fa7b3ce2e 100644
--- a/config/known_invalid_graphql_queries.yml
+++ b/config/known_invalid_graphql_queries.yml
@@ -6,4 +6,3 @@ filenames:
   - ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql
   - app/assets/javascripts/repository/queries/blob_info.query.graphql
   - ee/app/assets/javascripts/security_configuration/graphql/configure_dependency_scanning.mutation.graphql
-  - ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 197c1dc373f5491817f61b3b17366c76a1ac4163..57a3b55f88f0adf6819fc5ab43573b15741faf3b 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -851,6 +851,10 @@ Input type: `CommitCreateInput`
 
 ### `Mutation.configureSast`
 
+Configure SAST for a project by enabling SAST in a new or modified
+`.gitlab-ci.yml` file in a new branch. The new branch and a URL to
+create a Merge Request are a part of the response.
+
 Input type: `ConfigureSastInput`
 
 #### Arguments
@@ -865,11 +869,36 @@ Input type: `ConfigureSastInput`
 
 | Name | Type | Description |
 | ---- | ---- | ----------- |
+| <a id="mutationconfiguresastbranch"></a>`branch` | [`String`](#string) | Branch that has the new/modified `.gitlab-ci.yml` file. |
 | <a id="mutationconfiguresastclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
 | <a id="mutationconfiguresasterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
-| <a id="mutationconfiguresaststatus"></a>`status` | [`String!`](#string) | Status of creating the commit for the supplied SAST CI configuration. |
 | <a id="mutationconfiguresastsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. |
 
+### `Mutation.configureSecretDetection`
+
+Configure Secret Detection for a project by enabling Secret Detection
+in a new or modified `.gitlab-ci.yml` file in a new branch. The new
+branch and a URL to create a Merge Request are a part of the
+response.
+
+Input type: `ConfigureSecretDetectionInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationconfiguresecretdetectionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationconfiguresecretdetectionprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationconfiguresecretdetectionbranch"></a>`branch` | [`String`](#string) | Branch that has the new/modified `.gitlab-ci.yml` file. |
+| <a id="mutationconfiguresecretdetectionclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationconfiguresecretdetectionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationconfiguresecretdetectionsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. |
+
 ### `Mutation.createAlertIssue`
 
 Input type: `CreateAlertIssueInput`
diff --git a/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue b/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue
index a0a3b1aa2ad28e203936d919739928465d6b508a..b79fdbd2cec32f18c2152cdf982de9330326c015 100644
--- a/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue
+++ b/ee/app/assets/javascripts/security_configuration/components/manage_via_mr.vue
@@ -39,7 +39,9 @@ export default {
         const { data } = await this.$apollo.mutate({
           mutation: this.featureSettings.mutation,
           variables: {
-            fullPath: this.projectPath,
+            input: {
+              projectPath: this.projectPath,
+            },
           },
         });
         const { errors, successPath } = data[this.featureSettings.type];
diff --git a/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql b/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql
index 2fd4b0982df65a1c015bf5d225dac39f14234523..e42a8de64f35a6e83c759b784c4110a2d80eb652 100644
--- a/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql
+++ b/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql
@@ -1,5 +1,5 @@
-mutation configureSecretDetection($fullPath: ID!) {
-  configureSecretDetection(fullPath: $fullPath) {
+mutation configureSecretDetection($input: ConfigureSecretDetectionInput!) {
+  configureSecretDetection(input: $input) {
     successPath
     errors
   }
diff --git a/ee/app/assets/javascripts/security_configuration/index.js b/ee/app/assets/javascripts/security_configuration/index.js
index ab15b0309d17993dcf751a4dd377d3385172301b..93d7659ff89403d86e1e51033ecd3660b91b5cd5 100644
--- a/ee/app/assets/javascripts/security_configuration/index.js
+++ b/ee/app/assets/javascripts/security_configuration/index.js
@@ -19,6 +19,7 @@ export const initSecurityConfiguration = (el) => {
     dependencyScanningHelpPath,
     toggleAutofixSettingEndpoint,
     gitlabCiHistoryPath,
+    projectPath,
   } = el.dataset;
 
   return new Vue({
@@ -26,6 +27,9 @@ export const initSecurityConfiguration = (el) => {
     components: {
       SecurityConfigurationApp,
     },
+    provide: {
+      projectPath,
+    },
     render(createElement) {
       return createElement(SecurityConfigurationApp, {
         props: {
diff --git a/ee/app/views/projects/security/configuration/show.html.haml b/ee/app/views/projects/security/configuration/show.html.haml
index cf849bff30a777572124cfd8b2644a78eb14694d..dcf062a5e04bfb93e11b4d4c6b2950ca4b561f26 100644
--- a/ee/app/views/projects/security/configuration/show.html.haml
+++ b/ee/app/views/projects/security/configuration/show.html.haml
@@ -5,6 +5,7 @@
   = render_ce 'projects/security/configuration/show'
 - else
   #js-security-configuration{ data: { **@configuration.to_html_data_attribute,
+    project_path: @project.full_path,
     auto_fix_help_path: '/',
     toggle_autofix_setting_endpoint: 'configuration/auto_fix',
     container_scanning_help_path: help_page_path('user/application_security/container_scanning/index'),
diff --git a/lib/security/ci_configuration/base_build_action.rb b/lib/security/ci_configuration/base_build_action.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b169d780cad5de35ac9e725c393a8e516cb29ce3
--- /dev/null
+++ b/lib/security/ci_configuration/base_build_action.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Security
+  module CiConfiguration
+    class BaseBuildAction
+      def initialize(auto_devops_enabled, existing_gitlab_ci_content)
+        @auto_devops_enabled = auto_devops_enabled
+        @existing_gitlab_ci_content = existing_gitlab_ci_content || {}
+      end
+
+      def generate
+        action = @existing_gitlab_ci_content.present? ? 'update' : 'create'
+
+        update_existing_content!
+
+        { action: action, file_path: '.gitlab-ci.yml', content: prepare_existing_content, default_values_overwritten: @default_values_overwritten }
+      end
+
+      private
+
+      def generate_includes
+        includes = @existing_gitlab_ci_content['include'] || []
+        includes = Array.wrap(includes)
+        includes << { 'template' => template }
+        includes.uniq
+      end
+
+      def prepare_existing_content
+        content = @existing_gitlab_ci_content.to_yaml
+        content = remove_document_delimiter(content)
+
+        content.prepend(comment)
+      end
+
+      def remove_document_delimiter(content)
+        content.gsub(/^---\n/, '')
+      end
+
+      def comment
+        <<~YAML
+          # You can override the included template(s) by including variable overrides
+          # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+          # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
+          # Note that environment variables can be set in several places
+          # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
+        YAML
+      end
+    end
+  end
+end
diff --git a/lib/security/ci_configuration/sast_build_actions.rb b/lib/security/ci_configuration/sast_build_action.rb
similarity index 76%
rename from lib/security/ci_configuration/sast_build_actions.rb
rename to lib/security/ci_configuration/sast_build_action.rb
index b2d684bc1e1204564cd00429880f4d60ef55136e..c319fc1637db162a0481e95f4af75c6c2d83aa06 100644
--- a/lib/security/ci_configuration/sast_build_actions.rb
+++ b/lib/security/ci_configuration/sast_build_action.rb
@@ -2,25 +2,16 @@
 
 module Security
   module CiConfiguration
-    class SastBuildActions
+    class SastBuildAction < BaseBuildAction
       SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, eslint, flawfinder, gosec, kubesec, nodejs-scan, phpcs-security-audit, pmd-apex, security-code-scan, sobelow, spotbugs'
 
       def initialize(auto_devops_enabled, params, existing_gitlab_ci_content)
-        @auto_devops_enabled = auto_devops_enabled
+        super(auto_devops_enabled, existing_gitlab_ci_content)
         @variables = variables(params)
-        @existing_gitlab_ci_content = existing_gitlab_ci_content || {}
         @default_sast_values = default_sast_values(params)
         @default_values_overwritten = false
       end
 
-      def generate
-        action = @existing_gitlab_ci_content.present? ? 'update' : 'create'
-
-        update_existing_content!
-
-        [{ action: action, file_path: '.gitlab-ci.yml', content: prepare_existing_content, default_values_overwritten: @default_values_overwritten }]
-      end
-
       private
 
       def variables(params)
@@ -71,19 +62,12 @@ def update_existing_content!
         @existing_gitlab_ci_content['stages'] = set_stages
         @existing_gitlab_ci_content['variables'] = set_variables(global_variables, @existing_gitlab_ci_content)
         @existing_gitlab_ci_content['sast'] = set_sast_block
-        @existing_gitlab_ci_content['include'] = set_includes
+        @existing_gitlab_ci_content['include'] = generate_includes
 
         @existing_gitlab_ci_content.select! { |k, v| v.present? }
         @existing_gitlab_ci_content['sast'].select! { |k, v| v.present? }
       end
 
-      def set_includes
-        includes = @existing_gitlab_ci_content['include'] || []
-        includes = includes.is_a?(Array) ? includes : [includes]
-        includes << { 'template' => template }
-        includes.uniq
-      end
-
       def set_stages
         existing_stages = @existing_gitlab_ci_content['stages'] || []
         base_stages = @auto_devops_enabled ? auto_devops_stages : ['test']
@@ -121,26 +105,6 @@ def set_sast_block
         sast_content.select { |k, v| v.present? }
       end
 
-      def prepare_existing_content
-        content = @existing_gitlab_ci_content.to_yaml
-        content = remove_document_delimeter(content)
-
-        content.prepend(sast_comment)
-      end
-
-      def remove_document_delimeter(content)
-        content.gsub(/^---\n/, '')
-      end
-
-      def sast_comment
-        <<~YAML
-          # You can override the included template(s) by including variable overrides
-          # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
-          # Note that environment variables can be set in several places
-          # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
-        YAML
-      end
-
       def template
         return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled
 
diff --git a/lib/security/ci_configuration/secret_detection_build_action.rb b/lib/security/ci_configuration/secret_detection_build_action.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5d513bf55470f708ffb2e10cf56114b776eb1a9d
--- /dev/null
+++ b/lib/security/ci_configuration/secret_detection_build_action.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Security
+  module CiConfiguration
+    class SecretDetectionBuildAction < BaseBuildAction
+      private
+
+      def update_existing_content!
+        @existing_gitlab_ci_content['include'] = generate_includes
+      end
+
+      def template
+        return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled
+
+        'Security/Secret-Detection.gitlab-ci.yml'
+      end
+    end
+  end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e992addf7afb2de04cb945ddf3f0d060fdc0c9bb..d54afe256f87eb6ccd5c094447f5473966145cb8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8305,6 +8305,18 @@ msgstr ""
 msgid "Configure Prometheus"
 msgstr ""
 
+msgid "Configure SAST in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings."
+msgstr ""
+
+msgid "Configure SAST in `.gitlab-ci.yml`, creating this file if it does not already exist"
+msgstr ""
+
+msgid "Configure Secret Detection in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings) to customize Secret Detection settings."
+msgstr ""
+
+msgid "Configure Secret Detection in `.gitlab-ci.yml`, creating this file if it does not already exist"
+msgstr ""
+
 msgid "Configure Tracing"
 msgstr ""
 
@@ -29089,12 +29101,6 @@ msgstr ""
 msgid "Set %{epic_ref} as the parent epic."
 msgstr ""
 
-msgid "Set .gitlab-ci.yml to enable or configure SAST"
-msgstr ""
-
-msgid "Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings."
-msgstr ""
-
 msgid "Set a default description template to be used for new issues. %{link_start}What are description templates?%{link_end}"
 msgstr ""
 
diff --git a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb
index ed03a1cb9068e10386e96be5f2dff93fd19b4c87..7c3b552480f32c2411d2652c73bc4c684b7337cb 100644
--- a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb
+++ b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb
@@ -3,118 +3,11 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do
-  subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
+  include GraphqlHelpers
 
-  let_it_be(:project) { create(:project, :public, :repository) }
-  let_it_be(:user) { create(:user) }
+  let(:service) { ::Security::CiConfiguration::SastCreateService }
 
-  let_it_be(:service_result_json) do
-    {
-      status: "success",
-      success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?",
-      errors: nil
-    }
-  end
+  subject { resolve(described_class, args: { project_path: project.full_path, configuration: {} }, ctx: { current_user: user }) }
 
-  let_it_be(:service_error_result_json) do
-    {
-      status: "error",
-      success_path: nil,
-      errors: %w(error1 error2)
-    }
-  end
-
-  let(:context) do
-    GraphQL::Query::Context.new(
-      query: OpenStruct.new(schema: nil),
-      values: { current_user: user },
-      object: nil
-    )
-  end
-
-  specify { expect(described_class).to require_graphql_authorizations(:push_code) }
-
-  describe '#resolve' do
-    subject { mutation.resolve(project_path: project.full_path, configuration: {}) }
-
-    let(:result) { subject }
-
-    it 'raises an error if the resource is not accessible to the user' do
-      expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
-    end
-
-    context 'when user does not have enough permissions' do
-      before do
-        project.add_guest(user)
-      end
-
-      it 'raises an error' do
-        expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
-      end
-    end
-
-    context 'when user is a maintainer of a different project' do
-      before do
-        create(:project_empty_repo).add_maintainer(user)
-      end
-
-      it 'raises an error' do
-        expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
-      end
-    end
-
-    context 'when the user does not have permission to create a new branch' do
-      before_all do
-        project.add_developer(user)
-      end
-
-      let(:error_message) { 'You are not allowed to create protected branches on this project.' }
-
-      it 'returns an array of errors' do
-        allow_next_instance_of(::Files::MultiService) do |multi_service|
-          allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}"))
-        end
-
-        expect(result).to match(
-          status: :error,
-          success_path: nil,
-          errors: match_array([error_message])
-        )
-      end
-    end
-
-    context 'when the user can create a merge request' do
-      before_all do
-        project.add_developer(user)
-      end
-
-      context 'when service successfully generates a path to create a new merge request' do
-        it 'returns a success path' do
-          allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service|
-            allow(service).to receive(:execute).and_return(service_result_json)
-          end
-
-          expect(result).to match(
-            status: 'success',
-            success_path: service_result_json[:success_path],
-            errors: []
-          )
-        end
-      end
-
-      context 'when service can not generate any path to create a new merge request' do
-        it 'returns an array of errors' do
-          allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service|
-            allow(service).to receive(:execute).and_return(service_error_result_json)
-          end
-
-          expect(result).to match(
-            status: 'error',
-            success_path: be_nil,
-            errors: match_array(service_error_result_json[:errors])
-          )
-        end
-      end
-    end
-  end
+  include_examples 'graphql mutations security ci configuration'
 end
diff --git a/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5b4a7d5918c09effa092ec9b96d2d52f886c5fe5
--- /dev/null
+++ b/spec/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Security::CiConfiguration::ConfigureSecretDetection do
+  include GraphqlHelpers
+
+  let(:service) { ::Security::CiConfiguration::SecretDetectionCreateService }
+
+  subject { resolve(described_class, args: { project_path: project.full_path }, ctx: { current_user: user }) }
+
+  include_examples 'graphql mutations security ci configuration'
+end
diff --git a/spec/lib/security/ci_configuration/sast_build_actions_spec.rb b/spec/lib/security/ci_configuration/sast_build_action_spec.rb
similarity index 80%
rename from spec/lib/security/ci_configuration/sast_build_actions_spec.rb
rename to spec/lib/security/ci_configuration/sast_build_action_spec.rb
index c8f9430eff9404949217c559d8aed83ee86a1a76..6a1ea68fdd6d305ce0fbae0acc7972097352776f 100644
--- a/spec/lib/security/ci_configuration/sast_build_actions_spec.rb
+++ b/spec/lib/security/ci_configuration/sast_build_action_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe Security::CiConfiguration::SastBuildActions do
+RSpec.describe Security::CiConfiguration::SastBuildAction do
   let(:default_sast_values) do
     { 'global' =>
       [
@@ -85,8 +85,8 @@
         subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
         it 'generates the correct YML' do
-          expect(result.first[:action]).to eq('update')
-          expect(result.first[:content]).to eq(sast_yaml_two_includes)
+          expect(result[:action]).to eq('update')
+          expect(result[:content]).to eq(sast_yaml_two_includes)
         end
       end
 
@@ -96,12 +96,12 @@
         subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
         it 'generates the correct YML' do
-          expect(result.first[:action]).to eq('update')
-          expect(result.first[:content]).to eq(sast_yaml_two_includes)
+          expect(result[:action]).to eq('update')
+          expect(result[:content]).to eq(sast_yaml_two_includes)
         end
 
         it 'reports defaults have been overwritten' do
-          expect(result.first[:default_values_overwritten]).to eq(true)
+          expect(result[:default_values_overwritten]).to eq(true)
         end
       end
     end
@@ -112,8 +112,8 @@
       subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
       it 'generates the correct YML' do
-        expect(result.first[:action]).to eq('update')
-        expect(result.first[:content]).to eq(sast_yaml_all_params)
+        expect(result[:action]).to eq('update')
+        expect(result[:content]).to eq(sast_yaml_all_params)
       end
     end
 
@@ -124,11 +124,11 @@
       subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
       it 'generates the correct YML' do
-        expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set)
+        expect(result[:content]).to eq(sast_yaml_with_no_variables_set)
       end
 
       it 'reports defaults have not been overwritten' do
-        expect(result.first[:default_values_overwritten]).to eq(false)
+        expect(result[:default_values_overwritten]).to eq(false)
       end
 
       context 'analyzer section' do
@@ -137,7 +137,7 @@
         subject(:result) { described_class.new(auto_devops_enabled, params_with_analyzer_info, gitlab_ci_content).generate }
 
         it 'generates the correct YML' do
-          expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers)
+          expect(result[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers)
         end
 
         context 'analyzers are disabled' do
@@ -146,9 +146,9 @@
           subject(:result) { described_class.new(auto_devops_enabled, params_with_analyzer_info, gitlab_ci_content).generate }
 
           it 'writes SAST_EXCLUDED_ANALYZERS' do
-            stub_const('Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS', 'bandit, brakeman, flawfinder')
+            stub_const('Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS', 'bandit, brakeman, flawfinder')
 
-            expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers)
+            expect(result[:content]).to eq(sast_yaml_with_no_variables_set_but_analyzers)
           end
         end
 
@@ -158,9 +158,9 @@
           subject(:result) { described_class.new(auto_devops_enabled, params_with_all_analyzers_enabled, gitlab_ci_content).generate }
 
           it 'does not write SAST_DEFAULT_ANALYZERS or SAST_EXCLUDED_ANALYZERS' do
-            stub_const('Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS', 'brakeman, flawfinder')
+            stub_const('Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS', 'brakeman, flawfinder')
 
-            expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set)
+            expect(result[:content]).to eq(sast_yaml_with_no_variables_set)
           end
         end
       end
@@ -186,8 +186,8 @@
       subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
       it 'generates the correct YML' do
-        expect(result.first[:action]).to eq('update')
-        expect(result.first[:content]).to eq(sast_yaml_updated_stage)
+        expect(result[:action]).to eq('update')
+        expect(result[:content]).to eq(sast_yaml_updated_stage)
       end
     end
 
@@ -197,8 +197,8 @@
       subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
       it 'generates the correct YML' do
-        expect(result.first[:action]).to eq('update')
-        expect(result.first[:content]).to eq(sast_yaml_variable_section_added)
+        expect(result[:action]).to eq('update')
+        expect(result[:content]).to eq(sast_yaml_variable_section_added)
       end
     end
 
@@ -208,8 +208,8 @@
       subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
       it 'generates the correct YML' do
-        expect(result.first[:action]).to eq('update')
-        expect(result.first[:content]).to eq(sast_yaml_sast_section_added)
+        expect(result[:action]).to eq('update')
+        expect(result[:content]).to eq(sast_yaml_sast_section_added)
       end
     end
 
@@ -219,8 +219,8 @@
       subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
       it 'generates the correct YML' do
-        expect(result.first[:action]).to eq('update')
-        expect(result.first[:content]).to eq(sast_yaml_sast_variables_section_added)
+        expect(result[:action]).to eq('update')
+        expect(result[:content]).to eq(sast_yaml_sast_variables_section_added)
       end
     end
 
@@ -289,7 +289,7 @@ def existing_gitlab_ci
         subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
         it 'generates the correct YML' do
-          expect(result.first[:content]).to eq(sast_yaml_with_no_variables_set)
+          expect(result[:content]).to eq(sast_yaml_with_no_variables_set)
         end
       end
 
@@ -297,7 +297,7 @@ def existing_gitlab_ci
         subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
         it 'generates the correct YML' do
-          expect(result.first[:content]).to eq(sast_yaml_all_params)
+          expect(result[:content]).to eq(sast_yaml_all_params)
         end
       end
     end
@@ -308,22 +308,22 @@ def existing_gitlab_ci
       subject(:result) { described_class.new(auto_devops_enabled, params, gitlab_ci_content).generate }
 
       before do
-        allow_next_instance_of(described_class) do |sast_build_actions|
-          allow(sast_build_actions).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages)
+        allow_next_instance_of(described_class) do |sast_build_action|
+          allow(sast_build_action).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages)
         end
       end
 
       it 'generates the correct YML' do
-        expect(result.first[:content]).to eq(auto_devops_with_custom_stage)
+        expect(result[:content]).to eq(auto_devops_with_custom_stage)
       end
     end
   end
 
-  describe 'Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS' do
-    subject(:variable) {Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS}
+  describe 'Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS' do
+    subject(:variable) {Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS}
 
     it 'is sorted alphabetically' do
-      sorted_variable = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS
+      sorted_variable = Security::CiConfiguration::SastBuildAction::SAST_DEFAULT_ANALYZERS
         .split(',')
         .map(&:strip)
         .sort
@@ -342,7 +342,8 @@ def fast_auto_devops_stages
   def sast_yaml_with_no_variables_set_but_analyzers
     <<-CI_YML.strip_heredoc
     # You can override the included template(s) by including variable overrides
-    # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+    # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+    # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
     # Note that environment variables can be set in several places
     # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
     stages:
@@ -360,7 +361,8 @@ def sast_yaml_with_no_variables_set_but_analyzers
   def sast_yaml_with_no_variables_set
     <<-CI_YML.strip_heredoc
     # You can override the included template(s) by including variable overrides
-    # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+    # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+    # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
     # Note that environment variables can be set in several places
     # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
     stages:
@@ -375,7 +377,8 @@ def sast_yaml_with_no_variables_set
   def sast_yaml_all_params
     <<-CI_YML.strip_heredoc
       # You can override the included template(s) by including variable overrides
-      # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
       # Note that environment variables can be set in several places
       # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
       stages:
@@ -396,7 +399,8 @@ def sast_yaml_all_params
   def auto_devops_with_custom_stage
     <<-CI_YML.strip_heredoc
       # You can override the included template(s) by including variable overrides
-      # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
       # Note that environment variables can be set in several places
       # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
       stages:
@@ -430,7 +434,8 @@ def auto_devops_with_custom_stage
   def sast_yaml_two_includes
     <<-CI_YML.strip_heredoc
       # You can override the included template(s) by including variable overrides
-      # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
       # Note that environment variables can be set in several places
       # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
       stages:
@@ -453,7 +458,8 @@ def sast_yaml_two_includes
   def sast_yaml_variable_section_added
     <<-CI_YML.strip_heredoc
       # You can override the included template(s) by including variable overrides
-      # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
       # Note that environment variables can be set in several places
       # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
       stages:
@@ -474,7 +480,8 @@ def sast_yaml_variable_section_added
   def sast_yaml_sast_section_added
     <<-CI_YML.strip_heredoc
       # You can override the included template(s) by including variable overrides
-      # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
       # Note that environment variables can be set in several places
       # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
       stages:
@@ -496,7 +503,8 @@ def sast_yaml_sast_section_added
   def sast_yaml_sast_variables_section_added
     <<-CI_YML.strip_heredoc
       # You can override the included template(s) by including variable overrides
-      # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
       # Note that environment variables can be set in several places
       # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
       stages:
@@ -518,7 +526,8 @@ def sast_yaml_sast_variables_section_added
   def sast_yaml_updated_stage
     <<-CI_YML.strip_heredoc
       # You can override the included template(s) by including variable overrides
-      # See https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+      # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
       # Note that environment variables can be set in several places
       # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
       stages:
diff --git a/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31854fcf3a731e22d9fc1d0893ca4ba50a2dd864
--- /dev/null
+++ b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do
+  subject(:result) { described_class.new(auto_devops_enabled, gitlab_ci_content).generate }
+
+  let(:params) { {} }
+
+  context 'with existing .gitlab-ci.yml' do
+    let(:auto_devops_enabled) { false }
+
+    context 'secret_detection has not been included' do
+      let(:expected_yml) do
+        <<-CI_YML.strip_heredoc
+          # You can override the included template(s) by including variable overrides
+          # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+          # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
+          # Note that environment variables can be set in several places
+          # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
+          stages:
+          - test
+          - security
+          variables:
+            RANDOM: make sure this persists
+          include:
+          - template: existing.yml
+          - template: Security/Secret-Detection.gitlab-ci.yml
+        CI_YML
+      end
+
+      context 'template includes are an array' do
+        let(:gitlab_ci_content) do
+          { "stages" => %w(test security),
+            "variables" => { "RANDOM" => "make sure this persists" },
+            "include" => [{ "template" => "existing.yml" }] }
+        end
+
+        it 'generates the correct YML' do
+          expect(result[:action]).to eq('update')
+          expect(result[:content]).to eq(expected_yml)
+        end
+      end
+
+      context 'template include is not an array' do
+        let(:gitlab_ci_content) do
+          { "stages" => %w(test security),
+            "variables" => { "RANDOM" => "make sure this persists" },
+            "include" => { "template" => "existing.yml" } }
+        end
+
+        it 'generates the correct YML' do
+          expect(result[:action]).to eq('update')
+          expect(result[:content]).to eq(expected_yml)
+        end
+      end
+    end
+
+    context 'secret_detection has been included' do
+      let(:expected_yml) do
+        <<-CI_YML.strip_heredoc
+          # You can override the included template(s) by including variable overrides
+          # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+          # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
+          # Note that environment variables can be set in several places
+          # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
+          stages:
+          - test
+          variables:
+            RANDOM: make sure this persists
+          include:
+          - template: Security/Secret-Detection.gitlab-ci.yml
+        CI_YML
+      end
+
+      context 'secret_detection template include are an array' do
+        let(:gitlab_ci_content) do
+          { "stages" => %w(test),
+            "variables" => { "RANDOM" => "make sure this persists" },
+            "include" => [{ "template" => "Security/Secret-Detection.gitlab-ci.yml" }] }
+        end
+
+        it 'generates the correct YML' do
+          expect(result[:action]).to eq('update')
+          expect(result[:content]).to eq(expected_yml)
+        end
+      end
+
+      context 'secret_detection template include is not an array' do
+        let(:gitlab_ci_content) do
+          { "stages" => %w(test),
+            "variables" => { "RANDOM" => "make sure this persists" },
+            "include" => { "template" => "Security/Secret-Detection.gitlab-ci.yml" } }
+        end
+
+        it 'generates the correct YML' do
+          expect(result[:action]).to eq('update')
+          expect(result[:content]).to eq(expected_yml)
+        end
+      end
+    end
+  end
+
+  context 'with no .gitlab-ci.yml' do
+    let(:gitlab_ci_content) { nil }
+
+    context 'autodevops disabled' do
+      let(:auto_devops_enabled) { false }
+      let(:expected_yml) do
+        <<-CI_YML.strip_heredoc
+          # You can override the included template(s) by including variable overrides
+          # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+          # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
+          # Note that environment variables can be set in several places
+          # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
+          include:
+          - template: Security/Secret-Detection.gitlab-ci.yml
+        CI_YML
+      end
+
+      it 'generates the correct YML' do
+        expect(result[:action]).to eq('create')
+        expect(result[:content]).to eq(expected_yml)
+      end
+    end
+
+    context 'with autodevops enabled' do
+      let(:auto_devops_enabled) { true }
+      let(:expected_yml) do
+        <<-CI_YML.strip_heredoc
+          # You can override the included template(s) by including variable overrides
+          # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+          # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
+          # Note that environment variables can be set in several places
+          # See https://docs.gitlab.com/ee/ci/variables/#priority-of-environment-variables
+          include:
+          - template: Auto-DevOps.gitlab-ci.yml
+        CI_YML
+      end
+
+      before do
+        allow_next_instance_of(described_class) do |secret_detection_build_actions|
+          allow(secret_detection_build_actions).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages)
+        end
+      end
+
+      it 'generates the correct YML' do
+        expect(result[:action]).to eq('create')
+        expect(result[:content]).to eq(expected_yml)
+      end
+    end
+  end
+
+  # stubbing this method allows this spec file to use fast_spec_helper
+  def fast_auto_devops_stages
+    auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') )
+    auto_devops_template['stages']
+  end
+end
diff --git a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..23a154b71a02924c183548784c1de64c1bfa9d68
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ConfigureSecretDetection' do
+  include GraphqlHelpers
+
+  let_it_be(:project) { create(:project, :test_repo) }
+
+  let(:variables) { { project_path: project.full_path } }
+  let(:mutation) { graphql_mutation(:configure_secret_detection, variables) }
+  let(:mutation_response) { graphql_mutation_response(:configureSecretDetection) }
+
+  context 'when authorized' do
+    let_it_be(:user) { project.owner }
+
+    it 'creates a branch with secret detection configured' do
+      post_graphql_mutation(mutation, current_user: user)
+
+      expect(response).to have_gitlab_http_status(:success)
+      expect(mutation_response['errors']).to be_empty
+      expect(mutation_response['branch']).not_to be_empty
+      expect(mutation_response['successPath']).not_to be_empty
+    end
+  end
+end
diff --git a/spec/services/security/ci_configuration/sast_create_service_spec.rb b/spec/services/security/ci_configuration/sast_create_service_spec.rb
index ff7ab614e086eb7f714f4f1b6fd6358f8c6c5615..44f8f07a5beb4663534bd2694b9641098fbfe65b 100644
--- a/spec/services/security/ci_configuration/sast_create_service_spec.rb
+++ b/spec/services/security/ci_configuration/sast_create_service_spec.rb
@@ -3,67 +3,24 @@
 require 'spec_helper'
 
 RSpec.describe Security::CiConfiguration::SastCreateService, :snowplow do
-  describe '#execute' do
-    let_it_be(:project) { create(:project, :repository) }
-    let_it_be(:user) { create(:user) }
-    let(:params) { {} }
+  subject(:result) { described_class.new(project, user, params).execute }
 
-    subject(:result) { described_class.new(project, user, params).execute }
+  let(:branch_name) { 'set-sast-config-1' }
 
-    context 'user does not belong to project' do
-      it 'returns an error status' do
-        expect(result[:status]).to eq(:error)
-        expect(result[:success_path]).to be_nil
-      end
-
-      it 'does not track a snowplow event' do
-        subject
-
-        expect_no_snowplow_event
-      end
-    end
-
-    context 'user belongs to project' do
-      before do
-        project.add_developer(user)
-      end
-
-      it 'does track the snowplow event' do
-        subject
-
-        expect_snowplow_event(
-          category: 'Security::CiConfiguration::SastCreateService',
-          action: 'create',
-          label: 'false'
-        )
-      end
-
-      it 'raises exception if the user does not have permission to create a new branch' do
-        allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.")
-
-        expect { subject }.to raise_error(Gitlab::Git::PreReceiveError)
-      end
-
-      context 'with no parameters' do
-        it 'returns the path to create a new merge request' do
-          expect(result[:status]).to eq(:success)
-          expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
-        end
-      end
-
-      context 'with parameters' do
-        let(:params) do
-          { 'stage' => 'security',
-            'SEARCH_MAX_DEPTH' => 1,
-            'SECURE_ANALYZERS_PREFIX' => 'new_registry',
-            'SAST_EXCLUDED_PATHS' => 'spec,docs' }
-        end
+  let(:non_empty_params) do
+    { 'stage' => 'security',
+      'SEARCH_MAX_DEPTH' => 1,
+      'SECURE_ANALYZERS_PREFIX' => 'new_registry',
+      'SAST_EXCLUDED_PATHS' => 'spec,docs' }
+  end
 
-        it 'returns the path to create a new merge request' do
-          expect(result[:status]).to eq(:success)
-          expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
-        end
-      end
-    end
+  let(:snowplow_event) do
+    {
+      category: 'Security::CiConfiguration::SastCreateService',
+      action: 'create',
+      label: 'false'
+    }
   end
+
+  include_examples 'services security ci configuration create service'
 end
diff --git a/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c1df3ebdca5bfc86618e572b00f363b54f9be65f
--- /dev/null
+++ b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Security::CiConfiguration::SecretDetectionCreateService, :snowplow do
+  subject(:result) { described_class.new(project, user).execute }
+
+  let(:branch_name) { 'set-secret-detection-config-1' }
+
+  let(:snowplow_event) do
+    {
+      category: 'Security::CiConfiguration::SecretDetectionCreateService',
+      action: 'create',
+      label: ''
+    }
+  end
+
+  include_examples 'services security ci configuration create service', true
+end
diff --git a/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2bb3d807aa7bbe23b8574c735de0dd9a4309a752
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutations/security/ci_configuration_shared_examples.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples_for 'graphql mutations security ci configuration' do
+  let_it_be(:project) { create(:project, :public, :repository) }
+  let_it_be(:user) { create(:user) }
+
+  let(:branch) do
+    "set-secret-config"
+  end
+
+  let(:success_path) do
+    "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?"
+  end
+
+  let(:service_response) do
+    ServiceResponse.success(payload: { branch: branch, success_path: success_path })
+  end
+
+  let(:error) { "An error occured!" }
+
+  let(:service_error_response) do
+    ServiceResponse.error(message: error)
+  end
+
+  specify { expect(described_class).to require_graphql_authorizations(:push_code) }
+
+  describe '#resolve' do
+    let(:result) { subject }
+
+    it 'raises an error if the resource is not accessible to the user' do
+      expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+    end
+
+    context 'when user does not have enough permissions' do
+      before do
+        project.add_guest(user)
+      end
+
+      it 'raises an error' do
+        expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+      end
+    end
+
+    context 'when user is a maintainer of a different project' do
+      before do
+        create(:project_empty_repo).add_maintainer(user)
+      end
+
+      it 'raises an error' do
+        expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+      end
+    end
+
+    context 'when the user does not have permission to create a new branch' do
+      let(:error_message) { 'You are not allowed to create protected branches on this project.' }
+
+      before do
+        project.add_developer(user)
+
+        allow_next_instance_of(::Files::MultiService) do |multi_service|
+          allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}"))
+        end
+      end
+
+      it 'returns an array of errors' do
+        expect(result).to match(
+          branch: be_nil,
+          success_path: be_nil,
+          errors: match_array([error_message])
+        )
+      end
+    end
+
+    context 'when the user can create a merge request' do
+      before do
+        project.add_developer(user)
+      end
+
+      context 'when service successfully generates a path to create a new merge request' do
+        before do
+          allow_next_instance_of(service) do |service|
+            allow(service).to receive(:execute).and_return(service_response)
+          end
+        end
+
+        it 'returns a success path' do
+          expect(result).to match(
+            branch: branch,
+            success_path: success_path,
+            errors: []
+          )
+        end
+      end
+
+      context 'when service can not generate any path to create a new merge request' do
+        before do
+          allow_next_instance_of(service) do |service|
+            allow(service).to receive(:execute).and_return(service_error_response)
+          end
+        end
+
+        it 'returns an array of errors' do
+          expect(result).to match(
+            branch: be_nil,
+            success_path: be_nil,
+            errors: match_array([error])
+          )
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..538fd2bb513e196be5279e765777160daf8a3d88
--- /dev/null
+++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples_for 'services security ci configuration create service' do |skip_w_params|
+  let_it_be(:project) { create(:project, :repository) }
+  let_it_be(:user) { create(:user) }
+
+  describe '#execute' do
+    let(:params) { {} }
+
+    context 'user does not belong to project' do
+      it 'returns an error status' do
+        expect(result.status).to eq(:error)
+        expect(result.payload[:success_path]).to be_nil
+      end
+
+      it 'does not track a snowplow event' do
+        subject
+
+        expect_no_snowplow_event
+      end
+    end
+
+    context 'user belongs to project' do
+      before do
+        project.add_developer(user)
+      end
+
+      it 'does track the snowplow event' do
+        subject
+
+        expect_snowplow_event(**snowplow_event)
+      end
+
+      it 'raises exception if the user does not have permission to create a new branch' do
+        allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.")
+
+        expect { subject }.to raise_error(Gitlab::Git::PreReceiveError)
+      end
+
+      context 'when exception is raised' do
+        let_it_be(:project) { create(:project, :repository) }
+
+        before do
+          allow(project.repository).to receive(:add_branch).and_raise(StandardError, "The unexpected happened!")
+        end
+
+        context 'when branch was created' do
+          before do
+            allow(project.repository).to receive(:branch_exists?).and_return(true)
+          end
+
+          it 'tries to rm branch' do
+            expect(project.repository).to receive(:rm_branch).with(user, branch_name)
+            expect { subject }.to raise_error(StandardError)
+          end
+        end
+
+        context 'when branch was not created' do
+          before do
+            allow(project.repository).to receive(:branch_exists?).and_return(false)
+          end
+
+          it 'does not try to rm branch' do
+            expect(project.repository).not_to receive(:rm_branch)
+            expect { subject }.to raise_error(StandardError)
+          end
+        end
+      end
+
+      context 'with no parameters' do
+        it 'returns the path to create a new merge request' do
+          expect(result.status).to eq(:success)
+          expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
+        end
+      end
+
+      unless skip_w_params
+        context 'with parameters' do
+          let(:params) { non_empty_params }
+
+          it 'returns the path to create a new merge request' do
+            expect(result.status).to eq(:success)
+            expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
+          end
+        end
+      end
+    end
+  end
+end