diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 29091eb93ef1a2f40ff37247fedd104c1e3dc7e4..62024bff4c080d12936117b3d79de2b675c40bb5 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -115,6 +115,15 @@ def open
     end
   end
 
+  def multi_read(offsets)
+    open do |stream|
+      offsets.map do |start_offset, end_offset|
+        stream.seek(start_offset)
+        stream.read(end_offset - start_offset + 1)
+      end
+    end
+  end
+
   # Used to replace an existing upload with another +file+ without modifying stored metadata
   # Use this method only to repair/replace an existing upload, or to upload to a Geo secondary node
   #
diff --git a/ee/app/finders/security/findings_finder.rb b/ee/app/finders/security/findings_finder.rb
index 19f127fa01f7a64f00295b9afc5d8d9acee4300c..7846422a8b5c431b7d92983ce4953d8c714c14ff 100644
--- a/ee/app/finders/security/findings_finder.rb
+++ b/ee/app/finders/security/findings_finder.rb
@@ -19,7 +19,8 @@ class FindingsFinder
     include ::VulnerabilityFindingHelpers
 
     ResultSet = Struct.new(:relation, :findings) do
-      delegate :current_page, :limit_value, :total_pages, :total_count, :next_page, :prev_page, to: :relation
+      delegate :current_page, :limit_value, :total_pages, :total_count,
+               :next_page, :prev_page, :page, :empty?, to: :relation
     end
 
     DEFAULT_PAGE = 1
@@ -31,7 +32,7 @@ def initialize(pipeline, params: {})
     end
 
     def execute
-      return unless has_security_findings?
+      return ResultSet.new(Security::Finding.none.page(DEFAULT_PAGE), []) unless has_security_findings?
 
       ResultSet.new(security_findings, findings)
     end
diff --git a/ee/app/finders/security/pure_findings_finder.rb b/ee/app/finders/security/pure_findings_finder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b123a0b4a06c7c317122c7f6e3e47f8dde1b159e
--- /dev/null
+++ b/ee/app/finders/security/pure_findings_finder.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+# Security::PureFindingsFinder
+#
+# This finder returns Active Record relation of `Security::Finding` model
+# which is different than the other finders maintained by threat insights.
+# It's called pure because it does not depend on report artifacts(mostly)
+# and uses the data stored in the `Security::Finding` model.
+#
+# Arguments:
+#   pipeline - object to filter findings
+#   params:
+#     severity:    Array<String>
+#     confidence:  Array<String>
+#     report_type: Array<String>
+#     scope:       String
+#     page:        Int
+#     per_page:    Int
+
+module Security
+  class PureFindingsFinder < FindingsFinder
+    def execute
+      security_findings.tap do |findings|
+        findings.each(&:remediations) # initiates Batchloader
+      end
+    end
+
+    def available?
+      pipeline.security_findings.first&.finding_data.present?
+    end
+
+    private
+
+    def security_findings
+      super.with_feedbacks
+           .with_vulnerability
+    end
+  end
+end
diff --git a/ee/app/models/security/finding.rb b/ee/app/models/security/finding.rb
index e12f1d68f6547015b3617c61085f75589a7bd780..e61b4855f8819c44f10e2dc87a83c077ade126a1 100644
--- a/ee/app/models/security/finding.rb
+++ b/ee/app/models/security/finding.rb
@@ -10,9 +10,12 @@
 module Security
   class Finding < ApplicationRecord
     include EachBatch
+    include Presentable
     include PartitionedTable
 
     MAX_PARTITION_SIZE = 10.gigabyte
+    ATTRIBUTES_DELEGATED_TO_FINDING_DATA = %i[name description solution location identifiers links false_positive?
+                                              assets evidence details remediation_byte_offsets].freeze
 
     self.table_name = 'security_findings'
     self.primary_key = :id # As ActiveRecord does not support compound PKs
@@ -26,8 +29,15 @@ class Finding < ApplicationRecord
 
     belongs_to :scan, inverse_of: :findings, optional: false
     belongs_to :scanner, class_name: 'Vulnerabilities::Scanner', inverse_of: :security_findings, optional: false
+    belongs_to :vulnerability_read,
+               class_name: 'Vulnerabilities::Read',
+               primary_key: :uuid,
+               foreign_key: :uuid,
+               inverse_of: :security_findings
 
     has_one :build, through: :scan, disable_joins: true
+    has_one :vulnerability, through: :vulnerability_read
+
     has_many :feedbacks,
              class_name: 'Vulnerabilities::Feedback',
              inverse_of: :security_finding,
@@ -62,15 +72,17 @@ class Finding < ApplicationRecord
                 .where('vulnerability_feedback.finding_uuid = security_findings.uuid'))
     end
     scope :latest, -> { joins(:scan).merge(Security::Scan.latest_successful) }
-    scope :ordered, -> { order(severity: :desc, confidence: :desc, id: :asc) }
+    scope :ordered, -> { order(severity: :desc, id: :asc) }
     scope :with_pipeline_entities, -> { preload(build: [:job_artifacts, :pipeline]) }
-    scope :with_scan, -> { includes(:scan) }
+    scope :with_scan, -> { preload(:scan) }
     scope :with_scanner, -> { includes(:scanner) }
+    scope :with_feedbacks, -> { includes(:feedbacks) }
+    scope :with_vulnerability, -> { includes(:vulnerability) }
     scope :deduplicated, -> { where(deduplicated: true) }
     scope :grouped_by_scan_type, -> { joins(:scan).group('security_scans.scan_type') }
 
-    delegate :scan_type, :pipeline, to: :scan, allow_nil: true
-    delegate :project, to: :pipeline
+    delegate :scan_type, :pipeline, :remediations_proxy, to: :scan, allow_nil: true
+    delegate :project, :sha, to: :pipeline
 
     class << self
       def count_by_scan_type
@@ -103,5 +115,56 @@ def last_finding_in_partition(partition_number)
         where(partition_number: partition_number).last
       end
     end
+
+    # Following alias attributes as used by `Vulnerabilities::FindingEntity`
+    alias_attribute :raw_metadata, :finding_data
+    alias_attribute :report_type, :scan_type
+
+    def dismissal_feedback
+      feedbacks.find(&:for_dismissal?)
+    end
+
+    def issue_feedback
+      feedbacks.find(&:for_issue?)
+    end
+
+    def merge_request_feedback
+      feedbacks.find(&:for_merge_request?)
+    end
+
+    def state
+      return vulnerability.state if vulnerability
+
+      dismissal_feedback ? 'dismissed' : 'detected'
+    end
+
+    # Symbolizing the hash keys is important as Grape entity
+    # works with symbolized keys only.
+    # See https://github.com/ruby-grape/grape-entity/issues/223
+    def symbolized_finding_data
+      @symbolized_finding_data ||= finding_data.deep_symbolize_keys
+    end
+
+    def finding_data=(value)
+      super
+    ensure
+      @symbolized_finding_data = nil
+    end
+
+    # Defines methods for the keys exist in `finding_data` to support the same
+    # interface with `Vulnerabilities::Finding` model as these methods are used
+    # by `Vulnerabilities::FindingEntity`.
+    ATTRIBUTES_DELEGATED_TO_FINDING_DATA.each do |delegated_attribute|
+      define_method(delegated_attribute) do
+        symbolized_finding_data.fetch(delegated_attribute)
+      end
+    end
+
+    def remediations
+      return [] unless symbolized_finding_data[:remediation_byte_offsets]
+
+      symbolized_finding_data[:remediation_byte_offsets].map { |offset| offset.values_at(:start_byte, :end_byte) }
+                                                        .then { remediations_proxy.by_byte_offsets(_1) }
+    end
   end
 end
diff --git a/ee/app/models/security/remediations_proxy.rb b/ee/app/models/security/remediations_proxy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0c5f12726401470df976083ad19b9aa46ec67416
--- /dev/null
+++ b/ee/app/models/security/remediations_proxy.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Security
+  class RemediationsProxy
+    attr_reader :file
+
+    def initialize(file)
+      @file = file
+    end
+
+    def by_byte_offsets(byte_offsets)
+      byte_offsets.map do |offsets|
+        batch_load(offsets)
+      end
+    end
+
+    private
+
+    def batch_load(offsets)
+      BatchLoader.for(offsets).batch(key: object_id) do |byte_offsets, loader|
+        byte_offsets.uniq
+                    .sort
+                    .then { |offsets| file.multi_read(offsets) }
+                    .map { |data| Gitlab::Json.parse(data) }
+                    .zip(byte_offsets)
+                    .each { |remediation, offsets| loader.call(offsets, remediation) }
+      end
+    end
+  end
+end
diff --git a/ee/app/models/security/scan.rb b/ee/app/models/security/scan.rb
index 88a9417c6db98476df4131dd345eca8a4fc3d37b..756573f56a2fcce27c543cd159284b20c35332c9 100644
--- a/ee/app/models/security/scan.rb
+++ b/ee/app/models/security/scan.rb
@@ -107,6 +107,10 @@ def report_primary_identifiers
       @report_primary_identifiers ||= security_report&.primary_identifiers
     end
 
+    def remediations_proxy
+      @remediations_proxy ||= RemediationsProxy.new(job_artifact.file)
+    end
+
     private
 
     def security_report
diff --git a/ee/app/models/vulnerabilities/finding.rb b/ee/app/models/vulnerabilities/finding.rb
index 9f97eaff4a53c46ff084eaf3547650ead730e90c..fccbbf8bc5165d611ff107ba2afd036369f69d8b 100644
--- a/ee/app/models/vulnerabilities/finding.rb
+++ b/ee/app/models/vulnerabilities/finding.rb
@@ -380,7 +380,7 @@ def pipeline_branch
     end
 
     def false_positive?
-      vulnerability_flags.false_positive.any?
+      vulnerability_flags.any?(&:false_positive?)
     end
 
     def first_finding_pipeline
diff --git a/ee/app/models/vulnerabilities/read.rb b/ee/app/models/vulnerabilities/read.rb
index 5a244bde35b4f38d47ebfeb3936b5cfc6d79d798..f4299fcb56d4b7adbe7bb3de57cfebc11132c3c5 100644
--- a/ee/app/models/vulnerabilities/read.rb
+++ b/ee/app/models/vulnerabilities/read.rb
@@ -12,6 +12,12 @@ class Read < ApplicationRecord
     belongs_to :project
     belongs_to :scanner, class_name: 'Vulnerabilities::Scanner'
 
+    has_many :security_findings,
+             class_name: 'Security::Finding',
+             primary_key: :uuid,
+             foreign_key: :uuid,
+             inverse_of: :vulnerability_read
+
     validates :vulnerability_id, uniqueness: true, presence: true
     validates :project_id, presence: true
     validates :scanner_id, presence: true
diff --git a/ee/app/serializers/vulnerabilities/finding_entity.rb b/ee/app/serializers/vulnerabilities/finding_entity.rb
index f878020ed1e9719e0ed84064348e293d60fca831..ae5ec53908efc83ebbe815c961adbad728bb4186 100644
--- a/ee/app/serializers/vulnerabilities/finding_entity.rb
+++ b/ee/app/serializers/vulnerabilities/finding_entity.rb
@@ -13,9 +13,7 @@ class Vulnerabilities::FindingEntity < Grape::Entity
   expose :create_jira_issue_url do |occurrence|
     create_jira_issue_url_for(occurrence)
   end
-  expose :false_positive, if: -> (_, _) { expose_false_positive? } do |occurrence|
-    occurrence.vulnerability_flags.any?(&:false_positive?)
-  end
+  expose :false_positive?, as: :false_positive, if: -> (_, _) { expose_false_positive? }
   expose :create_vulnerability_feedback_issue_path do |occurrence|
     create_vulnerability_feedback_issue_path(occurrence.project)
   end
@@ -53,7 +51,7 @@ class Vulnerabilities::FindingEntity < Grape::Entity
   expose :scan
 
   expose :blob_path do |occurrence|
-    occurrence.present.blob_path
+    occurrence.present(presenter_class: Vulnerabilities::FindingPresenter).blob_path
   end
 
   alias_method :occurrence, :object
diff --git a/ee/config/feature_flags/development/utilize_finding_data.yml b/ee/config/feature_flags/development/utilize_finding_data.yml
new file mode 100644
index 0000000000000000000000000000000000000000..336bdce5ad097293b95336200e468d1ba428100d
--- /dev/null
+++ b/ee/config/feature_flags/development/utilize_finding_data.yml
@@ -0,0 +1,8 @@
+---
+name: utilize_finding_data
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103652
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383430
+milestone: '15.7'
+type: development
+group: group::threat insights
+default_enabled: false
diff --git a/ee/lib/api/vulnerability_findings.rb b/ee/lib/api/vulnerability_findings.rb
index 38f49165c42b5b427e06b68276eaa85b387b64bb..67d99891743724e07e05e85d9585fab4d03dccf2 100644
--- a/ee/lib/api/vulnerability_findings.rb
+++ b/ee/lib/api/vulnerability_findings.rb
@@ -23,31 +23,26 @@ def vulnerability_findings
         @vulnerability_findings ||= begin
           return paginate(Kaminari.paginate_array([])) unless pipeline
 
-          with_adaptive_finder || with_vulnerabilities_finder
+          with_pure_finder || with_adaptive_finder
         end
       end
 
-      # We are using this fallback to provide an early response to the users
-      # until the `security_findings` are stored.
-      # We will remove this fallback by https://gitlab.com/gitlab-org/gitlab/-/issues/334488
-      def with_vulnerabilities_finder
-        aggregated_report = Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: declared_params).execute
-
-        # We might have to add rubocop:disable annotation here in case
-        # https://gitlab.com/gitlab-org/gitlab/issues/32763 happens, using
-        # Kaminari.paginate_array here is correct
-        # See https://gitlab.com/gitlab-org/gitlab/issues/33588#note_291849433
-        # for discussion
-        paginate(Kaminari.paginate_array(aggregated_report.findings))
-      rescue Security::PipelineVulnerabilitiesFinder::ParseError
-        paginate(Kaminari.paginate_array([]))
+      def with_pure_finder
+        pure_finder = Security::PureFindingsFinder.new(pipeline, params: declared_params)
+        return unless Feature.enabled?(:utilize_finding_data, pipeline.project) && pure_finder.available?
+
+        paginate(pure_finder.execute)
       end
 
       def with_adaptive_finder
         result = Security::FindingsFinder.new(pipeline, params: declared_params).execute
-        return unless result
+        paginate(result)
+
+        if Feature.disabled?(:deprecate_vulnerabilities_feedback, user_project)
+          Gitlab::Vulnerabilities::FindingsPreloader.preload_feedback!(result.findings)
+        end
 
-        paginate(result).findings
+        result.findings
       end
     end
 
@@ -101,10 +96,6 @@ def with_adaptive_finder
       get ':id/vulnerability_findings' do
         authorize! :read_security_resource, user_project
 
-        if Feature.disabled?(:deprecate_vulnerabilities_feedback, user_project)
-          Gitlab::Vulnerabilities::FindingsPreloader.preload_feedback!(vulnerability_findings)
-        end
-
         present vulnerability_findings,
                 with: ::Vulnerabilities::FindingEntity,
                 request: GrapeRequestProxy.new(request, current_user)
diff --git a/ee/spec/factories/security_scans.rb b/ee/spec/factories/security_scans.rb
index 08d5a741a330bfcf9c46d7273a3d7f9621c205a9..2362ca73ec3672e9f09c6a569c8b30b267031b83 100644
--- a/ee/spec/factories/security_scans.rb
+++ b/ee/spec/factories/security_scans.rb
@@ -23,5 +23,26 @@
     trait :purged do
       status { :purged }
     end
+
+    trait :with_findings do
+      # rubocop:disable RSpec/FactoryBot/StrategyInCallback
+      after(:create) do |scan|
+        artifact = create(:ee_ci_job_artifact, scan.scan_type, job: scan.build, project: scan.project)
+        report = create(:ci_reports_security_report, pipeline: scan.pipeline, type: scan.scan_type)
+
+        Gitlab::Ci::Parsers.fabricate!(scan.scan_type, artifact.file.read, report).parse!
+        report = Security::MergeReportsService.new(report).execute
+
+        report.findings.each do |finding|
+          create(:security_finding,
+                 severity: finding.severity,
+                 confidence: finding.confidence,
+                 uuid: finding.uuid,
+                 deduplicated: true,
+                 scan: scan)
+        end
+      end
+      # rubocop:enable RSpec/FactoryBot/StrategyInCallback
+    end
   end
 end
diff --git a/ee/spec/finders/security/findings_finder_spec.rb b/ee/spec/finders/security/findings_finder_spec.rb
index 2310652e48de083a2917d25fb10616c8296141dc..dbf94aeb4c1f584d544922fe879c7be459cbb468 100644
--- a/ee/spec/finders/security/findings_finder_spec.rb
+++ b/ee/spec/finders/security/findings_finder_spec.rb
@@ -2,377 +2,56 @@
 
 require 'spec_helper'
 
-RSpec.describe Security::FindingsFinder do
-  let_it_be(:pipeline) { create(:ci_pipeline) }
-  let_it_be(:build_1) { create(:ci_build, :success, name: 'dependency_scanning', pipeline: pipeline) }
-  let_it_be(:build_2) { create(:ci_build, :success, name: 'sast', pipeline: pipeline) }
-  let_it_be(:artifact_ds) { create(:ee_ci_job_artifact, :dependency_scanning, job: build_1) }
-  let_it_be(:artifact_sast) { create(:ee_ci_job_artifact, :sast, job: build_2) }
-  let_it_be(:report_ds) { create(:ci_reports_security_report, pipeline: pipeline, type: :dependency_scanning) }
-  let_it_be(:report_sast) { create(:ci_reports_security_report, pipeline: pipeline, type: :sast) }
+RSpec.describe Security::FindingsFinder, feature_category: :vulnerability_management do
+  it_behaves_like 'security findings finder' do
+    let(:findings) { service_object.execute.findings }
+    let(:query_limit) { 11 }
 
-  let(:severity_levels) { nil }
-  let(:confidence_levels) { nil }
-  let(:report_types) { nil }
-  let(:scope) { nil }
-  let(:page) { nil }
-  let(:per_page) { nil }
-  let(:service_object) { described_class.new(pipeline, params: params) }
-  let(:params) do
-    {
-      severity: severity_levels,
-      confidence: confidence_levels,
-      report_type: report_types,
-      scope: scope,
-      page: page,
-      per_page: per_page
-    }
-  end
-
-  describe '#execute' do
-    context 'when the pipeline does not have security findings' do
-      subject { service_object.execute }
-
-      it { is_expected.to be_nil }
-    end
-
-    context 'when the pipeline has security findings' do
-      let(:finder_result) { service_object.execute }
-
-      before(:all) do
-        ds_content = File.read(artifact_ds.file.path)
-        Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(ds_content, report_ds)
-        report_ds.merge!(report_ds)
-        sast_content = File.read(artifact_sast.file.path)
-        Gitlab::Ci::Parsers::Security::Sast.parse!(sast_content, report_sast)
-        report_sast.merge!(report_sast)
-
-        findings = { artifact_ds => report_ds, artifact_sast => report_sast }.collect do |artifact, report|
-          scan = create(:security_scan, :latest_successful, scan_type: artifact.job.name, build: artifact.job)
-
-          report.findings.collect do |finding, index|
-            create(:security_finding,
-                   severity: finding.severity,
-                   confidence: finding.confidence,
-                   uuid: finding.uuid,
-                   deduplicated: true,
-                   scan: scan)
-          end
-        end.flatten
-
-        findings.second.update!(deduplicated: false)
-      end
-
-      before do
-        stub_licensed_features(sast: true, dependency_scanning: true)
-      end
-
-      context 'N+1 queries' do
-        it 'does not cause N+1 queries' do
-          expect { finder_result }.not_to exceed_query_limit(11)
-        end
+    context 'when the `security_findings` records have `overridden_uuid`s' do
+      let(:security_findings) { Security::Finding.by_build_ids(build_1) }
+      let(:expected_uuids) do
+        Security::Finding.where(overridden_uuid: nil).pluck(:uuid)
+          .concat(Security::Finding.where.not(overridden_uuid: nil).pluck(:overridden_uuid)) -
+          [Security::Finding.second[:overridden_uuid]]
       end
 
-      describe '#current_page' do
-        subject { finder_result.current_page }
-
-        context 'when the page is not provided' do
-          it { is_expected.to be(1) }
-        end
-
-        context 'when the page is provided' do
-          let(:page) { 2 }
-
-          it { is_expected.to be(2) }
-        end
-      end
-
-      describe '#limit_value' do
-        subject { finder_result.limit_value }
-
-        context 'when the per_page is not provided' do
-          it { is_expected.to be(20) }
-        end
-
-        context 'when the per_page is provided' do
-          let(:per_page) { 100 }
-
-          it { is_expected.to be(100) }
-        end
-      end
-
-      describe '#total_pages' do
-        subject { finder_result.total_pages }
-
-        context 'when the per_page is not provided' do
-          it { is_expected.to be(1) }
-        end
+      subject { findings.map(&:uuid) }
 
-        context 'when the per_page is provided' do
-          let(:per_page) { 3 }
-
-          it { is_expected.to be(3) }
-        end
-      end
-
-      describe '#total_count' do
-        subject { finder_result.total_count }
-
-        context 'when the scope is not provided' do
-          let(:finding_to_dismiss) { Security::Finding.first }
-
-          before do
-            stub_feature_flags(deprecate_vulnerabilities_feedback: deprecate_vulnerabilities_feedback?)
-          end
-
-          context 'when the `deprecate_vulnerabilities_feedback` feature is enabled' do
-            let(:deprecate_vulnerabilities_feedback?) { true }
-
-            before do
-              create(:vulnerabilities_finding, :dismissed, uuid: finding_to_dismiss.uuid)
-            end
-
-            it { is_expected.to be(7) }
-          end
-
-          context 'when the `deprecate_vulnerabilities_feedback` feature is disabled' do
-            let(:deprecate_vulnerabilities_feedback?) { false }
-
-            before do
-              create(:vulnerability_feedback,
-                 :dismissal,
-                 project: pipeline.project,
-                 category: :dependency_scanning,
-                 finding_uuid: finding_to_dismiss.uuid)
-            end
-
-            it { is_expected.to be(7) }
-          end
-        end
-
-        context 'when the scope is provided as `all`' do
-          let(:scope) { 'all' }
-
-          it { is_expected.to be(8) }
+      before do
+        security_findings.each do |security_finding|
+          security_finding.update!(overridden_uuid: security_finding.uuid, uuid: SecureRandom.uuid)
         end
       end
 
-      describe '#next_page' do
-        subject { finder_result.next_page }
-
-        context 'when the page is not provided' do
-          # Limit per_page to force pagination on smaller dataset
-          let(:per_page) { 2 }
-
-          it { is_expected.to be(2) }
-        end
-
-        context 'when the page is provided' do
-          let(:page) { 2 }
+      it { is_expected.to match_array(expected_uuids) }
+    end
 
-          it { is_expected.to be_nil }
-        end
+    describe '#vulnerability_flags' do
+      before do
+        stub_licensed_features(sast_fp_reduction: true)
       end
 
-      describe '#prev_page' do
-        subject { finder_result.prev_page }
-
-        context 'when the page is not provided' do
-          it { is_expected.to be_nil }
-        end
-
-        context 'when the page is provided' do
-          let(:page) { 2 }
-          # Limit per_page to force pagination on smaller dataset
-          let(:per_page) { 2 }
-
-          it { is_expected.to be(1) }
+      context 'with no vulnerability flags present' do
+        it 'does not have any vulnerability flag' do
+          expect(findings).to all(have_attributes(vulnerability_flags: be_empty))
         end
       end
 
-      describe '#vulnerability_flags' do
+      context 'with some vulnerability flags present' do
         before do
-          stub_licensed_features(sast_fp_reduction: true)
-        end
-
-        context 'with no vulnerability flags present' do
-          it 'does not have any vulnerability flag' do
-            expect(finder_result.findings).to all(have_attributes(vulnerability_flags: be_empty))
-          end
-        end
-
-        context 'with some vulnerability flags present' do
-          before do
-            allow_next_instance_of(Gitlab::Ci::Reports::Security::Finding) do |finding|
-              allow(finding).to receive(:flags).and_return([create(:ci_reports_security_flag)]) if finding.report_type == 'sast'
-            end
-          end
-
-          it 'has some vulnerability_findings with vulnerability flag' do
-            expect(finder_result.findings).to include(have_attributes(vulnerability_flags: be_present))
-          end
-
-          it 'does not have any vulnerability_flag if license is not available' do
-            stub_licensed_features(sast_fp_reduction: false)
-
-            expect(finder_result.findings).to all(have_attributes(vulnerability_flags: be_empty))
-          end
-        end
-      end
-
-      describe '#findings' do
-        subject { finder_result.findings.map(&:uuid) }
-
-        context 'with the default parameters' do
-          let(:expected_uuids) { Security::Finding.pluck(:uuid) - [Security::Finding.second[:uuid]] }
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when the uuid is provided' do
-          let(:uuid) { Security::Finding.pick(:uuid) }
-          let(:params) do
-            {
-              uuid: uuid
-            }
-          end
-
-          it { is_expected.to match_array([uuid]) }
-        end
-
-        context 'when the page is provided' do
-          let(:page) { 2 }
-          # Limit per_page to force pagination on smaller dataset
-          let(:per_page) { 2 }
-          let(:expected_uuids) { Security::Finding.pluck(:uuid)[4..5] }
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when the per_page is provided' do
-          let(:per_page) { 1 }
-          let(:expected_uuids) { [Security::Finding.pick(:uuid)] }
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when the `severity_levels` is provided' do
-          let(:severity_levels) { [:medium] }
-          let(:expected_uuids) { Security::Finding.where(severity: 'medium').pluck(:uuid) }
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when the `confidence_levels` is provided' do
-          let(:confidence_levels) { [:low] }
-          let(:expected_uuids) { Security::Finding.where(confidence: 'low' ).pluck(:uuid) }
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when the `report_types` is provided' do
-          let(:report_types) { :dependency_scanning }
-          let(:expected_uuids) do
-            Security::Finding.by_scan(Security::Scan.find_by(scan_type: 'dependency_scanning')).pluck(:uuid) -
-              [Security::Finding.second[:uuid]]
+          allow_next_instance_of(Gitlab::Ci::Reports::Security::Finding) do |finding|
+            allow(finding).to receive(:flags).and_return([create(:ci_reports_security_flag)]) if finding.report_type == 'sast'
           end
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when the `scope` is provided as `all`' do
-          let(:scope) { 'all' }
-
-          let(:expected_uuids) { Security::Finding.pluck(:uuid) - [Security::Finding.second[:uuid]] }
-
-          it { is_expected.to match_array(expected_uuids) }
         end
 
-        context 'when there is a retried build' do
-          let(:retried_build) { create(:ci_build, :success, :retried, name: 'dependency_scanning', pipeline: pipeline) }
-          let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning, job: retried_build) }
-          let(:report) { create(:ci_reports_security_report, pipeline: pipeline, type: :dependency_scanning) }
-          let(:report_types) { :dependency_scanning }
-          let(:expected_uuids) do
-            Security::Finding.by_scan(Security::Scan.find_by(scan_type: 'dependency_scanning')).pluck(:uuid) -
-              [Security::Finding.second[:uuid]]
-          end
-
-          before do
-            retried_content = File.read(artifact.file.path)
-            Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(retried_content, report)
-            report.merge!(report)
-
-            scan = create(:security_scan, scan_type: retried_build.name, build: retried_build, latest: false)
-
-            report.findings.each_with_index do |finding, index|
-              create(:security_finding,
-                     severity: finding.severity,
-                     confidence: finding.confidence,
-                     uuid: finding.uuid,
-                     deduplicated: true,
-                     scan: scan)
-            end
-          end
-
-          it { is_expected.to match_array(expected_uuids) }
+        it 'has some vulnerability_findings with vulnerability flag' do
+          expect(findings).to include(have_attributes(vulnerability_flags: be_present))
         end
 
-        context 'when the `security_findings` records have `overridden_uuid`s' do
-          let(:security_findings) { Security::Finding.by_build_ids(build_1) }
-
-          let(:expected_uuids) do
-            Security::Finding.where(overridden_uuid: nil).pluck(:uuid)
-              .concat(Security::Finding.where.not(overridden_uuid: nil).pluck(:overridden_uuid)) -
-              [Security::Finding.second[:overridden_uuid]]
-          end
+        it 'does not have any vulnerability_flag if license is not available' do
+          stub_licensed_features(sast_fp_reduction: false)
 
-          before do
-            security_findings.each do |security_finding|
-              security_finding.update!(overridden_uuid: security_finding.uuid, uuid: SecureRandom.uuid)
-            end
-          end
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when a build has more than one security report artifacts' do
-          let(:report_types) { :secret_detection }
-          let(:secret_detection_report) { create(:ci_reports_security_report, pipeline: pipeline, type: :secret_detection) }
-          let(:expected_uuids) { secret_detection_report.findings.map(&:uuid) }
-
-          before do
-            scan = create(:security_scan, :latest_successful, scan_type: :secret_detection, build: build_2)
-            artifact = create(:ee_ci_job_artifact, :secret_detection, job: build_2)
-            report_content = File.read(artifact.file.path)
-
-            Gitlab::Ci::Parsers::Security::SecretDetection.parse!(report_content, secret_detection_report)
-
-            secret_detection_report.findings.each_with_index do |finding, index|
-              create(:security_finding,
-                     severity: finding.severity,
-                     confidence: finding.confidence,
-                     uuid: finding.uuid,
-                     deduplicated: true,
-                     scan: scan)
-            end
-          end
-
-          it { is_expected.to match_array(expected_uuids) }
-        end
-
-        context 'when a vulnerability already exist for a security finding' do
-          let(:vulnerability) { create(:vulnerability, :with_finding, project: pipeline.project) }
-
-          subject { finder_result.findings.map(&:vulnerability).first }
-
-          before do
-            vulnerability.finding.update_attribute(:uuid, Security::Finding.first.uuid)
-          end
-
-          describe 'the vulnerability is included in results' do
-            it { is_expected.to eq(vulnerability) }
-          end
+          expect(findings).to all(have_attributes(vulnerability_flags: be_empty))
         end
       end
     end
diff --git a/ee/spec/finders/security/pure_findings_finder_spec.rb b/ee/spec/finders/security/pure_findings_finder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c0228596584d300b9c791da15d4a18c1696e0251
--- /dev/null
+++ b/ee/spec/finders/security/pure_findings_finder_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Security::PureFindingsFinder, feature_category: :vulnerability_management do
+  it_behaves_like 'security findings finder' do
+    let(:findings) { finder_result.to_a }
+    let(:query_limit) { 8 }
+
+    describe 'parsing artifacts' do
+      before do
+        allow(::Gitlab::Ci::Parsers).to receive(:fabricate!)
+      end
+
+      it 'does not parse artifacts' do
+        service_object.execute
+
+        expect(::Gitlab::Ci::Parsers).not_to have_received(:fabricate!)
+      end
+    end
+  end
+end
diff --git a/ee/spec/models/security/finding_spec.rb b/ee/spec/models/security/finding_spec.rb
index 90fbe64923dff3da766b3388445c685a31787322..60a12d287582e5334b71fc428229143c158f4b1a 100644
--- a/ee/spec/models/security/finding_spec.rb
+++ b/ee/spec/models/security/finding_spec.rb
@@ -2,16 +2,18 @@
 
 require 'spec_helper'
 
-RSpec.describe Security::Finding do
+RSpec.describe Security::Finding, feature_category: :vulnerability_management do
   let_it_be(:scan_1) { create(:security_scan, :latest_successful, scan_type: :sast) }
   let_it_be(:scan_2) { create(:security_scan, :latest_successful, scan_type: :dast) }
-  let_it_be(:finding_1) { create(:security_finding, scan: scan_1) }
-  let_it_be(:finding_2) { create(:security_finding, scan: scan_2) }
+  let_it_be(:finding_1, refind: true) { create(:security_finding, scan: scan_1) }
+  let_it_be(:finding_2, refind: true) { create(:security_finding, scan: scan_2) }
 
   describe 'associations' do
     it { is_expected.to belong_to(:scan).required }
     it { is_expected.to belong_to(:scanner).required }
+    it { is_expected.to belong_to(:vulnerability_read).class_name('Vulnerabilities::Read') }
     it { is_expected.to have_one(:build).through(:scan) }
+    it { is_expected.to have_one(:vulnerability).through(:vulnerability_read) }
 
     it {
       is_expected.to have_many(:feedbacks)
@@ -148,16 +150,16 @@
   end
 
   describe '.ordered' do
-    let_it_be(:finding_3) { create(:security_finding, severity: :critical, confidence: :confirmed) }
-    let_it_be(:finding_4) { create(:security_finding, severity: :critical, confidence: :high) }
+    let_it_be(:finding_3) { create(:security_finding, severity: :critical) }
+    let_it_be(:finding_4) { create(:security_finding, severity: :critical) }
 
     let(:expected_findings) { [finding_3, finding_4, finding_1, finding_2] }
 
     subject { described_class.ordered }
 
     before do
-      finding_1.update!(severity: :high, confidence: :unknown)
-      finding_2.update!(severity: :low, confidence: :confirmed)
+      finding_1.update!(severity: :high)
+      finding_2.update!(severity: :low)
     end
 
     it { is_expected.to eq(expected_findings) }
@@ -287,4 +289,126 @@
       it { is_expected.to match(1) }
     end
   end
+
+  describe '#state' do
+    subject { finding_1.state }
+
+    context 'when there is no associated vulnerability' do
+      context 'when there is no associated dismissal feedback' do
+        it { is_expected.to eq('detected') }
+      end
+
+      context 'when there is an associated dismissal feedback' do
+        before do
+          create(:vulnerability_feedback, :dismissal, finding_uuid: finding_1.uuid)
+        end
+
+        it { is_expected.to eq('dismissed') }
+      end
+    end
+
+    context 'when there is an associated vulnerability' do
+      where(:state) { %i[detected confirmed dismissed resolved] }
+
+      before do
+        create(:vulnerabilities_finding, state, uuid: finding_1.uuid)
+      end
+
+      with_them do
+        it { is_expected.to eq(state.to_s) }
+      end
+    end
+  end
+
+  describe 'feedback accessors' do
+    shared_examples_for 'has feedback method for' do |type|
+      context 'when there is no associated dismissal feedback' do
+        it { is_expected.to be_nil }
+      end
+
+      context 'when there is an associated dismissal feedback' do
+        let!(:feedback) { create(:vulnerability_feedback, type, finding_uuid: finding_1.uuid) }
+
+        it { is_expected.to eq(feedback) }
+      end
+    end
+
+    describe '#dismissal_feedback' do
+      it_behaves_like 'has feedback method for', :dismissal do
+        subject { finding_1.dismissal_feedback }
+      end
+    end
+
+    describe '#issue_feedback' do
+      it_behaves_like 'has feedback method for', :issue do
+        subject { finding_1.issue_feedback }
+      end
+    end
+
+    describe '#merge_request_feedback' do
+      it_behaves_like 'has feedback method for', :merge_request do
+        subject { finding_1.merge_request_feedback }
+      end
+    end
+  end
+
+  describe 'attributes delegated to `finding_data`' do
+    using RSpec::Parameterized::TableSyntax
+
+    where(:attribute, :expected_value) do
+      :name                     | 'Test finding'
+      :description              | 'Test description'
+      :solution                 | 'Test solution'
+      :location                 | 'Test location'
+      :identifiers              | ['Test identifier']
+      :links                    | ['Test link']
+      :false_positive?          | false
+      :assets                   | ['Test asset']
+      :evidence                 | {}
+      :details                  | []
+      :remediation_byte_offsets | { start_byte: 0, end_byte: 1 }
+    end
+
+    with_them do
+      let(:finding) { build(:security_finding) }
+
+      subject { finding.send(attribute) }
+
+      before do
+        finding.finding_data[attribute] = expected_value
+      end
+
+      it { is_expected.to eq(expected_value) }
+    end
+  end
+
+  describe '#remediations', :aggregate_failures do
+    let(:finding) { create(:security_finding, finding_data: finding_data) }
+    let(:mock_remediations) { [Object.new] }
+    let(:mock_proxy) { instance_double(Security::RemediationsProxy, by_byte_offsets: mock_remediations) }
+
+    subject(:remediations) { finding.remediations }
+
+    before do
+      allow(finding.scan).to receive(:remediations_proxy).and_return(mock_proxy)
+    end
+
+    context 'when the remediation byte offsets do not exist' do
+      let(:finding_data) { {} }
+
+      it 'does not call the proxy and returns an empty array' do
+        expect(remediations).to be_empty
+        expect(mock_proxy).not_to have_received(:by_byte_offsets)
+      end
+    end
+
+    context 'when the remediation byte offsets exist' do
+      let(:finding_data) { { remediation_byte_offsets: [{ start_byte: 0, end_byte: 10 }] } }
+
+      it 'delegates the call to the proxy' do
+        expect(remediations).to eq(mock_remediations)
+        expect(mock_proxy).to have_received(:by_byte_offsets)
+      end
+    end
+  end
 end
diff --git a/ee/spec/models/security/remediations_proxy_spec.rb b/ee/spec/models/security/remediations_proxy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..97984c661f7b66035a05aa76cd61a3c2aa48cc63
--- /dev/null
+++ b/ee/spec/models/security/remediations_proxy_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Security::RemediationsProxy, feature_category: :vulnerability_management do
+  let(:dast_artifact) { create(:ci_job_artifact, :common_security_report) }
+  let(:file) { dast_artifact.file }
+  let(:model) { described_class.new(file) }
+
+  describe '#by_byte_offsets' do
+    # The following byte offsets are collected by parsing the related artifact with Oj::Introspect.
+    # If the specs fail due to a change in the related artifact, you can collect them again by parsing
+    # the artifact again and checking the `:__oj_introspect` keys for remediations.
+    let(:remediation_1_byte_offsets) { [11842, 12008] }
+    let(:remediation_2_byte_offsets) { [12015, 12181] }
+    let(:remediation_3_byte_offsets) { remediation_2_byte_offsets }
+
+    subject(:data_fragments) do
+      model.by_byte_offsets([remediation_1_byte_offsets, remediation_2_byte_offsets, remediation_2_byte_offsets])
+           .map(&:deep_symbolize_keys)
+    end
+
+    before do
+      allow(file).to receive(:multi_read).and_call_original
+    end
+
+    it 'returns remediations by given byte offsets' do
+      expect(data_fragments).to eq(
+        [
+          {
+            diff: 'dG90YWxseSBsZWdpdCBkaWZm',
+            summary: 'this remediates CVE-2137',
+            fixes: [{ cve: 'CVE-2137' }]
+          },
+          {
+            diff: 'dG90YWxseSBsZWdpdCBkaWZm',
+            summary: 'this remediates CVE-2138',
+            fixes: [{ cve: 'CVE-2138' }]
+          },
+          {
+            diff: 'dG90YWxseSBsZWdpdCBkaWZm',
+            summary: 'this remediates CVE-2138',
+            fixes: [{ cve: 'CVE-2138' }]
+          }
+        ]
+      )
+    end
+
+    it 'delegates the call to GitlabUploader#multi_read with unique offsets' do
+      data_fragments
+
+      expect(file).to have_received(:multi_read).once.with([remediation_1_byte_offsets, remediation_2_byte_offsets])
+    end
+  end
+end
diff --git a/ee/spec/models/security/scan_spec.rb b/ee/spec/models/security/scan_spec.rb
index 46fc5ff8012980bd895b5a4818724972bd1b55ef..ed8688698be67bdfdeabccd788206d66a5bca264 100644
--- a/ee/spec/models/security/scan_spec.rb
+++ b/ee/spec/models/security/scan_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe Security::Scan do
+RSpec.describe Security::Scan, feature_category: :vulnerability_management do
   it_behaves_like 'cleanup by a loose foreign key' do
     let!(:model) { create(:security_scan) }
     let(:parent) { model.build }
@@ -399,6 +399,21 @@
     end
   end
 
+  describe '#remediations_proxy' do
+    let(:mock_file) { instance_double(JobArtifactUploader) }
+    let(:scan) { create(:security_scan, :with_findings) }
+
+    subject { scan.remediations_proxy }
+
+    before do
+      allow_next_found_instance_of(Ci::JobArtifact) do |artifact|
+        allow(artifact).to receive(:file).and_return(mock_file)
+      end
+    end
+
+    it { is_expected.to be_an_instance_of(Security::RemediationsProxy).and have_attributes(file: mock_file) }
+  end
+
   it_behaves_like 'having unique enum values'
 
   it 'sets `project_id` and `pipeline_id` before save' do
diff --git a/ee/spec/models/vulnerabilities/read_spec.rb b/ee/spec/models/vulnerabilities/read_spec.rb
index 4817b082b0cd0e9246e618cb20f024653750c9dc..55c400b3fa7a836f78281f92736292e3d48807f2 100644
--- a/ee/spec/models/vulnerabilities/read_spec.rb
+++ b/ee/spec/models/vulnerabilities/read_spec.rb
@@ -2,13 +2,14 @@
 
 require 'spec_helper'
 
-RSpec.describe Vulnerabilities::Read, type: :model do
+RSpec.describe Vulnerabilities::Read, type: :model, feature_category: :vulnerability_management do
   let_it_be(:project) { create(:project) }
 
   describe 'associations' do
     it { is_expected.to belong_to(:vulnerability) }
     it { is_expected.to belong_to(:project) }
     it { is_expected.to belong_to(:scanner).class_name('Vulnerabilities::Scanner') }
+    it { is_expected.to have_many(:security_findings).class_name('Security::Finding') }
   end
 
   describe 'validations' do
diff --git a/ee/spec/requests/api/vulnerability_findings_spec.rb b/ee/spec/requests/api/vulnerability_findings_spec.rb
index 1fc73e7889dcf240c26dbfb5dda93289e9a15dc5..8c377633f898e97ae54301576f20fc374e0b0380 100644
--- a/ee/spec/requests/api/vulnerability_findings_spec.rb
+++ b/ee/spec/requests/api/vulnerability_findings_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe API::VulnerabilityFindings do
+RSpec.describe API::VulnerabilityFindings, feature_category: :vulnerability_management do
   include AccessMatchersForRequest
 
   let_it_be(:project) { create(:project, :public) }
@@ -11,24 +11,23 @@
   describe 'GET /projects/:id/vulnerability_findings' do
     let(:project_vulnerability_findings_path) { "/projects/#{project.id}/vulnerability_findings" }
 
-    before do
-      stub_licensed_features(security_dashboard: true, sast: true, dependency_scanning: true, container_scanning: true)
-
-      create(:ee_ci_job_artifact, :dependency_scanning, job: build_ds, project: project)
-      create(:ee_ci_job_artifact, :sast, job: build_sast, project: project)
-      dismissal
-    end
-
-    let(:pipeline) { create(:ci_empty_pipeline, status: :created, project: project) }
-    let(:pipeline_without_vulnerabilities) { create(:ci_pipeline, status: :created, project: project) }
+    let_it_be(:pipeline) { create(:ci_empty_pipeline, status: :created, project: project) }
+    let_it_be(:pipeline_without_vulnerabilities) { create(:ci_pipeline, status: :created, project: project) }
 
-    let(:build_ds) { create(:ci_build, :success, name: 'ds_job', pipeline: pipeline, project: project) }
-    let(:build_sast) { create(:ci_build, :success, name: 'sast_job', pipeline: pipeline, project: project) }
+    let_it_be(:build_ds) { create(:ci_build, :success, name: 'ds_job', pipeline: pipeline, project: project) }
+    let_it_be(:build_sast) { create(:ci_build, :success, name: 'sast_job', pipeline: pipeline, project: project) }
 
     let(:ds_report) { pipeline.security_reports.reports['dependency_scanning'] }
     let(:sast_report) { pipeline.security_reports.reports['sast'] }
 
-    let(:dismissal) do
+    before(:all) do
+      create(:security_scan, :latest_successful, :with_findings, scan_type: :dependency_scanning, build: build_ds)
+      create(:security_scan, :latest_successful, :with_findings, scan_type: :sast, build: build_sast)
+    end
+
+    before do
+      stub_licensed_features(security_dashboard: true, sast: true, dependency_scanning: true, container_scanning: true)
+
       create(:vulnerability_feedback, :dismissal, :sast,
              project: project,
              pipeline: pipeline,
@@ -36,6 +35,8 @@
              vulnerability_data: sast_report.findings.first.raw_metadata,
              finding_uuid: sast_report.findings.first.uuid
       )
+
+      create(:vulnerabilities_finding, :dismissed, uuid: sast_report.findings.first.uuid)
     end
 
     context 'with an authorized user with proper permissions' do
@@ -80,45 +81,58 @@
       end
 
       describe 'using different finders' do
+        let(:utilize_finding_data?) { true }
+        let(:mock_findings_finder) { instance_double(Security::FindingsFinder, execute: []) }
+        let(:mock_pure_findings_finder) do
+          instance_double(Security::PureFindingsFinder,
+                          execute: [], available?: pure_finder_available?)
+        end
+
         before do
-          allow(Security::PipelineVulnerabilitiesFinder).to receive(:new).and_call_original
+          stub_feature_flags(utilize_finding_data: utilize_finding_data?)
 
-          allow_next_instance_of(Security::FindingsFinder) do |finder|
-            allow(finder).to receive(:execute).and_return(mock_result)
-          end
-        end
+          allow(Security::FindingsFinder).to receive(:new).and_return(mock_findings_finder)
+          allow(Security::PureFindingsFinder).to receive(:new).and_return(mock_pure_findings_finder)
 
-        context 'when the project uses `security_findings`' do
-          let(:finding) { create(:vulnerability_finding) }
-          let(:mock_result) { double(findings: [finding]) }
+          get api(project_vulnerability_findings_path, user), params: pagination
+        end
 
-          it 'does not use `Security::PipelineVulnerabilitiesFinder`' do
-            get api(project_vulnerability_findings_path, user), params: pagination
+        context 'when the `Security::PureFindingsFinder` is not available' do
+          let(:pure_finder_available?) { false }
 
-            expect(Security::PipelineVulnerabilitiesFinder).not_to have_received(:new)
+          it 'uses the `Security::FindingsFinder`' do
+            expect(mock_pure_findings_finder).not_to have_received(:execute)
+            expect(mock_findings_finder).to have_received(:execute)
           end
         end
 
-        context 'when the project does not use `security_findings`' do
-          let(:mock_result) { nil }
+        context 'when the `Security::PureFindingsFinder` is available' do
+          let(:pure_finder_available?) { true }
 
-          it 'fallsback to `Security::PipelineVulnerabilitiesFinder`' do
-            get api(project_vulnerability_findings_path, user), params: pagination
-
-            expect(Security::PipelineVulnerabilitiesFinder).to have_received(:new)
+          it 'uses the `Security::FindingsFinder`' do
+            expect(mock_pure_findings_finder).to have_received(:execute)
+            expect(mock_findings_finder).not_to have_received(:execute)
           end
+        end
+
+        context 'when the `utilize_finding_data` feature flag is turned off' do
+          let(:utilize_finding_data?) { false }
 
-          context 'when the `Security::PipelineVulnerabilitiesFinder` raises exception' do
-            before do
-              allow_next_instance_of(Security::PipelineVulnerabilitiesFinder) do |finder|
-                allow(finder).to receive(:execute).and_raise(Security::PipelineVulnerabilitiesFinder::ParseError.new('failed'))
-              end
+          context 'when the `Security::PureFindingsFinder` is not available' do
+            let(:pure_finder_available?) { false }
+
+            it 'uses the `Security::FindingsFinder`' do
+              expect(mock_pure_findings_finder).not_to have_received(:execute)
+              expect(mock_findings_finder).to have_received(:execute)
             end
+          end
 
-            it 'does not propagate the error to the client' do
-              get api(project_vulnerability_findings_path, user), params: pagination
+          context 'when the `Security::PureFindingsFinder` is available' do
+            let(:pure_finder_available?) { true }
 
-              expect(response).to have_gitlab_http_status(:ok)
+            it 'uses the `Security::FindingsFinder`' do
+              expect(mock_pure_findings_finder).not_to have_received(:execute)
+              expect(mock_findings_finder).to have_received(:execute)
             end
           end
         end
diff --git a/ee/spec/support/shared_examples/finders/security/findings_finder_shared_examples.rb b/ee/spec/support/shared_examples/finders/security/findings_finder_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c126c5748e7ef73f2f698a226be1ccf1eb7041f5
--- /dev/null
+++ b/ee/spec/support/shared_examples/finders/security/findings_finder_shared_examples.rb
@@ -0,0 +1,343 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'security findings finder' do
+  let_it_be(:pipeline) { create(:ci_pipeline) }
+  let_it_be(:build_1) { create(:ci_build, :success, name: 'dependency_scanning', pipeline: pipeline) }
+  let_it_be(:build_2) { create(:ci_build, :success, name: 'sast', pipeline: pipeline) }
+  let_it_be(:artifact_ds) { create(:ee_ci_job_artifact, :dependency_scanning, job: build_1) }
+  let_it_be(:artifact_sast) { create(:ee_ci_job_artifact, :sast, job: build_2) }
+  let_it_be(:report_ds) { create(:ci_reports_security_report, pipeline: pipeline, type: :dependency_scanning) }
+  let_it_be(:report_sast) { create(:ci_reports_security_report, pipeline: pipeline, type: :sast) }
+
+  let(:severity_levels) { nil }
+  let(:confidence_levels) { nil }
+  let(:report_types) { nil }
+  let(:scope) { nil }
+  let(:page) { nil }
+  let(:per_page) { nil }
+  let(:service_object) { described_class.new(pipeline, params: params) }
+  let(:params) do
+    {
+      severity: severity_levels,
+      confidence: confidence_levels,
+      report_type: report_types,
+      scope: scope,
+      page: page,
+      per_page: per_page
+    }
+  end
+
+  describe '#execute' do
+    context 'when the pipeline does not have security findings' do
+      subject { service_object.execute }
+
+      it { is_expected.to be_empty }
+    end
+
+    context 'when the pipeline has security findings' do
+      let(:finder_result) { service_object.execute }
+
+      before(:all) do
+        ds_content = File.read(artifact_ds.file.path)
+        Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(ds_content, report_ds)
+        report_ds.merge!(report_ds)
+        sast_content = File.read(artifact_sast.file.path)
+        Gitlab::Ci::Parsers::Security::Sast.parse!(sast_content, report_sast)
+        report_sast.merge!(report_sast)
+
+        findings = { artifact_ds => report_ds, artifact_sast => report_sast }.collect do |artifact, report|
+          scan = create(:security_scan, :latest_successful, scan_type: artifact.job.name, build: artifact.job)
+
+          report.findings.collect do |finding, index|
+            create(:security_finding,
+                   severity: finding.severity,
+                   confidence: finding.confidence,
+                   uuid: finding.uuid,
+                   deduplicated: true,
+                   scan: scan)
+          end
+        end.flatten
+
+        findings.second.update!(deduplicated: false)
+
+        create(:vulnerability_feedback,
+               :dismissal,
+               project: pipeline.project,
+               category: :sast,
+               finding_uuid: findings.first.uuid)
+      end
+
+      before do
+        stub_licensed_features(sast: true, dependency_scanning: true)
+      end
+
+      describe 'N+1 queries' do
+        it 'does not cause N+1 queries' do
+          expect { finder_result }.not_to exceed_query_limit(query_limit)
+        end
+      end
+
+      describe '#current_page' do
+        subject { finder_result.current_page }
+
+        context 'when the page is not provided' do
+          it { is_expected.to be(1) }
+        end
+
+        context 'when the page is provided' do
+          let(:page) { 2 }
+
+          it { is_expected.to be(2) }
+        end
+      end
+
+      describe '#limit_value' do
+        subject { finder_result.limit_value }
+
+        context 'when the per_page is not provided' do
+          it { is_expected.to be(20) }
+        end
+
+        context 'when the per_page is provided' do
+          let(:per_page) { 100 }
+
+          it { is_expected.to be(100) }
+        end
+      end
+
+      describe '#total_pages' do
+        subject { finder_result.total_pages }
+
+        context 'when the per_page is not provided' do
+          it { is_expected.to be(1) }
+        end
+
+        context 'when the per_page is provided' do
+          let(:per_page) { 3 }
+
+          it { is_expected.to be(3) }
+        end
+      end
+
+      describe '#total_count' do
+        subject { finder_result.total_count }
+
+        context 'when the scope is not provided' do
+          let(:finding_to_dismiss) { Security::Finding.first }
+
+          before do
+            stub_feature_flags(deprecate_vulnerabilities_feedback: deprecate_vulnerabilities_feedback?)
+          end
+
+          context 'when the `deprecate_vulnerabilities_feedback` feature is enabled' do
+            let(:deprecate_vulnerabilities_feedback?) { true }
+
+            before do
+              create(:vulnerabilities_finding, :dismissed, uuid: finding_to_dismiss.uuid)
+            end
+
+            it { is_expected.to be(7) }
+          end
+
+          context 'when the `deprecate_vulnerabilities_feedback` feature is disabled' do
+            let(:deprecate_vulnerabilities_feedback?) { false }
+
+            before do
+              create(:vulnerability_feedback,
+                 :dismissal,
+                 project: pipeline.project,
+                 category: :dependency_scanning,
+                 finding_uuid: finding_to_dismiss.uuid)
+            end
+
+            it { is_expected.to be(7) }
+          end
+        end
+
+        context 'when the scope is provided as `all`' do
+          let(:scope) { 'all' }
+
+          it { is_expected.to be(8) }
+        end
+      end
+
+      describe '#next_page' do
+        subject { finder_result.next_page }
+
+        context 'when the page is not provided' do
+          # Limit per_page to force pagination on smaller dataset
+          let(:per_page) { 2 }
+
+          it { is_expected.to be(2) }
+        end
+
+        context 'when the page is provided' do
+          let(:page) { 2 }
+
+          it { is_expected.to be_nil }
+        end
+      end
+
+      describe '#prev_page' do
+        subject { finder_result.prev_page }
+
+        context 'when the page is not provided' do
+          it { is_expected.to be_nil }
+        end
+
+        context 'when the page is provided' do
+          let(:page) { 2 }
+          # Limit per_page to force pagination on smaller dataset
+          let(:per_page) { 2 }
+
+          it { is_expected.to be(1) }
+        end
+      end
+
+      describe '#findings' do
+        subject { findings.map(&:uuid) }
+
+        context 'with the default parameters' do
+          let(:expected_uuids) { Security::Finding.pluck(:uuid) - [Security::Finding.second[:uuid]] }
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when the uuid is provided' do
+          let(:uuid) { Security::Finding.pick(:uuid) }
+          let(:params) do
+            {
+              uuid: uuid
+            }
+          end
+
+          it { is_expected.to match_array([uuid]) }
+        end
+
+        context 'when the page is provided' do
+          let(:page) { 2 }
+          # Limit per_page to force pagination on smaller dataset
+          let(:per_page) { 2 }
+          let(:expected_uuids) { Security::Finding.pluck(:uuid)[4..5] }
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when the per_page is provided' do
+          let(:per_page) { 1 }
+          let(:expected_uuids) { [Security::Finding.pick(:uuid)] }
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when the `severity_levels` is provided' do
+          let(:severity_levels) { [:medium] }
+          let(:expected_uuids) { Security::Finding.where(severity: 'medium').pluck(:uuid) }
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when the `confidence_levels` is provided' do
+          let(:confidence_levels) { [:low] }
+          let(:expected_uuids) { Security::Finding.where(confidence: 'low').pluck(:uuid) }
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when the `report_types` is provided' do
+          let(:report_types) { :dependency_scanning }
+          let(:expected_uuids) do
+            Security::Finding.by_scan(Security::Scan.find_by(scan_type: 'dependency_scanning')).pluck(:uuid) -
+              [Security::Finding.second[:uuid]]
+          end
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when the `scope` is provided as `all`' do
+          let(:scope) { 'all' }
+
+          let(:expected_uuids) { Security::Finding.pluck(:uuid) - [Security::Finding.second[:uuid]] }
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when there is a retried build' do
+          let(:retried_build) { create(:ci_build, :success, :retried, name: 'dependency_scanning', pipeline: pipeline) }
+          let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning, job: retried_build) }
+          let(:report) { create(:ci_reports_security_report, pipeline: pipeline, type: :dependency_scanning) }
+          let(:report_types) { :dependency_scanning }
+          let(:expected_uuids) do
+            Security::Finding.by_scan(Security::Scan.find_by(scan_type: 'dependency_scanning')).pluck(:uuid) -
+              [Security::Finding.second[:uuid]]
+          end
+
+          before do
+            retried_content = File.read(artifact.file.path)
+            Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(retried_content, report)
+            report.merge!(report)
+
+            scan = create(:security_scan, scan_type: retried_build.name, build: retried_build, latest: false)
+
+            report.findings.each_with_index do |finding, index|
+              create(:security_finding,
+                     severity: finding.severity,
+                     confidence: finding.confidence,
+                     uuid: finding.uuid,
+                     deduplicated: true,
+                     scan: scan)
+            end
+          end
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when a build has more than one security report artifacts' do
+          let(:report_types) { :secret_detection }
+          let(:expected_uuids) { secret_detection_report.findings.map(&:uuid) }
+          let(:secret_detection_report) do
+            create(:ci_reports_security_report,
+                   pipeline: pipeline,
+                   type: :secret_detection)
+          end
+
+          before do
+            scan = create(:security_scan, :latest_successful, scan_type: :secret_detection, build: build_2)
+            artifact = create(:ee_ci_job_artifact, :secret_detection, job: build_2)
+            report_content = File.read(artifact.file.path)
+
+            Gitlab::Ci::Parsers::Security::SecretDetection.parse!(report_content, secret_detection_report)
+
+            secret_detection_report.findings.each_with_index do |finding, index|
+              create(:security_finding,
+                     severity: finding.severity,
+                     confidence: finding.confidence,
+                     uuid: finding.uuid,
+                     deduplicated: true,
+                     scan: scan)
+            end
+          end
+
+          it { is_expected.to match_array(expected_uuids) }
+        end
+
+        context 'when a vulnerability already exist for a security finding' do
+          let!(:vulnerability_finding) do
+            create(:vulnerabilities_finding,
+                   :detected,
+                   uuid: Security::Finding.first.uuid,
+                   project: pipeline.project)
+          end
+
+          subject { findings.map(&:vulnerability).first }
+
+          describe 'the vulnerability is included in results' do
+            it { is_expected.to eq(vulnerability_finding.vulnerability) }
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index db70441aaf56c274509e3cd9f6dda1f08f0a5e9e..f62ab7266319f281dc04984d77c890e6355841c8 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -6,7 +6,7 @@
 RSpec.describe GitlabUploader do
   let(:uploader_class) { Class.new(described_class) }
 
-  subject { uploader_class.new(double) }
+  subject(:uploader) { uploader_class.new(double) }
 
   describe '#file_storage?' do
     context 'when file storage is used' do
@@ -161,6 +161,19 @@
     end
   end
 
+  describe '#multi_read' do
+    let(:file) { fixture_file_upload('spec/fixtures/trace/sample_trace', 'text/plain') }
+    let(:byte_offsets) { [[4, 10], [17, 29]] }
+
+    subject { uploader.multi_read(byte_offsets) }
+
+    before do
+      uploader.store!(file)
+    end
+
+    it { is_expected.to eq(%w[Running gitlab-runner]) }
+  end
+
   describe '.version' do
     subject { uploader_class.version }