diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c592421acfa8c7cbaea7198eac27384d01f90457..ef0866537699c2deba33caca671473eb254f8891 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -366,22 +366,6 @@ DETAILS:
 
 Returns [`CloudConnectorStatus`](#cloudconnectorstatus).
 
-### `Query.components`
-
-Find software dependencies by name.
-
-DETAILS:
-**Introduced** in GitLab 17.4.
-**Status**: Experiment.
-
-Returns [`[Component!]`](#component).
-
-#### Arguments
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| <a id="querycomponentsname"></a>`name` | [`String`](#string) | Entire name or part of the name. |
-
 ### `Query.containerRepository`
 
 Find a container repository.
@@ -23525,6 +23509,22 @@ four standard [pagination arguments](#pagination-arguments):
 | <a id="groupcomplianceframeworksids"></a>`ids` | [`[ComplianceManagementFrameworkID!]`](#compliancemanagementframeworkid) | List of Global IDs of compliance frameworks to return. |
 | <a id="groupcomplianceframeworkssearch"></a>`search` | [`String`](#string) | Search framework with most similar names. |
 
+##### `Group.components`
+
+Find software dependencies by name.
+
+DETAILS:
+**Introduced** in GitLab 17.5.
+**Status**: Experiment.
+
+Returns [`[Component!]`](#component).
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="groupcomponentsname"></a>`name` | [`String`](#string) | Entire name or part of the name. |
+
 ##### `Group.contactStateCounts`
 
 Counts of contacts by state for the group.
diff --git a/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue b/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue
index 89030d3fbbedb8061d4ae124603b954b55b4250c..06feee51d9eda41965848d621358bea375157bfd 100644
--- a/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue
+++ b/ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/component_token.vue
@@ -9,7 +9,7 @@ import {
 import { createAlert } from '~/alert';
 import { s__ } from '~/locale';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import componentsQuery from 'ee/dependencies/graphql/components.query.graphql';
+import componentsQuery from 'ee/dependencies/graphql/group_components.query.graphql';
 
 export default {
   components: {
@@ -81,11 +81,12 @@ export default {
       variables() {
         return {
           name: this.searchTerm,
+          fullPath: this.groupFullPath,
         };
       },
       update(data) {
         // Remove __typename
-        return data.components.map(({ id, name }) => ({ name, id }));
+        return data.group?.components?.map(({ id, name }) => ({ name, id }));
       },
       error() {
         createAlert({
diff --git a/ee/app/assets/javascripts/dependencies/graphql/components.query.graphql b/ee/app/assets/javascripts/dependencies/graphql/components.query.graphql
deleted file mode 100644
index 80140508c5370ff7aab3a22bf6cefe61fd7b9596..0000000000000000000000000000000000000000
--- a/ee/app/assets/javascripts/dependencies/graphql/components.query.graphql
+++ /dev/null
@@ -1,6 +0,0 @@
-query components($name: String) {
-  components(name: $name) {
-    id
-    name
-  }
-}
diff --git a/ee/app/assets/javascripts/dependencies/graphql/group_components.query.graphql b/ee/app/assets/javascripts/dependencies/graphql/group_components.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..0d097e903ed48d895a974d89881adf4296c169b0
--- /dev/null
+++ b/ee/app/assets/javascripts/dependencies/graphql/group_components.query.graphql
@@ -0,0 +1,9 @@
+query components($name: String, $fullPath: ID!) {
+  group(fullPath: $fullPath) {
+    id
+    components(name: $name) {
+      id
+      name
+    }
+  }
+}
diff --git a/ee/app/controllers/groups/dependencies_controller.rb b/ee/app/controllers/groups/dependencies_controller.rb
index 24f39daf8c728182feeb075e5b7391bf4396daa8..24f65a0cc5f302287bb94da3e789f21064b533ae 100644
--- a/ee/app/controllers/groups/dependencies_controller.rb
+++ b/ee/app/controllers/groups/dependencies_controller.rb
@@ -108,7 +108,8 @@ def dependencies_finder_params
                           licenses: [],
                           package_managers: [],
                           project_ids: [],
-                          component_ids: []
+                          component_ids: [],
+                          component_names: []
                         )
                       else
                         params.permit(:cursor, :page, :per_page, :sort, :sort_by)
diff --git a/ee/app/finders/sbom/aggregations_finder.rb b/ee/app/finders/sbom/aggregations_finder.rb
index e1fc603129ed33a38a2d10bb062aacc08e523d2d..6be7e63a7d6d91b559e5b53004cc89ee23e9cfd3 100644
--- a/ee/app/finders/sbom/aggregations_finder.rb
+++ b/ee/app/finders/sbom/aggregations_finder.rb
@@ -88,6 +88,7 @@ def inner_occurrences
 
       relation = filter_by_licences(relation)
       relation = filter_by_component_ids(relation)
+      relation = filter_by_component_names(relation)
 
       relation
         .order(inner_order)
@@ -108,6 +109,13 @@ def filter_by_component_ids(relation)
       relation.filter_by_component_ids(params[:component_ids])
     end
 
+    def filter_by_component_names(relation)
+      return relation if Feature.disabled?(:group_level_dependencies_filtering_by_component, namespace)
+      return relation unless params[:component_names].present?
+
+      relation.filter_by_component_names(params[:component_names])
+    end
+
     def inner_order
       evaluator = ->(column) { column_expression(column) }
 
diff --git a/ee/app/finders/sbom/components_finder.rb b/ee/app/finders/sbom/components_finder.rb
index 1329d1971065ab593795dd221a28750f52a15e9c..d36173dcdd41675c3c2f801300a3e205b33c0a22 100644
--- a/ee/app/finders/sbom/components_finder.rb
+++ b/ee/app/finders/sbom/components_finder.rb
@@ -2,24 +2,17 @@
 
 module Sbom
   class ComponentsFinder
-    DEFAULT_MAX_RESULTS = 30
-
-    def initialize(name)
-      @name = name
+    def initialize(namespace, query = nil)
+      @namespace = namespace
+      @query = query
     end
 
     def execute
-      components
+      Sbom::Component.by_namespace(namespace, query)
     end
 
     private
 
-    attr_reader :name
-
-    def components
-      return Sbom::Component.limit(DEFAULT_MAX_RESULTS) unless name
-
-      Sbom::Component.by_name(name).limit(DEFAULT_MAX_RESULTS)
-    end
+    attr_reader :namespace, :query
   end
 end
diff --git a/ee/app/graphql/ee/types/group_type.rb b/ee/app/graphql/ee/types/group_type.rb
index 0cc226071f4c1f5c88b5a1b8b85a6e7049d080db..2563d6afc337db9c219f8ad3787e330e8c3317c6 100644
--- a/ee/app/graphql/ee/types/group_type.rb
+++ b/ee/app/graphql/ee/types/group_type.rb
@@ -318,6 +318,14 @@ module GroupType
           resolver: ::Resolvers::Sbom::DependenciesResolver,
           description: 'Software dependencies used by projects under this group.'
 
+        field :components,
+          [::Types::Sbom::ComponentType],
+          null: true,
+          authorize: :read_dependency,
+          description: 'Find software dependencies by name.',
+          resolver: ::Resolvers::Sbom::ComponentResolver,
+          alpha: { milestone: '17.5' }
+
         def billable_members_count(requested_hosted_plan: nil)
           object.billable_members_count(requested_hosted_plan)
         end
diff --git a/ee/app/graphql/ee/types/query_type.rb b/ee/app/graphql/ee/types/query_type.rb
index 93ea957d9d6e11e6840f60cd94a331ef538ee1c5..3f253702e09b0fbda552947d28cc868a6bad1fe4 100644
--- a/ee/app/graphql/ee/types/query_type.rb
+++ b/ee/app/graphql/ee/types/query_type.rb
@@ -220,13 +220,6 @@ module QueryType
           alpha: { milestone: '17.4' },
           description: 'Find a project secrets manager.',
           resolver: ::Resolvers::SecretsManagement::ProjectSecretsManagerResolver
-
-        field :components,
-          [::Types::Sbom::ComponentType],
-          null: true,
-          description: 'Find software dependencies by name.',
-          resolver: ::Resolvers::Sbom::ComponentResolver,
-          alpha: { milestone: '17.4' }
       end
 
       def vulnerability(id:)
diff --git a/ee/app/graphql/resolvers/sbom/component_resolver.rb b/ee/app/graphql/resolvers/sbom/component_resolver.rb
index fef4af4f8bdc100e9373fafc5de6563c2899c468..934fa48335110c2cacf73f5f67f7a88500d76c1c 100644
--- a/ee/app/graphql/resolvers/sbom/component_resolver.rb
+++ b/ee/app/graphql/resolvers/sbom/component_resolver.rb
@@ -3,6 +3,8 @@
 module Resolvers
   module Sbom
     class ComponentResolver < BaseResolver
+      include Gitlab::Graphql::Authorize::AuthorizeResource
+
       type [::Types::Sbom::ComponentType], null: true
 
       description 'Software dependencies, optionally filtered by name'
@@ -10,8 +12,10 @@ class ComponentResolver < BaseResolver
       argument :name, ::GraphQL::Types::String, required: false,
         description: 'Entire name or part of the name.'
 
+      alias_method :namespace, :object
+
       def resolve(name: nil)
-        ::Sbom::ComponentsFinder.new(name).execute
+        ::Sbom::ComponentsFinder.new(namespace, name).execute
       end
     end
   end
diff --git a/ee/app/helpers/dependencies_helper.rb b/ee/app/helpers/dependencies_helper.rb
index 27e33be29d7dde7cd2c08170c0cbe9b2d8d93afa..c9913ebb2a8ab69315046d5823c4e0f62e39e0d7 100644
--- a/ee/app/helpers/dependencies_helper.rb
+++ b/ee/app/helpers/dependencies_helper.rb
@@ -23,6 +23,7 @@ def group_dependencies_data(group, below_group_limit)
       locations_endpoint: locations_group_dependencies_path(group),
       export_endpoint: expose_path(api_v4_groups_dependency_list_exports_path(id: group.id)),
       vulnerabilities_endpoint: expose_path(api_v4_occurrences_vulnerabilities_path),
+      group_full_path: group.full_path,
       below_group_limit: below_group_limit.to_s
     })
   end
diff --git a/ee/app/models/sbom/component.rb b/ee/app/models/sbom/component.rb
index 3667f9e68e525d36f0b55e2c063e14ee86802df6..1f3ede3aa1eaee9931b9428bf6554c398e1dfcbc 100644
--- a/ee/app/models/sbom/component.rb
+++ b/ee/app/models/sbom/component.rb
@@ -22,7 +22,87 @@ class Component < ::Gitlab::Database::SecApplicationRecord
     end
 
     scope :by_name, ->(name) do
-      where(Sbom::Component.arel_table[:name].matches("%#{name}%"))
+      where('name ILIKE ?', "%#{sanitize_sql_like(name)}%") # rubocop:disable GitlabSecurity/SqlInjection -- using sanitize_sql_like here
+    end
+
+    DEFAULT_COMPONENT_NAMES_LIMIT = 30
+    def self.by_namespace(namespace, query, limit = DEFAULT_COMPONENT_NAMES_LIMIT)
+      return Sbom::Component.none unless namespace.is_a?(Group)
+
+      component_names_group_query(
+        namespace.traversal_ids,
+        namespace.next_traversal_ids,
+        query,
+        limit
+      )
+    end
+
+    # In addition we need to perform a loose index scan with custom collation for performance reasons.
+    # Sorting can be unpredictable for words containing non-ASCII characters, but dependency names
+    # are usually ASCII
+    # See https://gitlab.com/gitlab-org/gitlab/-/issues/442407#note_2099802302 for performance
+    def self.component_names_group_query(start_id, end_id, query, limit)
+      query ||= ""
+
+      sql = <<~SQL
+        WITH RECURSIVE component_names AS (
+          SELECT
+            *
+          FROM (
+              SELECT
+                traversal_ids,
+                component_name,
+                component_id
+              FROM
+                sbom_occurrences
+              WHERE
+                traversal_ids >= '{:start_id}'
+                AND traversal_ids < '{:end_id}'
+                AND component_name LIKE :query COLLATE "C"
+              ORDER BY
+                sbom_occurrences.component_name COLLATE "C" ASC
+              LIMIT 1
+            ) sub_select
+          UNION ALL
+          SELECT
+            lateral_query.traversal_ids,
+            lateral_query.component_name,
+            lateral_query.component_id
+          FROM
+            component_names,
+            LATERAL (
+              SELECT
+                sbom_occurrences.traversal_ids,
+                sbom_occurrences.component_name,
+                sbom_occurrences.component_id
+              FROM
+                sbom_occurrences
+              WHERE
+                sbom_occurrences.traversal_ids >= '{:start_id}'
+                AND sbom_occurrences.traversal_ids < '{:end_id}'
+                AND component_name LIKE :query COLLATE "C"
+                AND sbom_occurrences.component_name > component_names.component_name
+              ORDER BY
+                sbom_occurrences.component_name COLLATE "C" ASC
+              LIMIT 1
+            ) lateral_query
+        )
+        SELECT
+          component_names.component_id AS id
+        FROM
+          component_names
+      SQL
+
+      sanitized_query = sanitize_sql_like(query)
+
+      query_params = {
+        start_id: start_id,
+        end_id: end_id,
+        query: "#{sanitized_query}%"
+      }
+
+      sql = sanitize_sql_array([sql, query_params])
+      where("id IN (#{sql})").order(name: :asc).limit(limit) # rubocop:disable GitlabSecurity/SqlInjection -- sanitized above
     end
   end
 end
diff --git a/ee/spec/finders/sbom/aggregations_finder_spec.rb b/ee/spec/finders/sbom/aggregations_finder_spec.rb
index b3ae5ccedaf0f6f0aed85d1ca5658841c1a00231..1a663248f92136402e72878a14ea87e261e533de 100644
--- a/ee/spec/finders/sbom/aggregations_finder_spec.rb
+++ b/ee/spec/finders/sbom/aggregations_finder_spec.rb
@@ -219,6 +219,42 @@ def occurrence(name:, severity:, traits: [])
       end
     end
 
+    describe 'filtering by component names' do
+      let_it_be(:component_1) { create(:sbom_component, name: 'activerecord') }
+      let_it_be(:component_2) { create(:sbom_component, name: 'apollo') }
+      let_it_be(:occurrence_1) do
+        create(:sbom_occurrence,
+          component: component_1,
+          project: target_projects.first
+        )
+      end
+
+      let_it_be(:occurrence_2) do
+        create(:sbom_occurrence,
+          component: component_2,
+          project: target_projects.last
+        )
+      end
+
+      let(:params) { { component_names: [component_1.name] } }
+
+      context 'when feature flag is enabled' do
+        it 'returns only matching Sbom::Occurrences' do
+          expect(execute.to_a).to match_array([occurrence_1])
+        end
+      end
+
+      context 'when feature flag is disabled' do
+        before do
+          stub_feature_flags(group_level_dependencies_filtering_by_component: false)
+        end
+
+        it 'returns the original relation' do
+          expect(execute.to_a).to match_array(target_occurrences + [occurrence_1, occurrence_2])
+        end
+      end
+    end
+
     describe 'filtering by license' do
       using RSpec::Parameterized::TableSyntax
 
diff --git a/ee/spec/finders/sbom/components_finder_spec.rb b/ee/spec/finders/sbom/components_finder_spec.rb
index 5617362a35644e990ed95dba8d03c233e6988dff..8cbd0a41ef06375fdc93185f4b0c146b243ba11c 100644
--- a/ee/spec/finders/sbom/components_finder_spec.rb
+++ b/ee/spec/finders/sbom/components_finder_spec.rb
@@ -3,40 +3,47 @@
 require 'spec_helper'
 
 RSpec.describe Sbom::ComponentsFinder, feature_category: :vulnerability_management do
-  let(:finder) { described_class.new(query) }
+  let(:finder) { described_class.new(group, query) }
+  let_it_be(:user) { create(:user) }
+  let_it_be(:group) { create(:group, developers: user) }
+  let_it_be(:project) { create(:project, namespace: group) }
+
   let_it_be(:component_1) { create(:sbom_component, name: "activerecord") }
-  let_it_be(:component_2) { create(:sbom_component, name: "activejob") }
-  let_it_be(:component_3) { create(:sbom_component, name: "activestorage") }
-  let_it_be(:component_4) { create(:sbom_component, name: "activesupport") }
+  let_it_be(:component_2) { create(:sbom_component, name: "component-a") }
+  let_it_be(:component_3) { create(:sbom_component, name: "component-b") }
+  let_it_be(:component_4) { create(:sbom_component, name: "buuba") }
+
+  let_it_be(:version_1) { create(:sbom_component_version, component: component_1) }
+  let_it_be(:version_2) { create(:sbom_component_version, component: component_2) }
+  let_it_be(:version_3) { create(:sbom_component_version, component: component_3) }
+  let_it_be(:version_4) { create(:sbom_component_version, component: component_4) }
+
+  let_it_be(:occurrence_1) { create(:sbom_occurrence, component_version: version_1, project: project) }
+  let_it_be(:occurrence_2) { create(:sbom_occurrence, component_version: version_2, project: project) }
+  let_it_be(:occurrence_3) { create(:sbom_occurrence, component_version: version_3, project: project) }
+  let_it_be(:occurrence_4) { create(:sbom_occurrence, component_version: version_4, project: project) }
 
   describe '#execute' do
+    before do
+      stub_const("Sbom::Component::DEFAULT_COMPONENT_NAMES_LIMIT", 3)
+    end
+
     subject(:find) { finder.execute }
 
     context 'when given no query string' do
       let(:query) { nil }
 
-      it "returns all Sbom::Components" do
-        expect(find).to match_array([component_1, component_2, component_3, component_4])
-      end
-
-      context 'when there is more than maximum limit Sbom::Components' do
-        before do
-          stub_const("#{described_class}::DEFAULT_MAX_RESULTS", 3)
-          create_list(:sbom_component, described_class::DEFAULT_MAX_RESULTS)
-        end
-
-        it 'does not return more than Sbom::Component::DEFAULT_MAX_RESULTS results' do
-          expect(Sbom::Component.count).to be > described_class::DEFAULT_MAX_RESULTS
-          expect(find.length).to be <= described_class::DEFAULT_MAX_RESULTS
-        end
+      it "returns all names up to limit", :aggregate_failures do
+        expect(find.length).to eq(3)
+        expect(find).to eq([component_1, component_4, component_2])
       end
     end
 
     context 'when given a query string' do
-      let(:query) { "actives" }
+      let(:query) { "active" }
 
-      it "returns all matching Sbom::Components" do
-        expect(find).to match_array([component_3, component_4])
+      it "returns all matching names" do
+        expect(find).to match_array([component_1])
       end
     end
   end
diff --git a/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js b/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js
index 3ca29c0f5189aca0dca3e43d9f814dde13113b09..79afd8d5c804e9b4c0320dc30980c7e109b8d5a9 100644
--- a/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js
+++ b/ee/spec/frontend/dependencies/components/filtered_search/tokens/component_token_spec.js
@@ -8,7 +8,7 @@ import Vue, { nextTick } from 'vue';
 import VueApollo from 'vue-apollo';
 import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
 import ComponentToken from 'ee/dependencies/components/filtered_search/tokens/component_token.vue';
-import groupDependencies from 'ee/dependencies/graphql/components.query.graphql';
+import groupDependencies from 'ee/dependencies/graphql/group_components.query.graphql';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
 import { createAlert } from '~/alert';
@@ -48,7 +48,10 @@ describe('ee/dependencies/components/filtered_search/tokens/component_token.vue'
     const defaultHandlers = {
       getGroupComponentsHandler: jest.fn().mockResolvedValue({
         data: {
-          components: TEST_COMPONENTS,
+          group: {
+            id: 'some-group-id',
+            components: TEST_COMPONENTS,
+          },
         },
       }),
     };
diff --git a/ee/spec/graphql/ee/types/group_type_spec.rb b/ee/spec/graphql/ee/types/group_type_spec.rb
index 283559c399cb67bc18140daa1da57031cf0f0a1a..83b411f8fdbd69dbcfc4354fc76bd7805f36eee4 100644
--- a/ee/spec/graphql/ee/types/group_type_spec.rb
+++ b/ee/spec/graphql/ee/types/group_type_spec.rb
@@ -44,6 +44,83 @@
   it { expect(described_class).to have_graphql_field(:permanent_deletion_date) }
   it { expect(described_class).to have_graphql_field(:pending_member_approvals) }
   it { expect(described_class).to have_graphql_field(:dependencies) }
+  it { expect(described_class).to have_graphql_field(:components) }
+
+  describe 'components' do
+    let_it_be(:guest) { create(:user) }
+    let_it_be(:developer) { create(:user) }
+    let_it_be(:group) { create(:group, developers: developer, guests: guest) }
+    let_it_be(:project_1) { create(:project, namespace: group) }
+    let_it_be(:sbom_occurrence_1) { create(:sbom_occurrence, project: project_1) }
+    let(:component_1) { sbom_occurrence_1.component }
+    let_it_be(:project_2) { create(:project, namespace: group) }
+    let_it_be(:sbom_occurrence_2) { create(:sbom_occurrence, project: project_2) }
+    let(:component_2) { sbom_occurrence_2.component }
+    let(:query) do
+      %(
+        query {
+          group(fullPath: "#{group.full_path}") {
+            name
+            #{components_query}
+              id
+              name
+            }
+          }
+        }
+      )
+    end
+
+    let(:components_query) do
+      if component_name
+        "components(name: \"#{component_name}\") {"
+      else
+        "components {"
+      end
+    end
+
+    subject(:query_result) { GitlabSchema.execute(query, context: { current_user: user }).as_json }
+
+    before do
+      stub_licensed_features(security_dashboard: true, dependency_scanning: true)
+    end
+
+    context 'with developer access' do
+      let(:user) { developer }
+
+      context 'when no name is passed' do
+        let(:component_name) { nil }
+
+        it 'returns all components for all projects under given group' do
+          components = query_result.dig(*%w[data group components])
+          names = components.pluck('name')
+
+          expect(components.count).to be(2)
+          expect(names).to match_array([component_1.name, component_2.name])
+        end
+      end
+
+      context 'when name is passed' do
+        let(:component_name) { component_2.name }
+
+        it "returns all components that match the name" do
+          components = query_result.dig(*%w[data group components])
+
+          expect(components.count).to be(1)
+          expect(components.first['name']).to eq(component_2.name)
+        end
+      end
+    end
+
+    context 'without developer access' do
+      let(:user) { guest }
+      let(:component_name) { component_2.name }
+
+      it 'does not return any components' do
+        components = query_result.dig(*%w[data group components])
+        expect(components).to be_nil
+      end
+    end
+  end
 
   describe 'dependencies' do
     let_it_be(:user) { create(:user) }
diff --git a/ee/spec/graphql/resolvers/sbom/component_resolver_spec.rb b/ee/spec/graphql/resolvers/sbom/component_resolver_spec.rb
index 747f014ccd3ee5bbf2c9adda4a71900dcc6e5a79..a77a32ce9ae5e660a72adc44931892a3d255ed1f 100644
--- a/ee/spec/graphql/resolvers/sbom/component_resolver_spec.rb
+++ b/ee/spec/graphql/resolvers/sbom/component_resolver_spec.rb
@@ -5,32 +5,46 @@
 RSpec.describe Resolvers::Sbom::ComponentResolver, feature_category: :vulnerability_management do
   include GraphqlHelpers
 
-  let_it_be(:component_1) { create(:sbom_component, name: "activestorage") }
-  let_it_be(:component_2) { create(:sbom_component, name: "activesupport") }
-  let_it_be(:component_3) { create(:sbom_component, name: "log4j") }
+  let_it_be(:user) { create(:user) }
+  let_it_be(:group) { create(:group, developers: user) }
+
+  let_it_be(:project_1) { create(:project, namespace: group) }
+  let_it_be(:sbom_component_1) { create(:sbom_component, name: "activerecord") }
+  let_it_be(:sbom_occurrence_1) { create(:sbom_occurrence, component: sbom_component_1, project: project_1) }
+
+  let_it_be(:sbom_component_2) { create(:sbom_component, name: "activestorage") }
+  let_it_be(:sbom_occurrence_2) { create(:sbom_occurrence, component: sbom_component_2, project: project_1) }
+
+  let_it_be(:project_2) { create(:project, namespace: group) }
+  let_it_be(:sbom_component_3) { create(:sbom_component, name: "log4j") }
+  let_it_be(:sbom_occurrence_3) { create(:sbom_occurrence, component: sbom_component_3, project: project_2) }
 
   describe '#resolve' do
-    subject { resolve_components(args: { name: name }) }
+    subject { resolve_components(dependable, args: { name: name }) }
 
-    context 'when not given a query string' do
-      let(:name) { nil }
+    context 'when given a group' do
+      let(:dependable) { group }
 
-      it { is_expected.to match_array([component_1, component_2, component_3]) }
-    end
+      context 'when not given a query string' do
+        let(:name) { nil }
+
+        it { is_expected.to match_array([sbom_component_1, sbom_component_2, sbom_component_3]) }
+      end
 
-    context 'when given a query string' do
-      let(:name) { "actives" }
+      context 'when given a query string' do
+        let(:name) { "active" }
 
-      it { is_expected.to match_array([component_1, component_2]) }
+        it { is_expected.to match_array([sbom_component_1, sbom_component_2]) }
+      end
     end
   end
 
-  def resolve_components(args: {})
+  def resolve_components(obj, args: {})
     resolve(
       described_class,
-      obj: nil,
+      obj: obj,
       args: args,
-      ctx: {}
+      ctx: { current_user: user }
     )
   end
 end
diff --git a/ee/spec/graphql/types/query_type_spec.rb b/ee/spec/graphql/types/query_type_spec.rb
index 6e801908c77cf141f097fcf65dd4a43ed00e44c2..ae1282a4b8b30aaec00efb12628a4c6204a4765d 100644
--- a/ee/spec/graphql/types/query_type_spec.rb
+++ b/ee/spec/graphql/types/query_type_spec.rb
@@ -48,8 +48,7 @@
       :ai_self_hosted_models,
       :ai_self_hosted_model_feature_settings,
       :cloud_connector_status,
-      :project_secrets_manager,
-      :components
+      :project_secrets_manager
     ]
 
     all_expected_fields = expected_foss_fields + expected_ee_fields
diff --git a/ee/spec/models/sbom/component_spec.rb b/ee/spec/models/sbom/component_spec.rb
index 78536693fe33002fcd8c8f494c8e5935fdff9c29..adef3bd6da881f2690905ed2f0a23c2be291c6cf 100644
--- a/ee/spec/models/sbom/component_spec.rb
+++ b/ee/spec/models/sbom/component_spec.rb
@@ -77,6 +77,46 @@
     end
   end
 
+  describe '.by_namespace' do
+    let_it_be(:group) { create(:group) }
+    let_it_be(:project) { create(:project, namespace: group) }
+    let_it_be(:component_1) { create(:sbom_component, name: "activerecord") }
+    let_it_be(:occurrence_1) { create(:sbom_occurrence, component: component_1, project: project) }
+    let_it_be(:component_2) { create(:sbom_component, name: "activesupport") }
+    let_it_be(:occurrence) { create(:sbom_occurrence, component: component_2, project: project) }
+
+    subject(:results) { described_class.by_namespace(thing, query) }
+
+    context 'when passed a Namespace' do
+      let(:thing) { group }
+
+      context 'when given a query string' do
+        let(:query) { component_1.name }
+
+        it 'returns matching components' do
+          expect(results).to match_array([component_1])
+        end
+      end
+
+      context 'when no query string is given' do
+        let(:query) { nil }
+
+        it 'returns all components' do
+          expect(results).to match_array([component_1, component_2])
+        end
+      end
+    end
+
+    context 'when given anything else' do
+      let(:thing) { project }
+      let(:query) { "active" }
+
+      it 'returns no results' do
+        expect(results).to be_empty
+      end
+    end
+  end
+
   context 'with loose foreign key on sbom_components.organization_id' do
     it_behaves_like 'cleanup by a loose foreign key' do
       let_it_be(:parent) { create(:organization) }
diff --git a/ee/spec/requests/groups/dependencies_controller_spec.rb b/ee/spec/requests/groups/dependencies_controller_spec.rb
index e5fdb4f454ddab7aadf790b01b618f0ae4d7c6ca..c81de0bfd4f919036e221d6bbb3e8576e772c973 100644
--- a/ee/spec/requests/groups/dependencies_controller_spec.rb
+++ b/ee/spec/requests/groups/dependencies_controller_spec.rb
@@ -121,8 +121,8 @@
 
           context 'with existing dependencies' do
             let_it_be(:project) { create(:project, group: group) }
-            let_it_be(:component_1) { create(:sbom_component, name: 'a') }
-            let_it_be(:component_2) { create(:sbom_component, name: 'b') }
+            let_it_be(:component_1) { create(:sbom_component, name: 'activerecord') }
+            let_it_be(:component_2) { create(:sbom_component, name: 'apollo') }
             let_it_be(:sbom_occurrence_npm) do
               create(
                 :sbom_occurrence,
@@ -218,6 +218,22 @@
               end
             end
 
+            context 'when filtering with component_names' do
+              let(:params) do
+                {
+                  component_names: [component_1.name]
+                }
+              end
+
+              it 'returns matching Sbom::Occurrence records' do
+                subject
+
+                dependency_name = json_response.dig("dependencies", 0, "name")
+
+                expect(dependency_name).to eq(sbom_occurrence_npm.component_name)
+              end
+            end
+
             context 'when paginating over licenses' do
               let(:params) do
                 {