diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index 5052d84378fc599554bb956215a08c69a535ccc9..77fb72382984d1b39ed076c8b2f1ffe146fd5326 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -266,6 +266,12 @@ def forward_pipeline_variables?
       end
     end
 
+    def expand_file_refs?
+      strong_memoize(:expand_file_refs) do
+        !Feature.enabled?(:ci_prevent_file_var_expansion_downstream_pipeline, project)
+      end
+    end
+
     private
 
     def cross_project_params
diff --git a/config/feature_flags/development/ci_prevent_file_var_expansion_downstream_pipeline.yml b/config/feature_flags/development/ci_prevent_file_var_expansion_downstream_pipeline.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f0bcacbe2bd44f4657a8d757a53d4acfb3bccade
--- /dev/null
+++ b/config/feature_flags/development/ci_prevent_file_var_expansion_downstream_pipeline.yml
@@ -0,0 +1,8 @@
+---
+name: ci_prevent_file_var_expansion_downstream_pipeline
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124320
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414583
+milestone: '16.3'
+type: development
+group: group::pipeline security
+default_enabled: false
diff --git a/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb b/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb
index 6690e9f1c1f2fb6b4a3a4cbc000362b94c31b101..bb7a6e7ab59b2fd5a489dbd7bef2396eaa2fbaf2 100644
--- a/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb
+++ b/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb
@@ -6,9 +6,40 @@ module Variables
       module Downstream
         class ExpandableVariableGenerator < Base
           def for(item)
-            expanded_value = ::ExpandVariables.expand(item.value, context.all_bridge_variables)
+            expanded_var = expanded_var_for(item)
+            file_vars = file_var_dependencies_for(item)
 
-            [{ key: item.key, value: expanded_value }]
+            [expanded_var].concat(file_vars)
+          end
+
+          private
+
+          def expanded_var_for(item)
+            {
+              key: item.key,
+              value: ::ExpandVariables.expand(
+                item.value,
+                context.all_bridge_variables,
+                expand_file_refs: context.expand_file_refs
+              )
+            }
+          end
+
+          def file_var_dependencies_for(item)
+            return [] if context.expand_file_refs
+            return [] unless item.depends_on
+
+            item.depends_on.filter_map do |dependency|
+              dependency_variable = context.all_bridge_variables[dependency]
+
+              if dependency_variable&.file?
+                {
+                  key: dependency_variable.key,
+                  value: dependency_variable.value,
+                  variable_type: :file
+                }
+              end
+            end
           end
         end
       end
diff --git a/lib/gitlab/ci/variables/downstream/generator.rb b/lib/gitlab/ci/variables/downstream/generator.rb
index 93c995cc918fe18b32f41301df546ea07e3ad8cc..350d29958cfc12ab14e8bdc0e2c5098262f29741 100644
--- a/lib/gitlab/ci/variables/downstream/generator.rb
+++ b/lib/gitlab/ci/variables/downstream/generator.rb
@@ -7,12 +7,12 @@ module Downstream
         class Generator
           include Gitlab::Utils::StrongMemoize
 
-          Context = Struct.new(:all_bridge_variables, keyword_init: true)
+          Context = Struct.new(:all_bridge_variables, :expand_file_refs, keyword_init: true)
 
           def initialize(bridge)
             @bridge = bridge
 
-            context = Context.new(all_bridge_variables: bridge.variables)
+            context = Context.new(all_bridge_variables: bridge.variables, expand_file_refs: bridge.expand_file_refs?)
 
             @raw_variable_generator = RawVariableGenerator.new(context)
             @expandable_variable_generator = ExpandableVariableGenerator.new(context)
diff --git a/qa/qa/specs/features/api/4_verify/file_variable_downstream_pipeline_spec.rb b/qa/qa/specs/features/api/4_verify/file_variable_downstream_pipeline_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..95cc6d95c90defe743e33dd5743f80ef0a2c44fc
--- /dev/null
+++ b/qa/qa/specs/features/api/4_verify/file_variable_downstream_pipeline_spec.rb
@@ -0,0 +1,241 @@
+# frozen_string_literal: true
+
+module QA
+  RSpec.describe 'Verify', :runner, product_group: :pipeline_security, feature_flag: {
+    name: 'ci_prevent_file_var_expansion_downstream_pipeline',
+    scope: :project
+  } do
+    describe 'Pipeline with file variables and downstream pipelines' do
+      let(:random_string) { Faker::Alphanumeric.alphanumeric(number: 8) }
+      let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }
+
+      let!(:project) do
+        Resource::Project.fabricate_via_api! do |project|
+          project.name = 'upstream-project-with-file-variables'
+        end
+      end
+
+      let!(:downstream_project) do
+        Resource::Project.fabricate_via_api! do |project|
+          project.name = 'downstream-project'
+        end
+      end
+
+      let!(:project_runner) do
+        Resource::ProjectRunner.fabricate! do |runner|
+          runner.project = project
+          runner.name = executor
+          runner.tags = [executor]
+        end
+      end
+
+      let!(:downstream_project_runner) do
+        Resource::ProjectRunner.fabricate! do |runner|
+          runner.project = downstream_project
+          runner.name = "#{executor}-downstream"
+          runner.tags = [executor]
+        end
+      end
+
+      let(:add_ci_file) do
+        Resource::Repository::Commit.fabricate_via_api! do |commit|
+          commit.project = project
+          commit.commit_message = 'Add .gitlab-ci.yml and child.yml'
+          commit.add_files(
+            [
+              {
+                file_path: '.gitlab-ci.yml',
+                content: <<~YAML
+                  default:
+                    tags: [#{executor}]
+
+                  variables:
+                    EXTRA_ARGS: "-f $TEST_PROJECT_FILE"
+                    DOCKER_REMOTE_ARGS: --tlscacert="$DOCKER_CA_CERT"
+                    EXTRACTED_CRT_FILE: ${DOCKER_CA_CERT}.crt
+                    MY_FILE_VAR: $TEST_PROJECT_FILE
+
+                  trigger_child:
+                    trigger:
+                      strategy: depend
+                      include:
+                        - local: child.yml
+
+                  trigger_downstream_project:
+                    trigger:
+                      strategy: depend
+                      project: #{downstream_project.path_with_namespace}
+
+                YAML
+              },
+              {
+                file_path: 'child.yml',
+                content: <<~YAML
+                  default:
+                    tags: [#{executor}]
+
+                  child_job_echo:
+                    script:
+                      - echo "run something $EXTRA_ARGS"
+                      - echo "docker run $DOCKER_REMOTE_ARGS"
+                      - echo "run --output=$EXTRACTED_CRT_FILE"
+                      - echo "Will read private key from $MY_FILE_VAR"
+
+                  child_job_cat:
+                    script:
+                      - cat "$MY_FILE_VAR"
+                      - cat "$DOCKER_CA_CERT"
+                YAML
+              }
+            ]
+          )
+        end
+      end
+
+      let(:add_downstream_project_ci_file) do
+        Resource::Repository::Commit.fabricate_via_api! do |commit|
+          commit.project = downstream_project
+          commit.commit_message = 'Add .gitlab-ci.yml'
+          commit.add_files(
+            [
+              {
+                file_path: '.gitlab-ci.yml',
+                content: <<~YAML
+                  default:
+                    tags: [#{executor}]
+
+                  downstream_job_echo:
+                    script:
+                      - echo "run something $EXTRA_ARGS"
+                      - echo "docker run $DOCKER_REMOTE_ARGS"
+                      - echo "run --output=$EXTRACTED_CRT_FILE"
+                      - echo "Will read private key from $MY_FILE_VAR"
+
+                  downstream_job_cat:
+                    script:
+                      - cat "$MY_FILE_VAR"
+                      - cat "$DOCKER_CA_CERT"
+                YAML
+              }
+            ]
+          )
+        end
+      end
+
+      let(:add_project_file_variables) do
+        {
+          'TEST_PROJECT_FILE' => "hello, this is test\n",
+          'DOCKER_CA_CERT' => "This is secret\n"
+        }.each do |file_name, content|
+          add_file_variable_to_project(project, file_name, content)
+        end
+      end
+
+      let(:upstream_pipeline) do
+        Resource::Pipeline.fabricate_via_api! do |pipeline|
+          pipeline.project = project
+        end
+      end
+
+      def child_pipeline
+        Resource::Pipeline.fabricate_via_api! do |pipeline|
+          pipeline.project = project
+          pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'trigger_child')
+        end
+      end
+
+      def downstream_project_pipeline
+        Resource::Pipeline.fabricate_via_api! do |pipeline|
+          pipeline.project = downstream_project
+          pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'trigger_downstream_project')
+        end
+      end
+
+      around do |example|
+        Runtime::Feature.enable(:ci_prevent_file_var_expansion_downstream_pipeline, project: project)
+        example.run
+        Runtime::Feature.disable(:ci_prevent_file_var_expansion_downstream_pipeline, project: project)
+      end
+
+      before do
+        add_project_file_variables
+        add_downstream_project_ci_file
+        add_ci_file
+        upstream_pipeline
+        wait_for_pipelines
+      end
+
+      after do
+        project_runner.remove_via_api!
+        downstream_project_runner.remove_via_api!
+      end
+
+      it(
+        'creates variable with file path in downstream pipelines and can read file variable content',
+        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/416337'
+      ) do
+        child_echo_job = Resource::Job.fabricate_via_api! do |job|
+          job.project = project
+          job.id = project.job_by_name('child_job_echo')[:id]
+        end
+
+        child_cat_job = Resource::Job.fabricate_via_api! do |job|
+          job.project = project
+          job.id = project.job_by_name('child_job_cat')[:id]
+        end
+
+        downstream_project_echo_job = Resource::Job.fabricate_via_api! do |job|
+          job.project = downstream_project
+          job.id = downstream_project.job_by_name('downstream_job_echo')[:id]
+        end
+
+        downstream_project_cat_job = Resource::Job.fabricate_via_api! do |job|
+          job.project = downstream_project
+          job.id = downstream_project.job_by_name('downstream_job_cat')[:id]
+        end
+
+        aggregate_failures do
+          trace = child_echo_job.trace
+          expect(trace).to include('run something -f', "#{project.name}.tmp/TEST_PROJECT_FILE")
+          expect(trace).to include('docker run --tlscacert=', "#{project.name}.tmp/DOCKER_CA_CERT")
+          expect(trace).to include('run --output=', "#{project.name}.tmp/DOCKER_CA_CERT.crt")
+          expect(trace).to include('Will read private key from', "#{project.name}.tmp/TEST_PROJECT_FILE")
+
+          trace = child_cat_job.trace
+          expect(trace).to have_content('hello, this is test')
+          expect(trace).to have_content('This is secret')
+
+          trace = downstream_project_echo_job.trace
+          expect(trace).to include('run something -f', "#{downstream_project.name}.tmp/TEST_PROJECT_FILE")
+          expect(trace).to include('docker run --tlscacert=', "#{downstream_project.name}.tmp/DOCKER_CA_CERT")
+          expect(trace).to include('run --output=', "#{downstream_project.name}.tmp/DOCKER_CA_CERT.crt")
+          expect(trace).to include('Will read private key from', "#{downstream_project.name}.tmp/TEST_PROJECT_FILE")
+
+          trace = downstream_project_cat_job.trace
+          expect(trace).to have_content('hello, this is test')
+          expect(trace).to have_content('This is secret')
+        end
+      end
+
+      private
+
+      def add_file_variable_to_project(project, key, value)
+        Resource::CiVariable.fabricate_via_api! do |ci_variable|
+          ci_variable.project = project
+          ci_variable.key = key
+          ci_variable.value = value
+          ci_variable.variable_type = 'file'
+        end
+      end
+
+      def wait_for_pipelines
+        Support::Waiter.wait_until(max_duration: 300, sleep_interval: 10) do
+          upstream_pipeline.reload!
+          upstream_pipeline.status == 'success' &&
+            child_pipeline.status == 'success' &&
+            downstream_project_pipeline.status == 'success'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
index 5b33527e06ce03d65ef46adce4474a2a60487c96..95d0f089f6d992c35a94c59929aed81f96859c9e 100644
--- a/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
+++ b/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
@@ -7,13 +7,19 @@
     Gitlab::Ci::Variables::Collection.fabricate(
       [
         { key: 'REF1', value: 'ref 1' },
-        { key: 'REF2', value: 'ref 2' }
+        { key: 'REF2', value: 'ref 2' },
+        { key: 'NESTED_REF1', value: 'nested $REF1' }
       ]
     )
   end
 
+  let(:expand_file_refs) { false }
+
   let(:context) do
-    Gitlab::Ci::Variables::Downstream::Generator::Context.new(all_bridge_variables: all_bridge_variables)
+    Gitlab::Ci::Variables::Downstream::Generator::Context.new(
+      all_bridge_variables: all_bridge_variables,
+      expand_file_refs: expand_file_refs
+    )
   end
 
   subject(:generator) { described_class.new(context) }
@@ -34,5 +40,54 @@
         expect(generator.for(var)).to match_array([{ key: 'VAR1', value: 'ref 1 ref 2 ' }])
       end
     end
+
+    context 'when given a variable with nested interpolation' do
+      it 'returns an array containing the expanded variables' do
+        var = Gitlab::Ci::Variables::Collection::Item.fabricate({ key: 'VAR1', value: '$REF1 $REF2 $NESTED_REF1' })
+
+        expect(generator.for(var)).to match_array([{ key: 'VAR1', value: 'ref 1 ref 2 nested $REF1' }])
+      end
+    end
+
+    context 'when given a variable with expansion on a file variable' do
+      let(:all_bridge_variables) do
+        Gitlab::Ci::Variables::Collection.fabricate(
+          [
+            { key: 'REF1', value: 'ref 1' },
+            { key: 'FILE_REF2', value: 'ref 2', file: true },
+            { key: 'NESTED_REF3', value: 'ref 3 $REF1 and $FILE_REF2', file: true }
+          ]
+        )
+      end
+
+      context 'when expand_file_refs is false' do
+        let(:expand_file_refs) { false }
+
+        it 'returns an array containing the unexpanded variable and the file variable dependency' do
+          var = { key: 'VAR1', value: '$REF1 $FILE_REF2 $FILE_REF3 $NESTED_REF3' }
+          var = Gitlab::Ci::Variables::Collection::Item.fabricate(var)
+
+          expected = [
+            { key: 'VAR1', value: 'ref 1 $FILE_REF2  $NESTED_REF3' },
+            { key: 'FILE_REF2', value: 'ref 2', variable_type: :file },
+            { key: 'NESTED_REF3', value: 'ref 3 $REF1 and $FILE_REF2', variable_type: :file }
+          ]
+
+          expect(generator.for(var)).to match_array(expected)
+        end
+      end
+
+      context 'when expand_file_refs is true' do
+        let(:expand_file_refs) { true }
+
+        it 'returns an array containing the expanded variables' do
+          var = { key: 'VAR1', value: '$REF1 $FILE_REF2 $FILE_REF3 $NESTED_REF3' }
+          var = Gitlab::Ci::Variables::Collection::Item.fabricate(var)
+
+          expected = { key: 'VAR1', value: 'ref 1 ref 2  ref 3 $REF1 and $FILE_REF2' }
+          expect(generator.for(var)).to contain_exactly(expected)
+        end
+      end
+    end
   end
 end
diff --git a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
index 61e8b9a8c4ad4dd8af294c2ea82cb23e62067c7c..cd68b0cdf2bb71c85ee316d89d4576bbbf3530cf 100644
--- a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
+++ b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
@@ -45,6 +45,7 @@
       variables: bridge_variables,
       forward_yaml_variables?: true,
       forward_pipeline_variables?: true,
+      expand_file_refs?: false,
       yaml_variables: yaml_variables,
       pipeline_variables: pipeline_variables,
       pipeline_schedule_variables: pipeline_schedule_variables
@@ -81,5 +82,61 @@
 
       expect(generator.calculate).to be_empty
     end
+
+    context 'with file variable interpolation' do
+      let(:bridge_variables) do
+        Gitlab::Ci::Variables::Collection.fabricate(
+          [
+            { key: 'REF1', value: 'ref 1' },
+            { key: 'FILE_REF3', value: 'ref 3', file: true }
+          ]
+        )
+      end
+
+      let(:yaml_variables) do
+        [{ key: 'INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+      end
+
+      let(:pipeline_variables) do
+        [{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+      end
+
+      let(:pipeline_schedule_variables) do
+        [{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+      end
+
+      context 'when expand_file_refs is true' do
+        before do
+          allow(bridge).to receive(:expand_file_refs?).and_return(true)
+        end
+
+        it 'expands file variables' do
+          expected = [
+            { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1  ref 3 ' },
+            { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1  ref 3 ' },
+            { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1  ref 3 ' }
+          ]
+
+          expect(generator.calculate).to contain_exactly(*expected)
+        end
+      end
+
+      context 'when expand_file_refs is false' do
+        before do
+          allow(bridge).to receive(:expand_file_refs?).and_return(false)
+        end
+
+        it 'does not expand file variables and adds file variables' do
+          expected = [
+            { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1  $FILE_REF3 ' },
+            { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1  $FILE_REF3 ' },
+            { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1  $FILE_REF3 ' },
+            { key: 'FILE_REF3', value: 'ref 3', variable_type: :file }
+          ]
+
+          expect(generator.calculate).to contain_exactly(*expected)
+        end
+      end
+    end
   end
 end
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index d93250af1777ec6e55f0c7d3bffd4f357ab5f02c..b284cacf3545246f370b983242fe56314bb163f1 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -3,7 +3,7 @@
 require 'spec_helper'
 
 RSpec.describe Ci::Bridge, feature_category: :continuous_integration do
-  let_it_be(:project) { create(:project) }
+  let_it_be(:project) { create(:project, :in_group) }
   let_it_be(:target_project) { create(:project, name: 'project', namespace: create(:namespace, name: 'my')) }
   let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
 
@@ -196,11 +196,18 @@
   end
 
   describe '#downstream_variables' do
+    # A new pipeline needs to be created in each test.
+    # The pipeline #variables_builder is memoized. The builder internally also memoizes variables.
+    # Having pipeline in a let_it_be might lead to flaky tests
+    # because a test might expect new variables but the variables builder does not
+    # return the new variables due to memoized results from previous tests.
+    let(:pipeline) { create(:ci_pipeline, project: project) }
+
     subject(:downstream_variables) { bridge.downstream_variables }
 
     it 'returns variables that are going to be passed downstream' do
       expect(bridge.downstream_variables)
-        .to include(key: 'BRIDGE', value: 'cross')
+        .to contain_exactly(key: 'BRIDGE', value: 'cross')
     end
 
     context 'when using variables interpolation' do
@@ -241,14 +248,49 @@
       end
     end
 
+    context 'when using variables interpolation on file variables' do
+      let(:yaml_variables) do
+        [
+          {
+            key: 'EXPANDED_FILE',
+            value: '$TEST_FILE_VAR'
+          }
+        ]
+      end
+
+      before do
+        bridge.yaml_variables = yaml_variables
+        create(:ci_variable, :file, project: bridge.pipeline.project, key: 'TEST_FILE_VAR', value: 'test-file-value')
+      end
+
+      it 'does not expand file variable and forwards the file variable' do
+        expected_vars = [
+          { key: 'EXPANDED_FILE', value: '$TEST_FILE_VAR' },
+          { key: 'TEST_FILE_VAR', value: 'test-file-value', variable_type: :file }
+        ]
+
+        expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
+      end
+
+      context 'and feature flag is disabled' do
+        before do
+          stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
+        end
+
+        it 'expands the file variable' do
+          expect(bridge.downstream_variables).to contain_exactly({ key: 'EXPANDED_FILE', value: 'test-file-value' })
+        end
+      end
+    end
+
     context 'when recursive interpolation has been used' do
       before do
-        bridge.yaml_variables << { key: 'EXPANDED', value: '$EXPANDED', public: true }
+        bridge.yaml_variables = [{ key: 'EXPANDED', value: '$EXPANDED', public: true }]
       end
 
       it 'does not expand variable recursively' do
         expect(bridge.downstream_variables)
-          .to include(key: 'EXPANDED', value: '$EXPANDED')
+          .to contain_exactly(key: 'EXPANDED', value: '$EXPANDED')
       end
     end
 
@@ -279,26 +321,82 @@
           }
         end
 
+        before do
+          create(:ci_pipeline_variable, pipeline: pipeline, key: 'PVAR1', value: 'PVAL1')
+        end
+
         it 'returns variables according to the forward value' do
           expect(bridge.downstream_variables.map { |v| v[:key] }).to contain_exactly(*variables)
         end
       end
 
       context 'when sending a variable via both yaml and pipeline' do
-        let(:pipeline) { create(:ci_pipeline, project: project) }
-
         let(:options) do
           { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
         end
 
         before do
-          create(:ci_pipeline_variable, pipeline: pipeline, key: 'BRIDGE', value: 'new value')
+          bridge.yaml_variables = [{ key: 'SHARED_KEY', value: 'old_value' }]
+          create(:ci_pipeline_variable, pipeline: pipeline, key: 'SHARED_KEY', value: 'new value')
         end
 
         it 'uses the pipeline variable' do
-          expect(bridge.downstream_variables).to contain_exactly(
-            { key: 'BRIDGE', value: 'new value' }
-          )
+          expect(bridge.downstream_variables).to contain_exactly({ key: 'SHARED_KEY', value: 'new value' })
+        end
+      end
+
+      context 'when sending a file variable from pipeline variable' do
+        let(:options) do
+          { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
+        end
+
+        before do
+          bridge.yaml_variables = [{ key: 'FILE_VAR', value: 'old_value' }]
+          create(:ci_pipeline_variable, :file, pipeline: pipeline, key: 'FILE_VAR', value: 'new value')
+        end
+
+        # The current behaviour forwards the file variable as an environment variable.
+        # TODO: decide whether to forward as a file var in https://gitlab.com/gitlab-org/gitlab/-/issues/416334
+        it 'forwards the pipeline file variable' do
+          expect(bridge.downstream_variables).to contain_exactly({ key: 'FILE_VAR', value: 'new value' })
+        end
+      end
+
+      context 'when a pipeline variable interpolates a scoped file variable' do
+        let(:options) do
+          { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
+        end
+
+        before do
+          bridge.yaml_variables = [{ key: 'YAML_VAR', value: '$PROJECT_FILE_VAR' }]
+
+          create(:ci_variable, :file, project: pipeline.project, key: 'PROJECT_FILE_VAR', value: 'project file')
+          create(:ci_pipeline_variable, pipeline: pipeline, key: 'FILE_VAR', value: '$PROJECT_FILE_VAR')
+        end
+
+        it 'does not expand the scoped file variable and forwards the file variable' do
+          expected_vars = [
+            { key: 'FILE_VAR', value: '$PROJECT_FILE_VAR' },
+            { key: 'YAML_VAR', value: '$PROJECT_FILE_VAR' },
+            { key: 'PROJECT_FILE_VAR', value: 'project file', variable_type: :file }
+          ]
+
+          expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
+        end
+
+        context 'and feature flag is disabled' do
+          before do
+            stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
+          end
+
+          it 'expands the file variable' do
+            expected_vars = [
+              { key: 'FILE_VAR', value: 'project file' },
+              { key: 'YAML_VAR', value: 'project file' }
+            ]
+
+            expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
+          end
         end
       end
 
@@ -315,10 +413,66 @@
         end
 
         it 'adds the schedule variable' do
-          expect(bridge.downstream_variables).to contain_exactly(
+          expected_vars = [
             { key: 'BRIDGE', value: 'cross' },
             { key: 'schedule_var_key', value: 'schedule var value' }
-          )
+          ]
+
+          expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
+        end
+      end
+    end
+
+    context 'when sending a file variable from pipeline schedule' do
+      let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
+      let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
+
+      let(:options) do
+        { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
+      end
+
+      before do
+        bridge.yaml_variables = []
+        pipeline_schedule.variables.create!(key: 'schedule_var_key', value: 'schedule var value', variable_type: :file)
+      end
+
+      # The current behaviour forwards the file variable as an environment variable.
+      # TODO: decide whether to forward as a file var in https://gitlab.com/gitlab-org/gitlab/-/issues/416334
+      it 'forwards the schedule file variable' do
+        expect(bridge.downstream_variables).to contain_exactly({ key: 'schedule_var_key', value: 'schedule var value' })
+      end
+    end
+
+    context 'when a pipeline schedule variable interpolates a scoped file variable' do
+      let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
+      let(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
+
+      let(:options) do
+        { trigger: { project: 'my/project', forward: { pipeline_variables: true } } }
+      end
+
+      before do
+        bridge.yaml_variables = []
+        create(:ci_variable, :file, project: pipeline.project, key: 'PROJECT_FILE_VAR', value: 'project file')
+        pipeline_schedule.variables.create!(key: 'schedule_var_key', value: '$PROJECT_FILE_VAR')
+      end
+
+      it 'does not expand the scoped file variable and forwards the file variable' do
+        expected_vars = [
+          { key: 'schedule_var_key', value: '$PROJECT_FILE_VAR' },
+          { key: 'PROJECT_FILE_VAR', value: 'project file', variable_type: :file }
+        ]
+
+        expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
+      end
+
+      context 'and feature flag is disabled' do
+        before do
+          stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
+        end
+
+        it 'expands the file variable' do
+          expect(bridge.downstream_variables).to contain_exactly({ key: 'schedule_var_key', value: 'project file' })
         end
       end
     end