diff --git a/db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb b/db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2d1c274ab3850257f7176ee274f0dda932290b62
--- /dev/null
+++ b/db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddSppRepositoryPipelineAccessToProjectSettings < Gitlab::Database::Migration[2.2]
+  enable_lock_retries!
+  milestone '17.4'
+
+  def change
+    add_column :project_settings, :spp_repository_pipeline_access, :boolean
+  end
+end
diff --git a/db/schema_migrations/20240828103148 b/db/schema_migrations/20240828103148
new file mode 100644
index 0000000000000000000000000000000000000000..d5a5dcbeb528e344cfacea35c942765782541815
--- /dev/null
+++ b/db/schema_migrations/20240828103148
@@ -0,0 +1 @@
+3a6da002969d32ed71e3a8700a007380d20193df5f3a3591e98136a135380f4f
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 91b21383f49e4483f011ee9e2246809dcae99807..409fc6aef8f96b6399a49d99c9c43c7960c4b634 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -16719,6 +16719,7 @@ CREATE TABLE project_settings (
     duo_features_enabled boolean DEFAULT true NOT NULL,
     require_reauthentication_to_approve boolean,
     observability_alerts_enabled boolean DEFAULT true NOT NULL,
+    spp_repository_pipeline_access boolean,
     CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)),
     CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
     CONSTRAINT check_3ca5cbffe6 CHECK ((char_length(issue_branch_template) <= 255)),
diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb
index e3f12e99e9463aa48355b4c678c5b426c50d818b..b116d2cf181e1df3a4fc587cb36251c1bf7f4d1a 100644
--- a/ee/app/models/ee/project.rb
+++ b/ee/app/models/ee/project.rb
@@ -1224,6 +1224,22 @@ def auto_rollback_enabled?
       ci_cd_settings.auto_rollback_enabled?
     end
 
+    def affected_by_security_policy_management_project?(management_project)
+      all_parent_groups = group&.self_and_ancestor_ids
+      policies = ::Security::OrchestrationPolicyConfiguration
+        .for_management_project(management_project)
+        .for_project(id)
+
+      if all_parent_groups.present?
+        policies = policies.or(
+          ::Security::OrchestrationPolicyConfiguration
+            .for_management_project(management_project)
+            .for_namespace(all_parent_groups))
+      end
+
+      policies.exists?
+    end
+
     def all_security_orchestration_policy_configurations(include_invalid: false)
       all_parent_groups = group&.self_and_ancestor_ids
       return [] if all_parent_groups.blank? && !security_orchestration_policy_configuration&.policy_configuration_valid? && !include_invalid
diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb
index 7f4e9066c5ee3809a954993e053a1c860c9de842..fb7d7d59a247434b504b42df9cbf93b2f2259d62 100644
--- a/ee/app/policies/ee/project_policy.rb
+++ b/ee/app/policies/ee/project_policy.rb
@@ -825,6 +825,16 @@ module ProjectPolicy
         enable :build_download_code
       end
 
+      desc "SPP project access to read policy config for pipeline execution policy"
+      condition(:spp_repository_access_allowed) do
+        Security::OrchestrationPolicyConfiguration.policy_management_project?(project) &&
+          project.project_setting.spp_repository_pipeline_access
+      end
+
+      rule { spp_repository_access_allowed & project_allowed_for_job_token_by_scope }.policy do
+        enable :download_code_spp_repository
+      end
+
       rule do
         summarize_new_merge_request_enabled & can?(:create_merge_request_in)
       end.enable :summarize_new_merge_request
diff --git a/ee/lib/ee/gitlab/ci/config/external/file/project.rb b/ee/lib/ee/gitlab/ci/config/external/file/project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f4cbc4913a191dfbf093d3b462f5e39bd69cbc9
--- /dev/null
+++ b/ee/lib/ee/gitlab/ci/config/external/file/project.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module EE
+  module Gitlab
+    module Ci
+      module Config
+        module External
+          module File
+            module Project
+              extend ::Gitlab::Utils::Override
+
+              private
+
+              override :project_access_allowed?
+              def project_access_allowed?(user, project)
+                super || security_policy_management_project_access_allowed?(user, project)
+              end
+
+              def security_policy_management_project_access_allowed?(user, project)
+                return false unless context.pipeline_config&.pipeline_policy_context&.execution_policy_mode?
+                return false unless context.project.affected_by_security_policy_management_project?(project)
+
+                Ability.allowed?(user, :download_code_spp_repository, project)
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/ee/spec/lib/gitlab/ci/config/external/file/project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f3104301933998116e05163cb6c8c9c08afe9a5e
--- /dev/null
+++ b/ee/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::File::Project, feature_category: :pipeline_composition do
+  include RepoHelpers
+
+  let_it_be(:context_project) { create(:project) }
+  let_it_be(:project) { create(:project, :repository) }
+  let_it_be(:user) { create(:user, developer_of: project) }
+
+  let(:context_user) { user }
+  let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
+  let(:project_file) { described_class.new(params, context) }
+  let(:pipeline_config) { nil }
+  let(:context_params) do
+    {
+      project: context_project,
+      sha: project.commit.sha,
+      user: context_user,
+      pipeline_config: pipeline_config
+    }
+  end
+
+  before do
+    allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance|
+      allow(instance).to receive(:check_execution_time!)
+    end
+  end
+
+  describe '#valid?' do
+    subject(:valid?) do
+      Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([project_file])
+      project_file.valid?
+    end
+
+    describe 'security_policy_management_project_access_allowed?' do
+      include_context 'with pipeline policy context'
+
+      let(:params) { { file: 'pipeline-execution-policy.yml', project: project.full_path } }
+      let(:execution_policy_dry_run) { true }
+      let(:pipeline_config) do
+        instance_double(Gitlab::Ci::ProjectConfig,
+          internal_include_prepended?: true,
+          pipeline_policy_context: pipeline_policy_context)
+      end
+
+      around do |example|
+        create_and_delete_files(project,
+          { '/pipeline-execution-policy.yml' => { compliance_job: { script: 'test' } }.to_yaml }) do
+          example.run
+        end
+      end
+
+      shared_examples_for 'user has no access to the project' do
+        it 'returns false' do
+          expect(valid?).to be(false)
+          expect(project_file.error_message).to include("Project `#{project.full_path}` not found or access denied!")
+        end
+      end
+
+      context 'when user does not have permission to access file' do
+        let(:context_user) { create(:user) }
+
+        it_behaves_like 'user has no access to the project'
+
+        context 'and project is a security policy project' do
+          let_it_be(:security_orchestration_policy_configuration, reload: true) do
+            create(:security_orchestration_policy_configuration, security_policy_management_project: project,
+              project: context_project)
+          end
+
+          it_behaves_like 'user has no access to the project'
+
+          context 'and project is linked to the context project as a security policy project' do
+            before_all do
+              security_orchestration_policy_configuration.update!(project: context_project)
+            end
+
+            it_behaves_like 'user has no access to the project'
+
+            context 'and project allows SPP repository access via project settings' do
+              before do
+                project.project_setting.update!(spp_repository_pipeline_access: true)
+              end
+
+              it 'returns true' do
+                expect(valid?).to be(true)
+              end
+
+              context 'when not running in execution_policy_mode' do
+                let(:execution_policy_dry_run) { false }
+
+                it_behaves_like 'user has no access to the project'
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/models/ee/project_spec.rb b/ee/spec/models/ee/project_spec.rb
index 48d7b2494b5845a31f0c6561c74673d89ef87ceb..59c97325d182c35482709a6ca3538e2fd3c75632 100644
--- a/ee/spec/models/ee/project_spec.rb
+++ b/ee/spec/models/ee/project_spec.rb
@@ -3848,6 +3848,62 @@ def stub_default_url_options(host)
     it { is_expected.not_to include(scan_finding_rule, license_scanning_rule, any_merge_request_rule) }
   end
 
+  describe '#affected_by_security_policy_management_project?' do
+    subject { project.affected_by_security_policy_management_project?(security_policy_management_project) }
+
+    let_it_be(:spp_project) { create(:project, :repository) }
+    let_it_be(:other_spp_project) { create(:project, :repository) }
+
+    let(:security_policy_management_project) { spp_project }
+
+    it { is_expected.to be(false) }
+
+    context 'when security orchestration policy is configured for project' do
+      let_it_be(:project) { create(:project) }
+      let_it_be(:project_security_orchestration_policy_configuration) do
+        create(:security_orchestration_policy_configuration, project: project,
+          security_policy_management_project: spp_project)
+      end
+
+      it { is_expected.to be(true) }
+
+      context 'with other security policy management project' do
+        let(:security_policy_management_project) { other_spp_project }
+
+        it { is_expected.to be(false) }
+      end
+    end
+
+    context 'when security orchestration policy is configured for a parent namespace' do
+      let_it_be(:parent_group) { create(:group) }
+      let_it_be(:child_group) { create(:group, parent: parent_group) }
+      let_it_be(:project) { create(:project, group: child_group) }
+
+      let_it_be(:parent_group_security_orchestration_policy_configuration) do
+        create(:security_orchestration_policy_configuration, :namespace, namespace: parent_group,
+          security_policy_management_project: spp_project)
+      end
+
+      it { is_expected.to be(true) }
+
+      context 'with other security policy management project' do
+        let(:security_policy_management_project) { other_spp_project }
+
+        it { is_expected.to be(false) }
+      end
+    end
+
+    context 'when security orchestration policy is configured for another project' do
+      let_it_be(:another_project) { create(:project) }
+      let_it_be(:project_security_orchestration_policy_configuration) do
+        create(:security_orchestration_policy_configuration, project: another_project,
+          security_policy_management_project: spp_project)
+      end
+
+      it { is_expected.to be(false) }
+    end
+  end
+
   describe '#all_security_orchestration_policy_configurations' do
     subject { project.all_security_orchestration_policy_configurations }
 
diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb
index 31b3f60c11c853f6b0888f15877d95e59fb032bd..2597bb0022b68755da91c0829f615250610d472f 100644
--- a/ee/spec/policies/project_policy_spec.rb
+++ b/ee/spec/policies/project_policy_spec.rb
@@ -3330,6 +3330,99 @@ def create_member_role(member, abilities = member_role_abilities)
     end
   end
 
+  describe 'download_code_spp_repository policy' do
+    let(:current_user) { guest }
+
+    it { is_expected.not_to be_allowed(:download_code_spp_repository) }
+
+    context 'when project is a security policy project' do
+      before do
+        create(:security_orchestration_policy_configuration, security_policy_management_project: project)
+      end
+
+      it { is_expected.not_to be_allowed(:download_code_spp_repository) }
+
+      context 'and project allows spp_repository_pipeline_access' do
+        before do
+          project.project_setting.update!(spp_repository_pipeline_access: true)
+        end
+
+        context 'and the project is private' do
+          let(:project) { private_project }
+
+          it { is_expected.to be_allowed(:download_code_spp_repository) }
+        end
+
+        context 'and the project is internal' do
+          let(:project) { internal_project }
+
+          it { is_expected.to be_allowed(:download_code_spp_repository) }
+        end
+
+        context 'and the project is public' do
+          let(:project) { public_project }
+
+          it { is_expected.to be_allowed(:download_code_spp_repository) }
+        end
+
+        context 'and the project is public in group' do
+          let(:project) { public_project_in_group }
+
+          it { is_expected.to be_allowed(:download_code_spp_repository) }
+        end
+      end
+    end
+
+    context 'when user is authenticated via CI_JOB_TOKEN', :request_store do
+      let(:job) { build_stubbed(:ci_build, project: scope_project, user: current_user) }
+      let(:scope_project) { project }
+
+      let_it_be(:other_private_project) { create(:project, :private) }
+
+      before do
+        current_user.set_ci_job_token_scope!(job)
+        create(:security_orchestration_policy_configuration, security_policy_management_project: project)
+        project.project_setting.update!(spp_repository_pipeline_access: true)
+        project.update!(
+          ci_outbound_job_token_scope_enabled: token_scope_enabled,
+          ci_inbound_job_token_scope_enabled: token_scope_enabled
+        )
+        scope_project.update!(
+          ci_outbound_job_token_scope_enabled: token_scope_enabled,
+          ci_inbound_job_token_scope_enabled: token_scope_enabled
+        )
+      end
+
+      context 'when token scope is disabled' do
+        let(:token_scope_enabled) { false }
+
+        context 'when accessing from the same project' do
+          it { is_expected.to be_allowed(:download_code_spp_repository) }
+        end
+
+        context 'when accessing from other project' do
+          let(:scope_project) { other_private_project }
+
+          it { is_expected.to be_allowed(:download_code_spp_repository) }
+        end
+      end
+
+      context 'when token scope is enabled' do
+        let(:token_scope_enabled) { true }
+
+        context 'when accessing from the same project' do
+          it { is_expected.to be_allowed(:download_code_spp_repository) }
+        end
+
+        context 'when accessing from other project' do
+          let(:scope_project) { other_private_project }
+
+          it { is_expected.to be_disallowed(:download_code_spp_repository) }
+        end
+      end
+    end
+  end
+
   describe 'generate_description' do
     let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) }
     let(:current_user) { guest }
diff --git a/ee/spec/services/ci/create_pipeline_service/pipeline_execution_policy_spec.rb b/ee/spec/services/ci/create_pipeline_service/pipeline_execution_policy_spec.rb
index ec87f803094ee9f31c57008227a7c7f5991d359e..43c55e34f3fd6e74b26897480eaebb3bb7a718b0 100644
--- a/ee/spec/services/ci/create_pipeline_service/pipeline_execution_policy_spec.rb
+++ b/ee/spec/services/ci/create_pipeline_service/pipeline_execution_policy_spec.rb
@@ -587,6 +587,59 @@
     end
   end
 
+  describe 'access to policy configs inside security policy project repository' do
+    let(:namespace_policy) do
+      build(:pipeline_execution_policy,
+        content: { include: [{
+          project: namespace_policies_project.full_path,
+          file: namespace_policy_file,
+          ref: namespace_policies_project.default_branch_or_main
+        }] })
+    end
+
+    let(:project_policy) do
+      build(:pipeline_execution_policy,
+        content: { include: [{
+          project: project_policies_project.full_path,
+          file: project_policy_file,
+          ref: project_policies_project.default_branch_or_main
+        }] })
+    end
+
+    around do |example|
+      create_and_delete_files(
+        project_policies_project, project_policy_file => project_policy_content.to_yaml
+      ) do
+        create_and_delete_files(
+          namespace_policies_project, namespace_policy_file => namespace_policy_content.to_yaml
+        ) do
+          example.run
+        end
+      end
+    end
+
+    context 'when user does not have access to the policy repository' do
+      it 'responds with error' do
+        expect(execute).to be_error
+        expect(execute.payload.errors.full_messages)
+          .to contain_exactly(
+            "Pipeline execution policy error: Project `#{project_policies_project.full_path}` not found " \
+              "or access denied! Make sure any includes in the pipeline configuration are correctly defined.")
+      end
+
+      context 'when security policy projects enable `spp_repository_pipeline_access` project setting' do
+        before do
+          project_policies_project.project_setting.update!(spp_repository_pipeline_access: true)
+          namespace_policies_project.project_setting.update!(spp_repository_pipeline_access: true)
+        end
+
+        it 'responds with success' do
+          expect(execute).to be_success
+        end
+      end
+    end
+  end
+
   private
 
   def get_job_variable(job, key)
diff --git a/lib/gitlab/ci/config/external/file/project.rb b/lib/gitlab/ci/config/external/file/project.rb
index de726b57053b456b1b9e5e5959a99a36a695dd2f..d80323589dc6834ddae3fc8dda635fbfac72f2ce 100644
--- a/lib/gitlab/ci/config/external/file/project.rb
+++ b/lib/gitlab/ci/config/external/file/project.rb
@@ -90,12 +90,16 @@ def can_access_local_content?
                          .batch(key: context.user) do |projects, loader, args|
                 projects.uniq.each do |project|
                   context.logger.instrument(:config_file_project_validate_access) do
-                    loader.call(project, Ability.allowed?(args[:key], :download_code, project))
+                    loader.call(project, project_access_allowed?(args[:key], project))
                   end
                 end
               end
             end
 
+            def project_access_allowed?(user, project)
+              Ability.allowed?(user, :download_code, project)
+            end
+
             def sha
               return if project.nil?
 
@@ -179,3 +183,5 @@ def get_project_name(project_name)
     end
   end
 end
+
+Gitlab::Ci::Config::External::File::Project.prepend_mod
diff --git a/lib/gitlab/ci/project_config.rb b/lib/gitlab/ci/project_config.rb
index f92b84abfa9afc0badea96b04c7e5f08928b061c..bb185cf0a7d6f4d13ff701873c7386737442895d 100644
--- a/lib/gitlab/ci/project_config.rb
+++ b/lib/gitlab/ci/project_config.rb
@@ -49,7 +49,7 @@ def initialize(
         end
       end
 
-      delegate :content, :source, :url, to: :@config, allow_nil: true
+      delegate :content, :source, :url, :pipeline_policy_context, to: :@config, allow_nil: true
       delegate :internal_include_prepended?, to: :@config
 
       def exists?
diff --git a/lib/gitlab/ci/project_config/source.rb b/lib/gitlab/ci/project_config/source.rb
index 755640ad965aab85d37d2e18ed2617bf0a86eb8b..7288ee7fcd314ade3e08abe1c1cb096f9a86d387 100644
--- a/lib/gitlab/ci/project_config/source.rb
+++ b/lib/gitlab/ci/project_config/source.rb
@@ -42,10 +42,12 @@ def url
           nil
         end
 
+        attr_reader :pipeline_policy_context
+
         private
 
         attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge, :triggered_for_branch,
-          :ref, :pipeline_policy_context
+          :ref
 
         def ci_config_path
           @ci_config_path ||= project.ci_config_path_or_default
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index f4599f03a3ee1f8c4505e379633d06f5e147f819..7a55b8e7416509095ff1d7db29cf273a41331e0d 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -190,6 +190,7 @@ project_setting:
     - duo_features_enabled
     - require_reauthentication_to_approve
     - observability_alerts_enabled
+    - spp_repository_pipeline_access
 
 build_service_desk_setting: # service_desk_setting
   unexposed_attributes: