diff --git a/app/models/concerns/enums/vulnerability.rb b/app/models/concerns/enums/vulnerability.rb
index 4b325de61bcce55c39b18c484396abaaed243c03..dbf05dbc4287bbfe0f689f5df676581e7fba9de5 100644
--- a/app/models/concerns/enums/vulnerability.rb
+++ b/app/models/concerns/enums/vulnerability.rb
@@ -50,6 +50,10 @@ def self.confidence_levels
       CONFIDENCE_LEVELS
     end
 
+    def self.parse_confidence_level(input)
+      input&.downcase.then { |value| confidence_levels.key?(value) ? value : 'unknown' }
+    end
+
     def self.report_types
       REPORT_TYPES
     end
@@ -58,6 +62,10 @@ def self.severity_levels
       SEVERITY_LEVELS
     end
 
+    def self.parse_severity_level(input)
+      input&.downcase.then { |value| severity_levels.key?(value) ? value : 'unknown' }
+    end
+
     def self.detection_methods
       DETECTION_METHODS
     end
diff --git a/ee/lib/gitlab/vulnerability_scanning/dependency_scanning/finding_builder.rb b/ee/lib/gitlab/vulnerability_scanning/dependency_scanning/finding_builder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..16264c5b92ac00e3e494cf2514cc76899ab1b03f
--- /dev/null
+++ b/ee/lib/gitlab/vulnerability_scanning/dependency_scanning/finding_builder.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module VulnerabilityScanning
+    module DependencyScanning
+      class FindingBuilder < VulnerabilityScanning::FindingBuilder
+        include Gitlab::Utils::StrongMemoize
+
+        private
+
+        def validate!
+          if input_file.nil?
+            security_report.add_error('ContinuousDependencyScanning',
+              'Missing required gitlab:dependency_scanning CycloneDX properties')
+          end
+
+          !security_report.errored?
+        end
+
+        def title
+          advisory.title
+        end
+        strong_memoize_attr :title
+
+        def details
+          {
+            vulnerable_package: {
+              name: "Vulnerable Package",
+              type: "text",
+              value: "#{component.name}:#{component.version}"
+            }
+          }.with_indifferent_access.freeze
+        end
+
+        def input_file
+          sbom.source&.input_file_path || sbom.source&.source_file_path
+        end
+        strong_memoize_attr :input_file
+
+        def location
+          ::Gitlab::Ci::Reports::Security::Locations::DependencyScanning.new(
+            file_path: input_file,
+            package_name: component.name,
+            package_version: component.version
+          )
+        end
+        strong_memoize_attr :location
+
+        def original_data
+          {
+            message: title,
+            description: advisory.description,
+            # TODO: There can be more than one package affected by
+            # an advisory. Picking the first one is not a great solution
+            # and is only a temporary implementation.
+            # To be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/417627
+            solution: advisory.affected_packages.first&.solution,
+            location: {
+              file: input_file,
+              dependency: {
+                package: { name: component.name },
+                version: component.version
+              }
+            }
+          }.with_indifferent_access.freeze
+        end
+      end
+    end
+  end
+end
diff --git a/ee/lib/gitlab/vulnerability_scanning/finding_builder.rb b/ee/lib/gitlab/vulnerability_scanning/finding_builder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c66139864c39b24c3a9aa675d0c2a11125d2a7b0
--- /dev/null
+++ b/ee/lib/gitlab/vulnerability_scanning/finding_builder.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module VulnerabilityScanning
+    class FindingBuilder
+      include Gitlab::Utils::StrongMemoize
+
+      def initialize(build, sbom, security_report, component, advisory)
+        @build = build
+        @sbom = sbom
+        @security_report = security_report
+        @component = component
+        @advisory = advisory
+      end
+
+      def finding
+        validate!
+
+        ::Gitlab::Ci::Reports::Security::Finding.new(
+          uuid: uuid,
+          report_type: security_report.type,
+          name: title,
+          compare_key: '',
+          location: location,
+          evidence: nil,
+          severity: ::Enums::Vulnerability.parse_severity_level(severity),
+          confidence: 'unknown',
+          scanner: security_report.primary_scanner,
+          scan: security_report&.scan,
+          identifiers: identifiers,
+          links: links,
+          original_data: original_data,
+          metadata_version: metadata_version,
+          details: details,
+          project_id: build.project.id,
+          found_by_pipeline: build.pipeline)
+      end
+      strong_memoize_attr :finding
+
+      private
+
+      attr_reader :build, :sbom, :security_report, :component, :advisory
+
+      def validate!
+        raise NoMethodError, "#{self.class}#validate! is not implemented"
+      end
+
+      def uuid
+        uuid_v5_name_components = {
+          report_type: security_report.type,
+          primary_identifier_fingerprint: primary_identifier&.fingerprint,
+          location_fingerprint: location_fingerprint,
+          project_id: build.project.id
+        }
+
+        # Inline the comparison to squeeze out some performance
+        if security_report.type.nil? ||
+            primary_identifier&.fingerprint.nil? ||
+            location_fingerprint.nil?
+          Gitlab::AppLogger.warn(message: "One or more UUID name components are nil",
+            components: uuid_v5_name_components)
+          return
+        end
+
+        ::Security::VulnerabilityUUID.generate(
+          report_type: uuid_v5_name_components[:report_type],
+          primary_identifier_fingerprint: uuid_v5_name_components[:primary_identifier_fingerprint],
+          location_fingerprint: uuid_v5_name_components[:location_fingerprint],
+          project_id: uuid_v5_name_components[:project_id]
+        )
+      end
+
+      def primary_identifier
+        identifiers.first
+      end
+
+      def location_fingerprint
+        location&.fingerprint
+      end
+
+      def identifiers
+        advisory.identifiers.map do |identifier|
+          security_report.add_identifier(
+            ::Gitlab::Ci::Reports::Security::Identifier.new(
+              external_type: identifier['type'],
+              external_id: identifier['value'],
+              name: identifier['name'],
+              url: identifier['url']))
+        end
+      end
+      strong_memoize_attr :identifiers
+
+      def title
+        identifier = identifiers.find(&:cve?) || identifiers.find(&:cwe?) || identifiers.first
+
+        if location&.fingerprint_path
+          "#{identifier.name} in #{location.fingerprint_path}"
+        else
+          identifier.name.to_s
+        end
+      end
+      strong_memoize_attr :title
+
+      def links
+        advisory.urls.map { |url| ::Gitlab::Ci::Reports::Security::Link.new(name: nil, url: url) }
+      end
+
+      def severity
+        advisory&.cvss_v3&.severity || advisory&.cvss_v2&.severity
+      end
+
+      def details
+        {}
+      end
+
+      def location
+        raise NoMethodError, "#{self.class}#location is not implemented"
+      end
+
+      def original_data
+        raise NoMethodError, "#{self.class}#original_data is not implemented"
+      end
+
+      def metadata_version
+        "0.0.0"
+      end
+      strong_memoize_attr :metadata_version
+    end
+  end
+end
diff --git a/ee/lib/gitlab/vulnerability_scanning/security_report_builder.rb b/ee/lib/gitlab/vulnerability_scanning/security_report_builder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7662d369f8605f352db4714f4d448e81594ab307
--- /dev/null
+++ b/ee/lib/gitlab/vulnerability_scanning/security_report_builder.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module VulnerabilityScanning
+    class SecurityReportBuilder
+      include Gitlab::Utils::StrongMemoize
+
+      # We don't have a schema version set because there is no JSON to validate.
+      SECURITY_REPORT_VERSION = "0.0.0"
+
+      SCANNER_EXTERNAL_ID = "gitlab-sbom-vulnerability-scanner"
+      SCANNER_NAME = "GitLab SBoM Vulnerability Scanner"
+      SCANNER_VENDOR = "GitLab"
+      SCANNER_VERSION = "0.1.0"
+
+      attr_reader :report
+
+      def initialize(report_type, build, sbom)
+        @report_type = report_type
+        @build = build
+        @sbom = sbom
+        @report = ::Gitlab::Ci::Reports::Security::Report.new(report_type, build.pipeline, Time.zone.now)
+        report.version = SECURITY_REPORT_VERSION
+        report.add_scanner(scanner)
+      end
+
+      def add_component_advisories(component, advisories)
+        advisories.each do |advisory|
+          next if advisory.blank?
+
+          add_finding(component, advisory)
+        end
+      end
+
+      private
+
+      attr_reader :report_type, :build, :sbom
+
+      def scanner
+        ::Gitlab::Ci::Reports::Security::Scanner.new(
+          external_id: SCANNER_EXTERNAL_ID,
+          name: SCANNER_NAME,
+          vendor: SCANNER_VENDOR,
+          version: SCANNER_VERSION)
+      end
+      strong_memoize_attr :scanner
+
+      def add_finding(component, advisory)
+        builder = case report_type
+                  when "dependency_scanning"
+                    DependencyScanning::FindingBuilder.new(build, sbom, report, component, advisory)
+                  end
+
+        return unless builder
+
+        finding = builder.finding
+        report.add_finding(finding)
+      end
+    end
+  end
+end
diff --git a/ee/spec/factories/package_metadata/pm_identifiers.rb b/ee/spec/factories/package_metadata/pm_identifiers.rb
index 89c6a94e0c0da860bf3f4d337cc5e97ba684b4b0..21f66140fd164bfb58aeba08c54227287fc9299b 100644
--- a/ee/spec/factories/package_metadata/pm_identifiers.rb
+++ b/ee/spec/factories/package_metadata/pm_identifiers.rb
@@ -4,11 +4,18 @@
   factory :pm_identifier, class: Hash do
     trait :cve do
       type { 'cve' }
-      name { 'CVE-2023-27797' }
+      sequence(:name) { |n| "CVE-#{Date.today.year}-#{n.to_s.rjust(5, '0')}" }
       url { "https://nvd.nist.gov/vuln/detail/#{name}" }
       value { name }
     end
 
+    trait :cwe do
+      type { 'cwe' }
+      sequence(:name) { |n| "CWE-#{100 + n}" }
+      sequence(:url) { |n| "https://cwe.mitre.org/data/definitions/#{100 + n}.html" }
+      value { name }
+    end
+
     trait :gemnasium do
       type { 'gemnasium' }
       name { "Gemnasium-#{value}" }
diff --git a/ee/spec/fixtures/security_reports/simple/gl-dependency-scanning-report.json b/ee/spec/fixtures/security_reports/simple/gl-dependency-scanning-report.json
new file mode 100644
index 0000000000000000000000000000000000000000..109b13e2697782b4fa0d54a10e7453b0974fa454
--- /dev/null
+++ b/ee/spec/fixtures/security_reports/simple/gl-dependency-scanning-report.json
@@ -0,0 +1,148 @@
+{
+  "version": "15.0.6",
+  "vulnerabilities": [
+    {
+      "id": "2469fa54bd2cfd62163a907a8923b656885f514878c24ad126ac7ecceb009f52",
+      "name": "Allocation of File Descriptors or Handles Without Limits or Throttling",
+      "description": "Minio a Allocation of Memory Without Limits or Throttling vulnerability in write-to-RAM.",
+      "severity": "High",
+      "solution": "Unfortunately, there is no solution available yet.",
+      "location": {
+        "file": "go.mod",
+        "dependency": {
+          "package": {
+            "name": "github.com/minio/minio"
+          },
+          "version": "v0.0.0-20180419184637-5a16671f721f"
+        }
+      },
+      "identifiers": [
+        {
+          "type": "gemnasium",
+          "name": "Gemnasium-051e7fdd-4e0a-4dfd-ba52-083ee235a580",
+          "value": "051e7fdd-4e0a-4dfd-ba52-083ee235a580",
+          "url": "https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/blob/v1.2.142/go/github.com/minio/minio/CVE-2018-1000538.yml"
+        },
+        {
+          "type": "cve",
+          "name": "CVE-2018-1000538",
+          "value": "CVE-2018-1000538",
+          "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000538"
+        }
+      ],
+      "links": [
+        {
+          "url": "https://github.com/minio/minio/pull/5957"
+        },
+        {
+          "url": "https://nvd.nist.gov/vuln/detail/CVE-2018-1000538"
+        }
+      ],
+      "details": {
+        "vulnerable_package": {
+          "type": "text",
+          "name": "Vulnerable Package",
+          "value": "github.com/minio/minio:v0.0.0-20180419184637-5a16671f721f"
+        }
+      }
+    },
+    {
+      "id": "050e975be58be7b10a8b542e3e6a1d8e24727e6065f83290c8f3ba90158d9f83",
+      "name": "Improper Authentication",
+      "description": "MinIO versions before has an authentication bypass issue in the MinIO admin API. Given an admin access key, it is possible to perform admin API operations, i.e., creating new service accounts for existing access keys without knowing the admin secret key.",
+      "severity": "High",
+      "solution": "Upgrade to version RELEASE.2020-04-23T00-58-49Z or above.",
+      "location": {
+        "file": "go.mod",
+        "dependency": {
+          "package": {
+            "name": "github.com/minio/minio"
+          },
+          "version": "v0.0.0-20180419184637-5a16671f721f"
+        }
+      },
+      "identifiers": [
+        {
+          "type": "gemnasium",
+          "name": "Gemnasium-216192fe-2efa-4c52-addd-4bf3522c2b69",
+          "value": "216192fe-2efa-4c52-addd-4bf3522c2b69",
+          "url": "https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/blob/v1.2.142/go/github.com/minio/minio/CVE-2020-11012.yml"
+        },
+        {
+          "type": "cve",
+          "name": "CVE-2020-11012",
+          "value": "CVE-2020-11012",
+          "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11012"
+        }
+      ],
+      "links": [
+        {
+          "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-11012"
+        }
+      ],
+      "details": {
+        "vulnerable_package": {
+          "type": "text",
+          "name": "Vulnerable Package",
+          "value": "github.com/minio/minio:v0.0.0-20180419184637-5a16671f721f"
+        }
+      }
+    }
+  ],
+  "dependency_files": [
+    {
+      "path": "go.mod",
+      "package_manager": "go",
+      "dependencies": [
+        {
+          "package": {
+            "name": "github.com/minio/minio"
+          },
+          "version": "v0.0.0-20180419184637-5a16671f721f"
+        },
+        {
+          "package": {
+            "name": "github.com/minio/minio-go"
+          },
+          "version": "v6.0.14+incompatible"
+        },
+        {
+          "package": {
+            "name": "github.com/minio/sha256-simd"
+          },
+          "version": "v0.1.1"
+        },
+        {
+          "package": {
+            "name": "github.com/sirupsen/logrus"
+          },
+          "version": "v1.4.2"
+        }
+      ]
+    }
+  ],
+  "scan": {
+    "analyzer": {
+      "id": "gemnasium",
+      "name": "Gemnasium",
+      "url": "https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium",
+      "vendor": {
+        "name": "GitLab"
+      },
+      "version": "3.9.6"
+    },
+    "scanner": {
+      "id": "gemnasium",
+      "name": "Gemnasium",
+      "url": "https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium",
+      "vendor": {
+        "name": "GitLab"
+      },
+      "version": "3.9.6"
+    },
+    "type": "dependency_scanning",
+    "start_time": "2022-10-14T01:22:31",
+    "end_time": "2022-10-14T01:22:34",
+    "status": "success"
+  }
+}
diff --git a/ee/spec/lib/gitlab/vulnerability_scanning/dependency_scanning/finding_builder_spec.rb b/ee/spec/lib/gitlab/vulnerability_scanning/dependency_scanning/finding_builder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..375e19f7ac1ddb2c526e415b6cf7b71c3b884f9a
--- /dev/null
+++ b/ee/spec/lib/gitlab/vulnerability_scanning/dependency_scanning/finding_builder_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Gitlab::VulnerabilityScanning::DependencyScanning::FindingBuilder, feature_category: :software_composition_analysis do
+  let(:ci_build) { build(:ci_build) }
+  let(:sbom) { build(:ci_reports_sbom_report, source: sbom_source) }
+  let(:now) { Time.zone.now }
+  let(:security_report_type) { "dependency_scanning" }
+  let(:security_report) { Gitlab::Ci::Reports::Security::Report.new(security_report_type, ci_build.pipeline, now) }
+
+  let(:component) do
+    build(:ci_reports_sbom_component, name: "github.com/minio/minio", version: "v0.0.0-20180419184637-5a16671f721f",
+      purl_type: "golang")
+  end
+
+  let(:advisory) do
+    build(:pm_advisory,
+      published_date: "2018-06-26",
+      title: "Allocation of File Descriptors or Handles Without Limits or Throttling",
+      description: "Minio a Allocation of Memory Without Limits or Throttling vulnerability in write-to-RAM.",
+      cvss_v2: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
+      cvss_v3: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+      identifiers: [
+        build(:pm_identifier, type: "cve", name: "CVE-2018-1000538",
+          url: "https://nvd.nist.gov/vuln/detail/CVE-2018-1000538", value: "CVE-2018-1000538")
+      ],
+      urls: ["https://nvd.nist.gov/vuln/detail/CVE-2018-1000538", "https://github.com/minio/minio/pull/5957"])
+  end
+
+  subject(:builder) { described_class.new(ci_build, sbom, security_report, component, advisory) }
+
+  describe "#finding" do
+    let(:finding) { builder.finding }
+
+    context "when cyclonedx sbom contains required gitlab:dependency_scanning properties" do
+      let(:sbom_source) { build(:ci_reports_sbom_source, input_file_path: "go.mod", source_file_path: "go.mod") }
+
+      it "does not add any errors to the report" do
+        expect(security_report.errored?).to eq(false)
+      end
+
+      it "creates the links" do
+        expect(finding.links).to match_array([
+          have_attributes(url: "https://nvd.nist.gov/vuln/detail/CVE-2018-1000538"),
+          have_attributes(url: "https://github.com/minio/minio/pull/5957")
+        ])
+      end
+
+      it "creates the name" do
+        expect(finding.name).to eq(advisory.title)
+      end
+
+      it "creates the description" do
+        expect(finding.description).to eq(advisory.description)
+      end
+
+      it "creates a valid location" do
+        expect(finding.location).to have_attributes(
+          file_path: "go.mod",
+          package_name: "github.com/minio/minio",
+          package_version: "v0.0.0-20180419184637-5a16671f721f"
+        )
+      end
+
+      it "creates the severity" do
+        expect(finding.severity).to eq("high")
+      end
+
+      it "creates the confidence" do
+        expect(finding.confidence).to eq("unknown")
+      end
+
+      it "creates the metadata version" do
+        expect(finding.metadata_version).to eq("0.0.0")
+      end
+    end
+
+    context "when cyclonedx does not contain required gitlab:dependency_scanning properties" do
+      let(:sbom_source) { build(:ci_reports_sbom_source, input_file_path: nil, source_file_path: nil) }
+
+      before do
+        builder.finding
+      end
+
+      it "adds an error to the generated report" do
+        expect(security_report.errored?).to eq(true)
+        expect(security_report.errors).to match_array([
+          { type: "ContinuousDependencyScanning",
+            message: "Missing required gitlab:dependency_scanning CycloneDX properties" }
+        ])
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/gitlab/vulnerability_scanning/finding_builder_spec.rb b/ee/spec/lib/gitlab/vulnerability_scanning/finding_builder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac49361d4c96def3c987c76cbf0b1e12ec4d2b5e
--- /dev/null
+++ b/ee/spec/lib/gitlab/vulnerability_scanning/finding_builder_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Gitlab::VulnerabilityScanning::FindingBuilder, feature_category: :software_composition_analysis do
+  let_it_be(:ci_build) { build(:ci_build) }
+  let_it_be(:sbom) { build(:ci_reports_sbom_report) }
+  let_it_be(:location) { build(:ci_reports_security_locations_sast) }
+  let_it_be(:report_type) { :dependency_scanning }
+  let_it_be(:security_report) { build(:ci_reports_security_report, type: report_type) }
+  let_it_be(:component) { build(:ci_reports_sbom_component) }
+  let_it_be(:advisory) { build(:pm_advisory) }
+
+  subject(:builder) { described_class.new(ci_build, sbom, security_report, component, advisory) }
+
+  describe "#finding" do
+    context "when abstract methods have not been implemented" do
+      context "when validataion has not been implemented" do
+        it { expect { builder.finding }.to raise_error(NoMethodError, /#validate! is not implemented/) }
+      end
+
+      context "when location has not been implemented" do
+        before do
+          allow(builder).to receive(:validate!).and_return(nil)
+        end
+
+        it { expect { builder.finding }.to raise_error(NoMethodError, /#location is not implemented/) }
+      end
+
+      context "when original_data has not been implemented" do
+        before do
+          allow(builder).to receive(:validate!).and_return(nil)
+          allow(builder).to receive(:location).and_return(location)
+        end
+
+        it { expect { builder.finding }.to raise_error(NoMethodError, /#original_data is not implemented/) }
+      end
+    end
+
+    shared_examples 'emits warning for uuid' do
+      it 'emits a warning for the missing UUID component' do
+        expect(Gitlab::AppLogger).to receive(:warn).with(message: "One or more UUID name components are nil",
+          components: Hash)
+
+        builder.finding
+      end
+    end
+
+    context "when creating uuid" do
+      before do
+        allow(Gitlab::AppLogger).to receive(:warn).and_call_original
+        allow(builder).to receive(:validate!).and_return(nil)
+        allow(builder).to receive(:location_fingerprint).and_return("01234567890abcdef")
+        allow(builder).to receive(:location).and_return(location)
+        allow(builder).to receive(:original_data).and_return({})
+      end
+
+      context "when security_report.type is nil" do
+        let_it_be(:security_report) { build(:ci_reports_security_report, type: nil) }
+
+        it_behaves_like 'emits warning for uuid'
+      end
+
+      context "when primary_identifier is nil" do
+        before do
+          allow(builder).to receive(:primary_identifier).and_return(nil)
+        end
+
+        it_behaves_like 'emits warning for uuid'
+      end
+
+      context "when location_fingerprint is nil" do
+        before do
+          allow(builder).to receive(:location_fingerprint).and_return(nil)
+        end
+
+        it_behaves_like 'emits warning for uuid'
+      end
+
+      context "when all uuid components are present" do
+        it 'does not emit a warning for the missing UUID component' do
+          expect(Gitlab::AppLogger).not_to receive(:warn)
+
+          builder.finding
+        end
+      end
+    end
+
+    context "when creating finding name" do
+      before do
+        allow(builder).to receive(:validate!).and_return(nil)
+        allow(builder).to receive(:original_data).and_return({})
+      end
+
+      context "when location fingerprint path exists" do
+        before do
+          allow(builder).to receive(:location).and_return(location)
+        end
+
+        it { expect(builder.finding.name).to eq("CVE-2023-00001 in App.java") }
+      end
+
+      context "when location fingerprint path does not exist" do
+        before do
+          allow(builder).to receive(:location).and_return(nil)
+        end
+
+        it { expect(builder.finding.name).to eq("CVE-2023-00001") }
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/gitlab/vulnerability_scanning/security_report_builder_spec.rb b/ee/spec/lib/gitlab/vulnerability_scanning/security_report_builder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..17098edae368dff7e2cfa2a046ed8b2679b4592d
--- /dev/null
+++ b/ee/spec/lib/gitlab/vulnerability_scanning/security_report_builder_spec.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::VulnerabilityScanning::SecurityReportBuilder, feature_category: :software_composition_analysis do
+  let(:report_type) { "dependency_scanning" }
+  let_it_be(:sbom_source) { build(:ci_reports_sbom_source) }
+  let_it_be(:sbom) do
+    build(:ci_reports_sbom_report,
+      source: build(:ci_reports_sbom_source, input_file_path: "go.mod", source_file_path: "go.mod"))
+  end
+
+  let_it_be(:ci_build) { build(:ci_build) }
+
+  let_it_be(:minio_component) do
+    build(:ci_reports_sbom_component, name: "github.com/minio/minio", version: "v0.0.0-20180419184637-5a16671f721f",
+      purl_type: "golang")
+  end
+
+  let_it_be(:cve_2018_1000538) do
+    create(:pm_advisory,
+      published_date: "2018-06-26",
+      title: "Allocation of File Descriptors or Handles Without Limits or Throttling",
+      description: "Minio a Allocation of Memory Without Limits or Throttling vulnerability in write-to-RAM.",
+      cvss_v2: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
+      cvss_v3: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+      identifiers: [
+        build(:pm_identifier, type: "gemnasium", name: "Gemnasium-051e7fdd-4e0a-4dfd-ba52-083ee235a580",
+          value: "051e7fdd-4e0a-4dfd-ba52-083ee235a580", url: "https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/blob/v1.2.142/go/github.com/minio/minio/CVE-2018-1000538.yml"),
+        build(:pm_identifier, type: "cve", name: "CVE-2018-1000538",
+          url: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000538", value: "CVE-2018-1000538")
+      ],
+      urls: ["https://github.com/minio/minio/pull/5957", "https://nvd.nist.gov/vuln/detail/CVE-2018-1000538"])
+  end
+
+  let_it_be(:affected_package_1) do
+    create(:pm_affected_package, advisory: cve_2018_1000538,
+      solution: "Unfortunately, there is no solution available yet.")
+  end
+
+  let_it_be(:cve_2020_11012) do
+    create(:pm_advisory,
+      published_date: "2020-04-23",
+      title: "Improper Authentication",
+      description: "MinIO versions before has an authentication bypass issue in the MinIO admin API. " \
+                   "Given an admin access key, it is possible to perform admin API operations, i.e., " \
+                   "creating new service accounts for existing access keys without knowing the admin secret key.",
+      cvss_v2: "AV:N/AC:L/Au:N/C:N/I:P/A:N",
+      cvss_v3: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
+      identifiers: [
+        build(:pm_identifier, type: "gemnasium", name: "Gemnasium-216192fe-2efa-4c52-addd-4bf3522c2b69",
+          value: "216192fe-2efa-4c52-addd-4bf3522c2b69", url: "https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/blob/v1.2.142/go/github.com/minio/minio/CVE-2020-11012.yml"),
+        build(:pm_identifier, type: "cve", name: "CVE-2020-11012",
+          url: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11012", value: "CVE-2020-11012")
+      ],
+      urls: ["https://nvd.nist.gov/vuln/detail/CVE-2020-11012"])
+  end
+
+  let_it_be(:affected_package_2) do
+    create(:pm_affected_package, advisory: cve_2020_11012,
+      solution: "Upgrade to version RELEASE.2020-04-23T00-58-49Z or above.")
+  end
+
+  let(:dependency_scanning_json_report) do
+    File.read(
+      Rails.root.join('ee/spec/fixtures/security_reports/simple/gl-dependency-scanning-report.json')
+    )
+  end
+
+  let(:created_at) { Time.zone.now }
+  let(:expected) { Gitlab::Ci::Reports::Security::Report.new(report_type, ci_build.pipeline, created_at) }
+
+  subject(:builder) { described_class.new(report_type, ci_build, sbom) }
+
+  before do
+    Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(dependency_scanning_json_report, expected, validate: true)
+    builder.add_component_advisories(minio_component, [cve_2018_1000538, cve_2020_11012])
+  end
+
+  describe "#report" do
+    context "when components are vulnerable" do
+      it "builds a valid report" do
+        expect(builder.report.errored?).to eq(false)
+        expect(builder.report.warnings?).to eq(false)
+      end
+
+      it "adds correct findings" do
+        attributes = %i[
+          report_type
+          project_fingerprint
+          compare_key
+          uuid
+          name
+          description
+          solution
+          identifiers
+          flags
+          links
+          location
+          evidence
+          severity
+          confidence
+          details
+          signatures
+        ].freeze
+
+        convert_to_hash = ->(finding) { finding.to_hash.slice(*attributes) }
+        findings = builder.report.findings.map(&convert_to_hash)
+        expected_findings = expected.findings.map(&convert_to_hash)
+
+        expect(findings).to match_array(expected_findings)
+      end
+
+      it "adds correct identifiers" do
+        expect(builder.report.identifiers).to match_array(expected.identifiers)
+      end
+
+      it "does not produce or remove findings when compared against analyzer report" do
+        comparer = Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(ci_build.project, expected,
+          builder.report)
+        expect(comparer.added).to be_empty
+        expect(comparer.fixed).to be_empty
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/parsers/security/common.rb b/lib/gitlab/ci/parsers/security/common.rb
index 21408beb8cb590226a81be46118c0ab677638a8b..ee1da82f285e57b580e200db22e0812a89070e9a 100644
--- a/lib/gitlab/ci/parsers/security/common.rb
+++ b/lib/gitlab/ci/parsers/security/common.rb
@@ -126,8 +126,8 @@ def create_finding(data, remediations = [])
                 compare_key: data['cve'] || '',
                 location: location,
                 evidence: evidence,
-                severity: parse_severity_level(data['severity']),
-                confidence: parse_confidence_level(data['confidence']),
+                severity: ::Enums::Vulnerability.parse_severity_level(data['severity']),
+                confidence: ::Enums::Vulnerability.parse_confidence_level(data['confidence']),
                 scanner: create_scanner(top_level_scanner_data || data['scanner']),
                 scan: report&.scan,
                 identifiers: identifiers,
@@ -260,14 +260,6 @@ def create_link(link)
             ::Gitlab::Ci::Reports::Security::Link.new(name: link['name'], url: link['url'])
           end
 
-          def parse_severity_level(input)
-            input&.downcase.then { |value| ::Enums::Vulnerability.severity_levels.key?(value) ? value : 'unknown' }
-          end
-
-          def parse_confidence_level(input)
-            input&.downcase.then { |value| ::Enums::Vulnerability.confidence_levels.key?(value) ? value : 'unknown' }
-          end
-
           def create_location(location_data)
             raise NotImplementedError
           end
diff --git a/lib/gitlab/ci/reports/sbom/source.rb b/lib/gitlab/ci/reports/sbom/source.rb
index fbb8644c1b0913ac5bf8d5c0c34dc3f966fbc7b3..b7af6ea17c3d1555ddf7b227946dc0f89bd4e3a5 100644
--- a/lib/gitlab/ci/reports/sbom/source.rb
+++ b/lib/gitlab/ci/reports/sbom/source.rb
@@ -11,6 +11,22 @@ def initialize(type:, data:)
             @source_type = type
             @data = data
           end
+
+          def source_file_path
+            data.dig('source_file', 'path')
+          end
+
+          def input_file_path
+            data.dig('input_file', 'path')
+          end
+
+          def packager
+            data.dig('package_manager', 'name')
+          end
+
+          def language
+            data.dig('language', 'name')
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/reports/security/link.rb b/lib/gitlab/ci/reports/security/link.rb
index 1c4c05cd9ac1bac4799448a4ddf2d2ac2cf849d7..6804d2b2a29a2a5730bc23ecf4ad65d421cfb5a6 100644
--- a/lib/gitlab/ci/reports/security/link.rb
+++ b/lib/gitlab/ci/reports/security/link.rb
@@ -18,6 +18,10 @@ def to_hash
               url: url
             }.compact
           end
+
+          def ==(other)
+            name == other.name && url == other.url
+          end
         end
       end
     end
diff --git a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
index 63b8e5fdf01a8f1b3a4ec281c683583bd581c9da..c1eaea511b73c9e4ae55d5689a00639d8432b37b 100644
--- a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
@@ -24,4 +24,28 @@
       data: attributes[:data]
     )
   end
+
+  describe '#source_file_path' do
+    it 'returns the correct source_file_path' do
+      expect(subject.source_file_path).to eq('package.json')
+    end
+  end
+
+  describe '#input_file_path' do
+    it 'returns the correct input_file_path' do
+      expect(subject.input_file_path).to eq("package-lock.json")
+    end
+  end
+
+  describe '#packager' do
+    it 'returns the correct package manager name' do
+      expect(subject.packager).to eq("npm")
+    end
+  end
+
+  describe '#language' do
+    it 'returns the correct langauge' do
+      expect(subject.language).to eq("JavaScript")
+    end
+  end
 end