diff --git a/ee/app/models/sbom/occurrence.rb b/ee/app/models/sbom/occurrence.rb
index 7166f3753de75e720efe01da631e95d59d311241..65fa565ab3296021fd2eb093974f0f6fdd316d45 100644
--- a/ee/app/models/sbom/occurrence.rb
+++ b/ee/app/models/sbom/occurrence.rb
@@ -12,6 +12,7 @@ class Occurrence < ApplicationRecord
     belongs_to :project, optional: false
     belongs_to :pipeline, class_name: 'Ci::Pipeline'
     belongs_to :source
+    belongs_to :source_package, optional: true
 
     has_many :occurrences_vulnerabilities,
       class_name: 'Sbom::OccurrencesVulnerability',
diff --git a/ee/app/services/sbom/ingestion/ingest_report_slice_service.rb b/ee/app/services/sbom/ingestion/ingest_report_slice_service.rb
index c2f79cd17c6a4193a24bdc4a29b39f30e66dca63..1b3168bd4b83ed77b04b0cd666399d2060841f60 100644
--- a/ee/app/services/sbom/ingestion/ingest_report_slice_service.rb
+++ b/ee/app/services/sbom/ingestion/ingest_report_slice_service.rb
@@ -7,6 +7,7 @@ class IngestReportSliceService
         ::Sbom::Ingestion::Tasks::IngestComponents,
         ::Sbom::Ingestion::Tasks::IngestComponentVersions,
         ::Sbom::Ingestion::Tasks::IngestSources,
+        ::Sbom::Ingestion::Tasks::IngestSourcePackages,
         ::Sbom::Ingestion::Tasks::IngestOccurrences
       ].freeze
 
diff --git a/ee/app/services/sbom/ingestion/occurrence_map.rb b/ee/app/services/sbom/ingestion/occurrence_map.rb
index 697921cfa33679c3fe898f8cc96443515ada8a3a..ed22d847821e27715fef6e9d1e1e623fb577d316 100644
--- a/ee/app/services/sbom/ingestion/occurrence_map.rb
+++ b/ee/app/services/sbom/ingestion/occurrence_map.rb
@@ -6,7 +6,7 @@ class OccurrenceMap
       include Gitlab::Utils::StrongMemoize
 
       attr_reader :report_component, :report_source, :vulnerabilities
-      attr_accessor :component_id, :component_version_id, :source_id, :occurrence_id
+      attr_accessor :component_id, :component_version_id, :source_id, :occurrence_id, :source_package_id
 
       def initialize(report_component, report_source, vulnerabilities)
         @report_component = report_component
@@ -24,6 +24,8 @@ def to_h
           source_id: source_id,
           source_type: report_source&.source_type,
           source: report_source&.data,
+          source_package_id: source_package_id,
+          source_package_name: report_component.source_package_name,
           version: version
         }
       end
@@ -45,15 +47,15 @@ def vulnerability_ids
       end
       strong_memoize_attr :vulnerability_ids
 
-      delegate :packager, :input_file_path, to: :report_source, allow_nil: true
-      delegate :name, :version, to: :report_component
-
-      private
-
       def purl_type
         report_component.purl&.type
       end
 
+      delegate :packager, :input_file_path, to: :report_source, allow_nil: true
+      delegate :name, :version, :source_package_name, to: :report_component
+
+      private
+
       def vulnerabilities_info
         @vulnerabilities.fetch(name, version, input_file_path)
       end
diff --git a/ee/app/services/sbom/ingestion/tasks/ingest_occurrences.rb b/ee/app/services/sbom/ingestion/tasks/ingest_occurrences.rb
index 0bab96515ffecc96a9902a8e5609542f4e180ff6..64a35e6e5f6ffc238f3aa21ebdb3e549cd2aedaf 100644
--- a/ee/app/services/sbom/ingestion/tasks/ingest_occurrences.rb
+++ b/ee/app/services/sbom/ingestion/tasks/ingest_occurrences.rb
@@ -28,6 +28,7 @@ def attributes
               component_id: occurrence_map.component_id,
               component_version_id: occurrence_map.component_version_id,
               source_id: occurrence_map.source_id,
+              source_package_id: occurrence_map.source_package_id,
               commit_sha: pipeline.sha,
               uuid: uuid(occurrence_map),
               package_manager: occurrence_map.packager,
diff --git a/ee/app/services/sbom/ingestion/tasks/ingest_source_packages.rb b/ee/app/services/sbom/ingestion/tasks/ingest_source_packages.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c2cd764e5e46c5e87aaa7607439d9859fd28b980
--- /dev/null
+++ b/ee/app/services/sbom/ingestion/tasks/ingest_source_packages.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Sbom
+  module Ingestion
+    module Tasks
+      class IngestSourcePackages < Base
+        include Gitlab::Ingestion::BulkInsertableTask
+
+        SOURCE_PACKAGE_ATTRIBUTES = %i[name purl_type].freeze
+
+        self.model = Sbom::SourcePackage
+        self.unique_by = SOURCE_PACKAGE_ATTRIBUTES
+        self.uses = ([:id] + SOURCE_PACKAGE_ATTRIBUTES).freeze
+
+        private
+
+        def after_ingest
+          return_data.each do |source_package_id, source_package_name, purl_type|
+            maps_with(source_package_name, purl_type)&.each do |occurrence_map|
+              occurrence_map.source_package_id = source_package_id
+            end
+          end
+        end
+
+        def attributes
+          valid_occurrence_maps.map do |occurrence_map|
+            {
+              name: occurrence_map.source_package_name,
+              purl_type: occurrence_map.purl_type
+            }
+          end
+        end
+
+        def valid_occurrence_maps
+          @valid_occurrence_maps ||= occurrence_maps.filter(&:source_package_name)
+        end
+
+        def maps_with(source_package_name, purl_type)
+          grouped_maps[[source_package_name, purl_type]]
+        end
+
+        def grouped_maps
+          @grouped_maps ||= valid_occurrence_maps.group_by do |occurrence_map|
+            report_component = occurrence_map.report_component
+
+            [report_component.source_package_name, report_component.purl_type]
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/factories/sbom/ingestion/occurrence_maps.rb b/ee/spec/factories/sbom/ingestion/occurrence_maps.rb
index 217d961b67f3266dcce90d376f1ecab58bf7b6fe..7d3b610924e7e4e4425be0554de0fcf9ab6349ee 100644
--- a/ee/spec/factories/sbom/ingestion/occurrence_maps.rb
+++ b/ee/spec/factories/sbom/ingestion/occurrence_maps.rb
@@ -22,10 +22,16 @@
       occurrence factory: :sbom_occurrence
     end
 
+    trait :with_source_package do
+      report_component factory: [:ci_reports_sbom_component, :with_source_package_name]
+      source_package factory: :sbom_source_package
+    end
+
     trait :for_occurrence_ingestion do
       with_component
       with_component_version
       with_source
+      with_source_package
     end
 
     skip_create
@@ -38,6 +44,7 @@
         object.component_version_id = attributes[:component_version]&.id
         object.source_id = attributes[:source]&.id
         object.occurrence_id = attributes[:occurrence]&.id
+        object.source_package_id = attributes[:source_package]&.id
       end
     end
   end
diff --git a/ee/spec/factories/sbom/source_packages.rb b/ee/spec/factories/sbom/source_packages.rb
index 5c678f20fca201d53dc08f526d9b31a79f6e6d06..2dba6cee5bc761ec422dfa237bbff41fb14fd998 100644
--- a/ee/spec/factories/sbom/source_packages.rb
+++ b/ee/spec/factories/sbom/source_packages.rb
@@ -3,6 +3,6 @@
 FactoryBot.define do
   factory :sbom_source_package, class: 'Sbom::SourcePackage' do
     purl_type { 'deb' }
-    name { 'perl' }
+    sequence(:name) { |n| "component-#{n}" }
   end
 end
diff --git a/ee/spec/models/sbom/occurrence_spec.rb b/ee/spec/models/sbom/occurrence_spec.rb
index b6b713652e65656eda7be568d6e64670abef4b92..7228b1882ed3a543ee6751481ae84cfcbbca7341 100644
--- a/ee/spec/models/sbom/occurrence_spec.rb
+++ b/ee/spec/models/sbom/occurrence_spec.rb
@@ -11,6 +11,7 @@
     it { is_expected.to belong_to(:project).required }
     it { is_expected.to belong_to(:pipeline) }
     it { is_expected.to belong_to(:source) }
+    it { is_expected.to belong_to(:source_package) }
     it { is_expected.to have_many(:occurrences_vulnerabilities) }
     it { is_expected.to have_many(:vulnerabilities) }
   end
diff --git a/ee/spec/services/sbom/ingestion/occurrence_map_spec.rb b/ee/spec/services/sbom/ingestion/occurrence_map_spec.rb
index 29ceaec9356abf43bf480d80a72b5b923d4e7b4b..c6b66dd0890eeb3ed321a14afbda316c7f3a62d5 100644
--- a/ee/spec/services/sbom/ingestion/occurrence_map_spec.rb
+++ b/ee/spec/services/sbom/ingestion/occurrence_map_spec.rb
@@ -3,7 +3,7 @@
 require 'spec_helper'
 
 RSpec.describe Sbom::Ingestion::OccurrenceMap, feature_category: :dependency_management do
-  let_it_be(:report_component) { build_stubbed(:ci_reports_sbom_component) }
+  let_it_be(:report_component) { build_stubbed(:ci_reports_sbom_component, source_package_name: 'source-package-name') }
   let_it_be(:report_source) { build_stubbed(:ci_reports_sbom_source) }
 
   let(:vulnerability_info) { create(:sbom_vulnerabilities) }
@@ -17,6 +17,8 @@
       source: report_source.data,
       source_id: nil,
       source_type: report_source.source_type,
+      source_package_id: nil,
+      source_package_name: report_component.source_package_name,
       version: report_component.version
     }
   end
@@ -33,7 +35,8 @@
         {
           component_id: 1,
           component_version_id: 2,
-          source_id: 3
+          source_id: 3,
+          source_package_id: 4
         }
       end
 
@@ -41,6 +44,7 @@
         occurrence_map.component_id = ids[:component_id]
         occurrence_map.component_version_id = ids[:component_version_id]
         occurrence_map.source_id = ids[:source_id]
+        occurrence_map.source_package_id = ids[:source_package_id]
       end
 
       it 'returns a hash with ids and base data' do
@@ -62,6 +66,8 @@
             source: nil,
             source_id: nil,
             source_type: nil,
+            source_package_id: nil,
+            source_package_name: report_component.source_package_name,
             version: report_component.version
           }
         )
@@ -82,6 +88,8 @@
             source: report_source.data,
             source_id: nil,
             source_type: report_source.source_type,
+            source_package_id: nil,
+            source_package_name: report_component.source_package_name,
             version: report_component.version
           }
         )
@@ -105,6 +113,8 @@
             source: report_source.data,
             source_id: nil,
             source_type: report_source.source_type,
+            source_package_id: nil,
+            source_package_name: report_component.source_package_name,
             version: report_component.version
           }
         )
@@ -135,6 +145,7 @@
     it { is_expected.to delegate_method(:input_file_path).to(:report_source).allow_nil }
     it { is_expected.to delegate_method(:name).to(:report_component) }
     it { is_expected.to delegate_method(:version).to(:report_component) }
+    it { is_expected.to delegate_method(:source_package_name).to(:report_component) }
   end
 
   context 'without vulnerability data' do
diff --git a/ee/spec/services/sbom/ingestion/tasks/ingest_occurrences_spec.rb b/ee/spec/services/sbom/ingestion/tasks/ingest_occurrences_spec.rb
index b19026114b6da11885c05a4b66e9a35731341af4..d0900b7da1e500ae9d282e0e1bcb35999bac7178 100644
--- a/ee/spec/services/sbom/ingestion/tasks/ingest_occurrences_spec.rb
+++ b/ee/spec/services/sbom/ingestion/tasks/ingest_occurrences_spec.rb
@@ -49,6 +49,7 @@
           'commit_sha' => pipeline.sha,
           'package_manager' => occurrence_map.packager,
           'input_file_path' => occurrence_map.input_file_path,
+          'source_package_id' => occurrence_map.source_package_id,
           'licenses' => [
             {
               'spdx_identifier' => 'Apache-2.0',
@@ -113,6 +114,15 @@
       end
     end
 
+    context 'when there is no source package' do
+      let(:occurrence_maps) { create_list(:sbom_occurrence_map, 4, :for_occurrence_ingestion, source_package: nil) }
+
+      it 'inserts records without the source package' do
+        expect { ingest_occurrences }.to change(Sbom::Occurrence, :count).by(4)
+        expect(occurrence_maps).to all(have_attributes(occurrence_id: Integer))
+      end
+    end
+
     context 'when there is no purl' do
       let(:component) { create(:ci_reports_sbom_component, purl: nil) }
       let(:occurrence_map) { create(:sbom_occurrence_map, :for_occurrence_ingestion, report_component: component) }
diff --git a/ee/spec/services/sbom/ingestion/tasks/ingest_source_packages_spec.rb b/ee/spec/services/sbom/ingestion/tasks/ingest_source_packages_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..86ff83448a06e73cce15ebf7af350db5874d73f4
--- /dev/null
+++ b/ee/spec/services/sbom/ingestion/tasks/ingest_source_packages_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sbom::Ingestion::Tasks::IngestSourcePackages, feature_category: :dependency_management do
+  describe '#execute' do
+    let_it_be(:pipeline) { build_stubbed(:ci_pipeline) }
+    let_it_be(:occurrence_maps) { build_list(:sbom_occurrence_map, 2, :with_source_package) }
+
+    subject(:ingest_source_package) { described_class.new(pipeline, occurrence_maps) }
+
+    it_behaves_like 'bulk insertable task'
+
+    it 'creates source packages' do
+      expect { ingest_source_package.execute }.to change { Sbom::SourcePackage.count }.by(2)
+      expect(occurrence_maps).to all(have_attributes(source_package_id: Integer))
+    end
+
+    context 'when there is existing source package' do
+      before do
+        map = occurrence_maps.first.to_h
+        create(:sbom_source_package, name: map[:source_package_name], purl_type: map[:purl_type])
+      end
+
+      it 'does not create a new record for the existing source package' do
+        expect { ingest_source_package.execute }.to change { Sbom::SourcePackage.count }.by(1)
+        expect(occurrence_maps).to all(have_attributes(source_package_id: Integer))
+      end
+    end
+
+    context 'with same source package for multiple occurrences' do
+      let_it_be(:source_package) { build(:sbom_source_package) }
+
+      let_it_be(:sbom_components) do
+        [
+          build(:ci_reports_sbom_component, purl_type: source_package.purl_type,
+            source_package_name: source_package.name),
+          build(:ci_reports_sbom_component, purl_type: source_package.purl_type,
+            source_package_name: source_package.name),
+          build(:ci_reports_sbom_component, purl_type: source_package.purl_type,
+            source_package_name: "other_source_package"),
+          build(:ci_reports_sbom_component, purl_type: 'wolfi', source_package_name: source_package.name)
+        ]
+      end
+
+      let_it_be(:occurrence_maps) do
+        sbom_components.map do |component|
+          build(:sbom_occurrence_map, report_component: component)
+        end
+      end
+
+      it 'maps source package id with correct occurrence_maps' do
+        expect { ingest_source_package.execute }.to change { Sbom::SourcePackage.count }.by(3)
+        expect(occurrence_maps).to all(have_attributes(source_package_id: Integer))
+      end
+    end
+  end
+end
diff --git a/spec/factories/ci/reports/sbom/components.rb b/spec/factories/ci/reports/sbom/components.rb
index 231fefff99c75ce3a3c81eee6740a51a1115cb60..091bd92ab185ba163073aa085307ab6fd83ee201 100644
--- a/spec/factories/ci/reports/sbom/components.rb
+++ b/spec/factories/ci/reports/sbom/components.rb
@@ -22,6 +22,10 @@
       )
     end
 
+    trait :with_source_package_name do
+      sequence(:source_package_name) { |n| "source-package-name-#{n}" }
+    end
+
     skip_create
 
     initialize_with do