diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md
index 7865a884397a1ce8711c7ce4f0c9efc318eea679..b1ca644a66558b32b9b4d90173a7a30cc45bb4c1 100644
--- a/doc/user/application_security/policies/scan-execution-policies.md
+++ b/doc/user/application_security/policies/scan-execution-policies.md
@@ -475,11 +475,12 @@ Note the following:
 - `custom` scans are being executed for triggered rules only.
 - Jobs variables from `custom` scans take precedence over the project's CI/CD configuration.
 - Users triggering a pipeline must have at least read access to CI files specified in the `ci_configuration_path` or included in the CI/CD configuration.
-- It is not possible to define custom stages using the `stages` keyword in a custom scan action. Instead three default stages will be added to the pipeline:
+- It is not possible to define custom stages using the `stages` keyword in a custom scan action. Instead three reserved stages will be added to the pipeline:
   - `.pipeline-policy-pre`at the beginning of the pipeline, before the `.pre` stage.
   - `.pipeline-policy-test` after the `test` stage. If the `test` stage does not exist, it will be injected after the `build` stage. If the `build` stage does not exist, it will be injected at the beginning of the pipeline after the `.pre` stage.
   - `.pipeline-policy-post` at the very end of the pipeline, after the .post stage.
 - Jobs without a stage are assigned to the `.pipeline-policy-test` stage by default.
+- It is not possible to assign jobs to reserved stages outside of a custom scan action.
 
 #### Example security policies project
 
diff --git a/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb b/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb
index 9dd9fb8c04c925dc13655b7882306f32bac1e8bd..852fbc3b9d78bc5298ee8db5e35c2d7c10119f21 100644
--- a/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb
+++ b/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb
@@ -13,6 +13,7 @@ class Processor
           DEFAULT_POLICY_PRE_STAGE = '.pipeline-policy-pre'
           DEFAULT_POLICY_TEST_STAGE = '.pipeline-policy-test'
           DEFAULT_POLICY_POST_STAGE = '.pipeline-policy-post'
+          RESERVED_STAGES = [DEFAULT_POLICY_PRE_STAGE, DEFAULT_POLICY_TEST_STAGE, DEFAULT_POLICY_POST_STAGE].freeze
           DEFAULT_STAGES = Gitlab::Ci::Config::Entry::Stages.default
 
           def initialize(config, context, ref, source)
@@ -34,6 +35,8 @@ def perform
             merged_config = @config.deep_merge(merged_security_policy_config)
 
             if custom_scan_actions_enabled? && active_scan_custom_actions.any?
+              merged_config = clean_up_reserved_stages_jobs(merged_config)
+
               merged_config = merged_config.deep_merge(scan_custom_actions[:pipeline_scan])
 
               merged_config[:stages] = insert_custom_scan_stages(merged_config[:stages])
@@ -137,6 +140,14 @@ def merge_pipeline_scan_template(merged_config, defined_stages)
             end
           end
 
+          def clean_up_reserved_stages_jobs(config)
+            jobs_to_reject = config.except(*Config::Entry::Root.reserved_nodes_names).select do |_, content|
+              RESERVED_STAGES.include?(content[:stage])
+            end.keys
+
+            config.except(*jobs_to_reject)
+          end
+
           def insert_custom_scan_stages(config_stages)
             config_stages.append(DEFAULT_POLICY_POST_STAGE)
 
diff --git a/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb b/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb
index 163ab8d91405f4d6c9ec18f4ebe97f432bc832b1..f62e6a90fc118df6bdeed538dc14033d871db1a2 100644
--- a/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb
+++ b/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb
@@ -391,6 +391,30 @@
           it 'does not includes the custom job' do
             expect(perform_service[:custom_job]).to be_nil
           end
+
+          context 'and project has jobs in reserved stages' do
+            let(:config) do
+              {
+                stages: %w[.pipeline-policy-test],
+                reserved_stage_test_job: {
+                  stage: '.pipeline-policy-test',
+                  script: [
+                    'echo "Hello World"'
+                  ]
+                }
+              }
+            end
+
+            it 'does not remove the reserved stages and jobs', :aggregate_failures do
+              expect(perform_service[:reserved_stage_test_job]).to eq(
+                {
+                  script: ['echo "Hello World"'],
+                  stage: ".pipeline-policy-test"
+                }
+              )
+              expect(perform_service[:stages]).to include('.pipeline-policy-test')
+            end
+          end
         end
 
         it 'does not include the custom job' do
@@ -411,6 +435,24 @@
             )
           end
 
+          context 'and project has jobs in reserved stages' do
+            let(:config) do
+              {
+                stages: %w[.pipeline-policy-test],
+                test_job: {
+                  stage: '.pipeline-policy-test',
+                  script: [
+                    'echo "Hello World"'
+                  ]
+                }
+              }
+            end
+
+            it 'removes project jobs in reserved stages' do
+              expect(perform_service.key?(:test_job)).to eq(false)
+            end
+          end
+
           context 'when test stage does not exist' do
             let(:config) { { stages: %w[build deploy] } }