diff --git a/.rubocop_todo/rspec/be_eq.yml b/.rubocop_todo/rspec/be_eq.yml
index b51f7e7c78e46dad1118b6fdeea9c8f996e735e2..65b84951a8649b86a6468d3e54c6d0bed44da019 100644
--- a/.rubocop_todo/rspec/be_eq.yml
+++ b/.rubocop_todo/rspec/be_eq.yml
@@ -468,7 +468,6 @@ RSpec/BeEq:
     - 'ee/spec/services/projects/restore_service_spec.rb'
     - 'ee/spec/services/projects/update_service_spec.rb'
     - 'ee/spec/services/quick_actions/interpret_service_spec.rb'
-    - 'ee/spec/services/search/elastic/cluster_reindexing_service_spec.rb'
     - 'ee/spec/services/search/project_service_spec.rb'
     - 'ee/spec/services/search/zoekt/indexing_task_service_spec.rb'
     - 'ee/spec/services/security/configuration/project_set_continuous_vulnerability_scanning_service_spec.rb'
diff --git a/.rubocop_todo/rspec/receive_messages.yml b/.rubocop_todo/rspec/receive_messages.yml
index 581c114c0d1d6cd9af745a3904526202aaec2492..97b93e6b1bc1cc22a084d9b1b8cc46a023c1c24a 100644
--- a/.rubocop_todo/rspec/receive_messages.yml
+++ b/.rubocop_todo/rspec/receive_messages.yml
@@ -154,7 +154,6 @@ RSpec/ReceiveMessages:
     - 'ee/spec/services/merge_requests/mergeability/check_jira_status_service_spec.rb'
     - 'ee/spec/services/merge_requests/mergeability/check_path_locks_service_spec.rb'
     - 'ee/spec/services/resource_access_tokens/create_service_spec.rb'
-    - 'ee/spec/services/search/elastic/cluster_reindexing_service_spec.rb'
     - 'ee/spec/services/search/project_service_spec.rb'
     - 'ee/spec/services/security/orchestration/assign_service_spec.rb'
     - 'ee/spec/services/security/scan_result_policies/generate_policy_violation_comment_service_spec.rb'
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
index 10c22609858cbdd97807c68b73282f8cda28c398..c7ee7699d9ca5848818cdc0b7caf64421da79694 100644
--- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js
+++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
@@ -388,9 +388,12 @@ export const PROJECT_FILES_GO_BACK = {
   defaultKeys: ['esc'],
 };
 
+const { blobOverflowMenu } = gon.features ?? {};
 export const PROJECT_FILES_GO_TO_PERMALINK = {
   id: 'projectFiles.goToFilePermalink',
-  description: __('Go to file permalink (while viewing a file)'),
+  description: blobOverflowMenu
+    ? __('Copy file permalink')
+    : __('Go to file permalink (while viewing a file)'),
   defaultKeys: ['y'],
 };
 
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
index 9f8d7272e5c52e9afca92f48934fc8844a9ab850..13ecfaf1a5493dff78cdeeceef43a29c28ae1388 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
@@ -3,6 +3,12 @@ import { moveToFilePermalink } from '~/blob/utils';
 
 export default class ShortcutsBlob {
   constructor(shortcuts) {
+    const { blobOverflowMenu } = gon.features ?? {};
+    if (blobOverflowMenu) {
+      // TODO: Remove ShortcutsBlob entirely once these feature flags are removed.
+      return;
+    }
+
     shortcuts.add(PROJECT_FILES_GO_TO_PERMALINK, moveToFilePermalink);
   }
 }
diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js
index afc08045afaae2df5618a57ea2c250fee7d474f9..c7a7e33889881bd39dca4b793c22749922bba1f1 100644
--- a/app/assets/javascripts/search/store/actions.js
+++ b/app/assets/javascripts/search/store/actions.js
@@ -1,4 +1,5 @@
 import { omitBy } from 'lodash';
+import { nextTick } from 'vue';
 import Api from '~/api';
 import { createAlert } from '~/alert';
 import axios from '~/lib/utils/axios_utils';
@@ -23,6 +24,7 @@ import {
   prepareSearchAggregations,
   setDataToLS,
   skipBlobESCount,
+  buildDocumentTitle,
 } from './utils';
 
 export const fetchGroups = ({ commit }, search) => {
@@ -103,7 +105,45 @@ export const setFrequentProject = ({ state, commit }, item) => {
   commit(types.LOAD_FREQUENT_ITEMS, { key: PROJECTS_LOCAL_STORAGE_KEY, data: frequentItems });
 };
 
-export const setQuery = ({ state, commit, getters }, { key, value }) => {
+export const fetchSidebarCount = ({ commit, state }) => {
+  const items = Object.values(state.navigation)
+    .filter(
+      (navigationItem) =>
+        !navigationItem.active &&
+        navigationItem.count_link &&
+        skipBlobESCount(state, navigationItem.scope),
+    )
+    .map((navItem) => {
+      const navigationItem = { ...navItem };
+
+      const modifications = {
+        search: state.query?.search || '*',
+      };
+
+      if (navigationItem.scope === SCOPE_BLOB && loadDataFromLS(LS_REGEX_HANDLE)) {
+        modifications[REGEX_PARAM] = true;
+      }
+
+      navigationItem.count_link = setUrlParams(
+        modifications,
+        getNormalizedURL(navigationItem.count_link),
+      );
+      return navigationItem;
+    });
+
+  const promises = items.map((navigationItem) =>
+    axios
+      .get(navigationItem.count_link)
+      .then(({ data: { count } }) => {
+        commit(types.RECEIVE_NAVIGATION_COUNT, { key: navigationItem.scope, count });
+      })
+      .catch((e) => logError(e)),
+  );
+
+  return Promise.all(promises);
+};
+
+export const setQuery = async ({ state, commit, getters }, { key, value }) => {
   commit(types.SET_QUERY, { key, value });
 
   if (SIDEBAR_PARAMS.includes(key)) {
@@ -117,10 +157,14 @@ export const setQuery = ({ state, commit, getters }, { key, value }) => {
   if (
     state.searchType === SEARCH_TYPE_ZOEKT &&
     getters.currentScope === SCOPE_BLOB &&
-    gon.features.zoektMultimatchFrontend
+    gon.features?.zoektMultimatchFrontend
   ) {
     const newUrl = setUrlParams({ ...state.query }, window.location.href, false, true);
-    updateHistory({ state: state.query, url: newUrl, replace: true });
+    document.title = buildDocumentTitle(state.query.search);
+    updateHistory({ state: state.query, title: state.query.search, url: newUrl, replace: false });
+
+    await nextTick();
+    fetchSidebarCount({ state, commit });
   }
 };
 
@@ -148,53 +192,16 @@ export const resetQuery = ({ state }) => {
   );
 };
 
-export const closeLabel = ({ state, commit }, { title }) => {
-  const labels = state?.query?.[LABEL_FILTER_PARAM].filter((labelName) => labelName !== title);
-  setQuery({ state, commit }, { key: LABEL_FILTER_PARAM, value: labels });
+export const closeLabel = ({ state, commit, getters }, { title }) => {
+  const labels =
+    state?.query?.[LABEL_FILTER_PARAM]?.filter((labelName) => labelName !== title) || [];
+  setQuery({ state, commit, getters }, { key: LABEL_FILTER_PARAM, value: labels });
 };
 
 export const setLabelFilterSearch = ({ commit }, { value }) => {
   commit(types.SET_LABEL_SEARCH_STRING, value);
 };
 
-export const fetchSidebarCount = ({ commit, state }) => {
-  const items = Object.values(state.navigation)
-    .filter(
-      (navigationItem) =>
-        !navigationItem.active &&
-        navigationItem.count_link &&
-        skipBlobESCount(state, navigationItem.scope),
-    )
-    .map((navItem) => {
-      const navigationItem = { ...navItem };
-      const modifications = {
-        search: state.query?.search || '*',
-      };
-
-      if (navigationItem.scope === SCOPE_BLOB && loadDataFromLS(LS_REGEX_HANDLE)) {
-        modifications[REGEX_PARAM] = true;
-      }
-
-      navigationItem.count_link = setUrlParams(
-        modifications,
-        getNormalizedURL(navigationItem.count_link),
-      );
-
-      return navigationItem;
-    });
-
-  const promises = items.map((navigationItem) =>
-    axios
-      .get(navigationItem.count_link)
-      .then(({ data: { count } }) => {
-        commit(types.RECEIVE_NAVIGATION_COUNT, { key: navigationItem.scope, count });
-      })
-      .catch((e) => logError(e)),
-  );
-
-  return Promise.all(promises);
-};
-
 export const fetchAllAggregation = ({ commit, state }) => {
   commit(types.REQUEST_AGGREGATIONS);
   return axios
diff --git a/app/assets/javascripts/search/store/constants.js b/app/assets/javascripts/search/store/constants.js
index dbb2ce7f11205d8f3a4d5e1107fa087e89da12f3..fa9270ec5ff3a4fa642d59507adf9a1b4643b9fa 100644
--- a/app/assets/javascripts/search/store/constants.js
+++ b/app/assets/javascripts/search/store/constants.js
@@ -84,3 +84,4 @@ export const SEARCH_LEVEL_PROJECT = 'project';
 export const SEARCH_LEVEL_GROUP = 'group';
 
 export const LS_REGEX_HANDLE = `${REGEX_PARAM}_advanced_search`;
+export const SEARCH_WINDOW_TITLE = `${s__('GlobalSearch|Search')} · GitLab`;
diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js
index 7627b2e0e08a7794db0caa653b9addb7b48b29e5..b67f7d2843b7727ec69b3f9633c2603fdf2e08e7 100644
--- a/app/assets/javascripts/search/store/mutations.js
+++ b/app/assets/javascripts/search/store/mutations.js
@@ -33,7 +33,7 @@ export default {
     state.frequentItems[key] = data;
   },
   [types.RECEIVE_NAVIGATION_COUNT](state, { key, count }) {
-    const item = { ...state.navigation[key], count, count_link: null };
+    const item = { ...state.navigation[key], count };
     state.navigation = { ...state.navigation, [key]: item };
   },
   [types.REQUEST_AGGREGATIONS](state) {
diff --git a/app/assets/javascripts/search/store/utils.js b/app/assets/javascripts/search/store/utils.js
index 4ef2d91c057df1c0c19363e8d9f68b4365d364dc..aa203c0ba0485113ea9431261f00255920d36d60 100644
--- a/app/assets/javascripts/search/store/utils.js
+++ b/app/assets/javascripts/search/store/utils.js
@@ -14,6 +14,7 @@ import {
   NUMBER_FORMATING_OPTIONS,
   REGEX_PARAM,
   LS_REGEX_HANDLE,
+  SEARCH_WINDOW_TITLE,
 } from './constants';
 
 function extractKeys(object, keyList) {
@@ -114,7 +115,6 @@ export const mergeById = (inflatedData, storedData) => {
 
 export const isSidebarDirty = (currentQuery, urlQuery) => {
   return SIDEBAR_PARAMS.some((param) => {
-    // userAddParam ensures we don't get a false dirty from null !== undefined
     const userAddedParam = !urlQuery[param] && currentQuery[param];
     const userChangedExistingParam = urlQuery[param] && urlQuery[param] !== currentQuery[param];
 
@@ -219,3 +219,22 @@ export const skipBlobESCount = (state, itemScope) =>
     state.zoektAvailable &&
     itemScope === SCOPE_BLOB
   );
+
+export const buildDocumentTitle = (title) => {
+  const prevTitle = document.title;
+
+  if (prevTitle.includes(SEARCH_WINDOW_TITLE)) {
+    if (prevTitle.startsWith(SEARCH_WINDOW_TITLE)) {
+      return `${title} · ${SEARCH_WINDOW_TITLE}`;
+    }
+
+    if (prevTitle.trim().startsWith(` · ${SEARCH_WINDOW_TITLE}`.trim())) {
+      return `${title} · ${SEARCH_WINDOW_TITLE}`;
+    }
+
+    const pattern = new RegExp(`^.*?(?= · ${SEARCH_WINDOW_TITLE})`);
+    return prevTitle.replace(pattern, title);
+  }
+  // If pattern not found, return the original
+  return title;
+};
diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue
index 8e04bda01ae784b5af5c2da3b61d9c83a897c5cc..1811a608d7c2c166c2c1e1bc537573f4a40e6c03 100644
--- a/app/assets/javascripts/search/topbar/components/app.vue
+++ b/app/assets/javascripts/search/topbar/components/app.vue
@@ -1,8 +1,9 @@
 <script>
 import { GlButton } from '@gitlab/ui';
-import { isEmpty } from 'lodash';
+import { isEmpty, debounce } from 'lodash';
 // eslint-disable-next-line no-restricted-imports
 import { mapState, mapActions, mapGetters } from 'vuex';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
 import { InternalEvents } from '~/tracking';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import { s__ } from '~/locale';
@@ -51,6 +52,10 @@ export default {
         return this.query ? this.query.search : '';
       },
       set(value) {
+        if (this.isMultiMatch) {
+          this.debouncedSetQuery({ key: 'search', value });
+          return;
+        }
         this.setQuery({ key: 'search', value });
       },
     },
@@ -86,6 +91,7 @@ export default {
   created() {
     this.preloadStoredFrequentItems();
     this.regexEnabled = loadDataFromLS(LS_REGEX_HANDLE);
+    this.debouncedSetQuery = debounce(this.setQuery, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
   },
   methods: {
     ...mapActions(['applyQuery', 'setQuery', 'preloadStoredFrequentItems']),
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index d0ed3515bfd6684fabb6714cb38eae2d3b7e8839..d2b4b18f9207f6dd7e62f5919c68066f0dbbf999 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -55,6 +55,8 @@ def execute
     users = by_custom_attributes(users)
     users = by_non_internal(users)
     users = by_without_project_bots(users)
+    users = by_membership(users)
+    users = by_member_source_ids(users)
 
     order(users)
   end
@@ -180,6 +182,48 @@ def by_without_project_bots(users)
     users.without_project_bot
   end
 
+  def by_membership(users)
+    return users unless params[:by_membership]
+
+    group_members = Member
+      .non_request
+      .with_source(current_user.authorized_groups.self_and_ancestors)
+      .select(:user_id)
+      .to_sql
+
+    project_members = Member
+      .non_request
+      .with_source(current_user.authorized_projects)
+      .select(:user_id)
+      .to_sql
+
+    query = "users.id IN (#{group_members} UNION #{project_members})"
+    users.where(query) # rubocop: disable CodeReuse/ActiveRecord -- finder
+  end
+
+  def by_member_source_ids(users)
+    group_member_source_ids = params[:group_member_source_ids]
+    project_member_source_ids = params[:project_member_source_ids]
+
+    return users unless group_member_source_ids || project_member_source_ids
+
+    member_queries = []
+
+    if group_member_source_ids.present?
+      member_queries << Member.with_source_id(group_member_source_ids).with_source_type('Namespace')
+    end
+
+    if project_member_source_ids.present?
+      member_queries << Member.with_source_id(project_member_source_ids).with_source_type('Project')
+    end
+
+    return users if member_queries.empty?
+
+    member_query = member_queries.reduce(:or).non_request
+
+    users.id_in(member_query.select(:user_id))
+  end
+
   def order(users)
     return users unless params[:sort]
 
diff --git a/app/models/member.rb b/app/models/member.rb
index deffb3768a7ada51b05a9cdcfc6aa0bf10bc4406..614315657e03a3497e97bec91c4226bd6b3c0b14 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -209,6 +209,8 @@ class Member < ApplicationRecord
   end
 
   scope :with_source_id, ->(source_id) { where(source_id: source_id) }
+  scope :with_source, ->(source) { where(source: source) }
+  scope :with_source_type, ->(source_type) { where(source_type: source_type) }
   scope :including_source, -> { includes(:source) }
   scope :including_user, -> { includes(:user) }
 
diff --git a/config/feature_flags/gitlab_com_derisk/users_search_scoped_to_authorized_namespaces_basic_search.yml b/config/feature_flags/gitlab_com_derisk/users_search_scoped_to_authorized_namespaces_basic_search.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b7c9a64f6767531aa260ff8ef3d7282c093ad17c
--- /dev/null
+++ b/config/feature_flags/gitlab_com_derisk/users_search_scoped_to_authorized_namespaces_basic_search.yml
@@ -0,0 +1,9 @@
+---
+name: users_search_scoped_to_authorized_namespaces_basic_search
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442091
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182557
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/520710
+milestone: '17.10'
+group: group::global search
+type: gitlab_com_derisk
+default_enabled: false
diff --git a/config/feature_flags/gitlab_com_derisk/users_search_scoped_to_authorized_namespaces_basic_search_by_ids.yml b/config/feature_flags/gitlab_com_derisk/users_search_scoped_to_authorized_namespaces_basic_search_by_ids.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fd4fbcbaa7c4006fc32393b13c4277ebab0d0ada
--- /dev/null
+++ b/config/feature_flags/gitlab_com_derisk/users_search_scoped_to_authorized_namespaces_basic_search_by_ids.yml
@@ -0,0 +1,9 @@
+---
+name: users_search_scoped_to_authorized_namespaces_basic_search_by_ids
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442091
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182557
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/524297
+milestone: '17.10'
+group: group::global search
+type: gitlab_com_derisk
+default_enabled: false
diff --git a/ee/app/services/vulnerabilities/auto_resolve_service.rb b/ee/app/services/vulnerabilities/auto_resolve_service.rb
index 6fece9ca2fe2288a444c330a024d47e0d96fe2b2..e49ee1905dcd9cec47d2d40b3d82587f445addb8 100644
--- a/ee/app/services/vulnerabilities/auto_resolve_service.rb
+++ b/ee/app/services/vulnerabilities/auto_resolve_service.rb
@@ -19,7 +19,9 @@ def execute
       refresh_statistics
 
       ServiceResponse.success(payload: { count: vulnerabilities_to_resolve.size })
-    rescue ActiveRecord::ActiveRecordError
+    rescue ActiveRecord::ActiveRecordError => e
+      Gitlab::ErrorTracking.track_exception(e)
+
       error_response
     end
 
diff --git a/ee/spec/services/search/elastic/cluster_reindexing_service_spec.rb b/ee/spec/services/search/elastic/cluster_reindexing_service_spec.rb
index c69f16aa34428816e6b95245b4af351409fa290c..0c6eba35783c3042afb40edf5995b7d7fcf1b016 100644
--- a/ee/spec/services/search/elastic/cluster_reindexing_service_spec.rb
+++ b/ee/spec/services/search/elastic/cluster_reindexing_service_spec.rb
@@ -51,20 +51,19 @@
     end
 
     it 'errors when there is not enough space' do
-      allow(helper).to receive(:index_size_bytes).and_return(100.megabytes)
-      allow(helper).to receive(:cluster_free_size_bytes).and_return(30.megabytes)
+      allow(helper).to receive_messages(index_size_bytes: 100.megabytes, cluster_free_size_bytes: 30.megabytes)
 
       expect { cluster_reindexing_service.execute }.to change { task.reload.state }.from('initial').to('failure')
       expect(task.reload.error_message).to match(/storage available/)
     end
 
     it 'pauses elasticsearch indexing' do
-      expect(Gitlab::CurrentSettings.elasticsearch_pause_indexing).to eq(false)
+      expect(Gitlab::CurrentSettings.elasticsearch_pause_indexing).to be(false)
 
       expect { cluster_reindexing_service.execute }
         .to change { task.reload.state }.from('initial').to('indexing_paused')
 
-      expect(Gitlab::CurrentSettings.elasticsearch_pause_indexing).to eq(true)
+      expect(Gitlab::CurrentSettings.elasticsearch_pause_indexing).to be(true)
     end
 
     context 'when partial reindexing' do
@@ -93,11 +92,12 @@
       let!(:task) { create(:elastic_reindexing_task, state: :indexing_paused, targets: nil) }
 
       before do
-        allow(helper).to receive(:create_standalone_indices).and_return(issues_new_index_name => issues_alias)
         allow(helper).to receive(:target_index_names) { |options| { "#{options[:target]}-1" => true } }
-        allow(helper).to receive(:create_empty_index).and_return(main_new_index_name => main_alias)
+        allow(helper).to receive_messages(
+          create_standalone_indices: { issues_new_index_name => issues_alias },
+          create_empty_index: { main_new_index_name => main_alias }
+        )
         allow(helper).to receive(:reindex) { |options| "#{options[:to]}_task_id" }
-        allow(helper).to receive(:documents_count)
         allow(helper).to receive(:get_settings) do |options|
           number_of_shards = case options[:index_name]
                              when main_old_index_name then 10
@@ -212,11 +212,12 @@
     end
 
     before do
-      allow(helper).to receive(:task_status).and_return(
-        {
+      allow(helper).to receive_messages(
+        task_status: {
           'completed' => true,
           'response' => { 'total' => 20, 'created' => 20, 'updated' => 0, 'deleted' => 0 }
-        }
+        },
+        refresh_index: true
       )
       allow(helper).to receive(:reindex).and_return('task_1', 'task_2', 'task_3', 'task_4', 'task_5', 'task_6')
     end
diff --git a/ee/spec/services/vulnerabilities/auto_resolve_service_spec.rb b/ee/spec/services/vulnerabilities/auto_resolve_service_spec.rb
index 244c7f889002673795347c9fd2dd426afcc17b8e..87d87489b89029b2245f9fe1e8fca4014767da9b 100644
--- a/ee/spec/services/vulnerabilities/auto_resolve_service_spec.rb
+++ b/ee/spec/services/vulnerabilities/auto_resolve_service_spec.rb
@@ -143,7 +143,8 @@
         allow(Note).to receive(:insert_all!).and_raise(ActiveRecord::RecordNotUnique)
       end
 
-      it 'does not bubble up the error' do
+      it 'does not bubble up the error and tracks the exception' do
+        expect(Gitlab::ErrorTracking).to receive(:track_exception).with(ActiveRecord::RecordNotUnique)
         expect { service.execute }.not_to raise_error
       end
 
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index b94184cfe8c6d0d39ed226ce727792d8147b7eb6..c795c12b157eb529fc81ff84405f9faf1eaee6f1 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -109,7 +109,18 @@ def count_limit
     def users
       return User.none unless Ability.allowed?(current_user, :read_users_list)
 
-      UsersFinder.new(current_user, { search: query, use_minimum_char_limit: false }).execute
+      params = { search: query, use_minimum_char_limit: false }
+
+      if current_user && filters[:autocomplete]
+        if Feature.enabled?(:users_search_scoped_to_authorized_namespaces_basic_search, current_user)
+          params[:by_membership] = true
+        elsif Feature.enabled?(:users_search_scoped_to_authorized_namespaces_basic_search_by_ids, current_user)
+          params[:group_member_source_ids] = current_user_authorized_group_ids
+          params[:project_member_source_ids] = current_user_authorized_project_ids
+        end
+      end
+
+      UsersFinder.new(current_user, params).execute
     end
 
     # highlighting is only performed by Elasticsearch backed results
@@ -266,6 +277,20 @@ def issuable_params
       end
     end
 
+    def current_user_authorized_group_ids
+      GroupsFinder
+        .new(current_user, { all_available: false })
+        .execute
+        .pluck("#{Group.table_name}.#{Group.primary_key}") # rubocop: disable CodeReuse/ActiveRecord -- need to find ids
+    end
+
+    def current_user_authorized_project_ids
+      ProjectsFinder
+        .new(current_user: current_user, params: { non_public: true })
+        .execute
+        .pluck_primary_key
+    end
+
     # rubocop: disable CodeReuse/ActiveRecord
     def limited_count(relation)
       relation.reorder(nil).limit(count_limit).size
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 39ad4f5c7662285dbdf9bcf6b3efe750bcce15a3..73ff03c6da411fc27fb3026882e66fa3ed02618e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16881,6 +16881,9 @@ msgstr ""
 msgid "Copy file path"
 msgstr ""
 
+msgid "Copy file permalink"
+msgstr ""
+
 msgid "Copy image URL"
 msgstr ""
 
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 48161e675dd84771ea098976e9393de6416f99c9..ae861817234fe31cb5a53596b13ae7ebd4f69e8f 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -7,7 +7,13 @@
   include ListboxHelpers
 
   let_it_be(:user) { create(:user) }
-  let_it_be_with_reload(:project) { create(:project, :repository, namespace: user.namespace) }
+  let_it_be_with_reload(:project) do
+    # This helps with some of the test flakiness.
+    project = create(:project, :repository, namespace: user.namespace)
+    project.repository.root_ref
+    project.repository.ls_files('master')
+    project
+  end
 
   context 'when signed in' do
     before do
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index a766923a69ac3442b2093ddcee853ed2a7ac0d4b..770d7d6aeaed7088fa2e249987bd8346cce1e2ab 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -149,6 +149,77 @@
         users = described_class.new(user, admins: true).execute
         expect(users).to contain_exactly(user, normal_user, external_user, admin_user, unconfirmed_user, omniauth_user, internal_user, project_bot, service_account_user)
       end
+
+      context 'when filtering by_membership' do
+        let_it_be(:group_user) { create(:user) }
+        let_it_be(:project_user) { create(:user) }
+        let_it_be(:group) { create(:group, developers: [user]) }
+        let_it_be(:project) { create(:project, developers: [user]) }
+
+        subject(:users) { described_class.new(user, by_membership: true).execute }
+
+        it 'includes the user and project owner' do
+          expect(users).to contain_exactly(user, project.owner)
+        end
+
+        it 'includes users who are members of the user groups' do
+          group.add_developer(group_user)
+
+          expect(users).to contain_exactly(user, project.owner, group_user)
+        end
+
+        it 'includes users who are members of the user projects' do
+          project.add_developer(project_user)
+
+          expect(users).to contain_exactly(user, project.owner, project_user)
+        end
+      end
+
+      context 'when filtering by_member_source_ids' do
+        let_it_be(:group_user) { create(:user) }
+        let_it_be(:project_user) { create(:user) }
+        let_it_be(:group) { create(:group) }
+        let_it_be(:project) { create(:project) }
+
+        it 'filters by group membership' do
+          group.add_developer(group_user)
+
+          users = described_class.new(user, group_member_source_ids: [group.id]).execute
+
+          expect(users).to contain_exactly(group_user)
+        end
+
+        it 'filters by project membership' do
+          project.add_developer(project_user)
+
+          users = described_class.new(user, project_member_source_ids: [project.id]).execute
+
+          expect(users).to contain_exactly(project_user, project.owner)
+        end
+
+        it 'filters by group and project membership' do
+          group.add_developer(group_user)
+          project.add_developer(project_user)
+
+          users = described_class
+                    .new(user, group_member_source_ids: [group.id], project_member_source_ids: [project.id])
+                    .execute
+
+          expect(users).to contain_exactly(group_user, project_user, project.owner)
+        end
+
+        it 'does not include members not part of the filtered group' do
+          users = described_class.new(user, group_member_source_ids: [group.id]).execute
+
+          expect(users).not_to include(group_user)
+        end
+
+        it 'does not include members not part of the filtered project' do
+          users = described_class.new(user, project_member_source_ids: [project.id]).execute
+
+          expect(users).not_to include(project_user)
+        end
+      end
     end
 
     shared_examples 'executes users finder as admin' do
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_blob_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_blob_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3bb992636ee0753061ba2e9bd01de98c90676d2a
--- /dev/null
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_blob_spec.js
@@ -0,0 +1,56 @@
+import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
+import { PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
+import { moveToFilePermalink } from '~/blob/utils';
+
+describe('ShortcutsBlob', () => {
+  const shortcuts = {
+    add: jest.fn(),
+  };
+
+  const init = () => {
+    return new ShortcutsBlob(shortcuts);
+  };
+
+  beforeEach(() => {
+    shortcuts.add.mockClear();
+    window.gon = {};
+  });
+
+  describe('constructor', () => {
+    describe('when shortcuts should be added', () => {
+      it('adds the permalink shortcut when gon.features is undefined', () => {
+        init();
+
+        expect(shortcuts.add).toHaveBeenCalledWith(
+          PROJECT_FILES_GO_TO_PERMALINK,
+          moveToFilePermalink,
+        );
+      });
+
+      it('adds shortcuts when blobOverflowMenu is false', () => {
+        window.gon.features = {
+          blobOverflowMenu: false,
+        };
+
+        init();
+
+        expect(shortcuts.add).toHaveBeenCalledWith(
+          PROJECT_FILES_GO_TO_PERMALINK,
+          moveToFilePermalink,
+        );
+      });
+    });
+
+    describe('when shortcuts should not be added', () => {
+      it('does not add shortcuts when blobOverflowMenu is true', () => {
+        window.gon.features = {
+          blobOverflowMenu: true,
+        };
+
+        init();
+
+        expect(shortcuts.add).not.toHaveBeenCalled();
+      });
+    });
+  });
+});
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
index 4ec1e01e55326eebc133ca7319131a8890ee537f..558959b38e24c61a0078d4445a33214ee578af4d 100644
--- a/spec/frontend/search/mock_data.js
+++ b/spec/frontend/search/mock_data.js
@@ -220,7 +220,7 @@ export const MOCK_DATA_FOR_NAVIGATION_ACTION_MUTATION = {
     label: 'Projects',
     scope: 'projects',
     link: '/search?scope=projects&search=et',
-    count_link: null,
+    count_link: '/search/count?scope=projects&search=et',
   },
 };
 
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index c92600e50aafdc679f1513539765841dddf87561..ba98fc85298b47d8f2caa7bffded7fda08d3c6b3 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -1,6 +1,9 @@
 import MockAdapter from 'axios-mock-adapter';
 import { mapValues } from 'lodash';
+// rspec spec/frontend/fixtures/search_navigation.rb to generate this file
+import noActiveItems from 'test_fixtures/search_navigation/no_active_items.json';
 import testAction from 'helpers/vuex_action_helper';
+import { setUrlParams, updateHistory } from '~/lib/utils/url_utility';
 import Api from '~/api';
 import { createAlert } from '~/alert';
 import * as logger from '~/lib/logger';
@@ -45,6 +48,17 @@ jest.mock('~/lib/logger', () => ({
   logError: jest.fn(),
 }));
 
+jest.mock('~/lib/utils/url_utility', () => {
+  const urlUtility = jest.requireActual('~/lib/utils/url_utility');
+
+  return {
+    __esModule: true,
+    ...urlUtility,
+    setUrlParams: jest.fn(() => 'mocked-new-url'),
+    updateHistory: jest.fn(),
+  };
+});
+
 describe('Global Search Store Actions', () => {
   let mock;
   let state;
@@ -159,41 +173,112 @@ describe('Global Search Store Actions', () => {
     });
   });
 
-  describe.each`
-    payload                                      | isDirty  | isDirtyMutation
-    ${{ key: SIDEBAR_PARAMS[0], value: 'test' }} | ${false} | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: false }]}
-    ${{ key: SIDEBAR_PARAMS[0], value: 'test' }} | ${true}  | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: true }]}
-    ${{ key: SIDEBAR_PARAMS[1], value: 'test' }} | ${false} | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: false }]}
-    ${{ key: SIDEBAR_PARAMS[1], value: 'test' }} | ${true}  | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: true }]}
-    ${{ key: 'non-sidebar', value: 'test' }}     | ${false} | ${[]}
-    ${{ key: 'non-sidebar', value: 'test' }}     | ${true}  | ${[]}
-  `('setQuery', ({ payload, isDirty, isDirtyMutation }) => {
-    describe(`when filter param is ${payload.key} and utils.isSidebarDirty returns ${isDirty}`, () => {
-      const expectedMutations = [{ type: types.SET_QUERY, payload }].concat(isDirtyMutation);
+  describe('setQuery', () => {
+    describe('when search type is zoekt and scope is blob with zoektMultimatchFrontend feature enabled', () => {
+      const payload = { key: 'some-key', value: 'some-value' };
+      let originalGon;
+      let commit;
+      let fetchSidebarCountSpy;
 
       beforeEach(() => {
-        storeUtils.isSidebarDirty = jest.fn().mockReturnValue(isDirty);
+        originalGon = window.gon;
+        commit = jest.fn();
+
+        fetchSidebarCountSpy = jest
+          .spyOn(actions, 'fetchSidebarCount')
+          .mockImplementation(() => Promise.resolve());
+
+        window.gon = { features: { zoektMultimatchFrontend: true } };
+        storeUtils.isSidebarDirty = jest.fn().mockReturnValue(false);
+
+        state = createState({
+          query: { ...MOCK_QUERY, search: 'test-search' },
+          navigation: { ...MOCK_NAVIGATION },
+          searchType: 'zoekt',
+        });
       });
 
-      it(`should dispatch the correct mutations`, () => {
-        return testAction({ action: actions.setQuery, payload, state, expectedMutations });
+      afterEach(() => {
+        window.gon = originalGon;
+        fetchSidebarCountSpy.mockRestore();
       });
-    });
-  });
 
-  describe.each`
-    payload
-    ${{ key: REGEX_PARAM, value: true }}
-    ${{ key: REGEX_PARAM, value: { random: 'test' } }}
-  `('setQuery', ({ payload }) => {
-    describe(`when query param is ${payload.key}`, () => {
-      beforeEach(() => {
-        storeUtils.setDataToLS = jest.fn();
-        actions.setQuery({ state, commit: jest.fn() }, payload);
+      it('should update URL, document title, and history', async () => {
+        const getters = { currentScope: 'blobs' };
+
+        await actions.setQuery({ state, commit, getters }, payload);
+
+        expect(setUrlParams).toHaveBeenCalledWith(
+          { ...state.query },
+          window.location.href,
+          false,
+          true,
+        );
+
+        expect(document.title).toBe(state.query.search);
+
+        expect(updateHistory).toHaveBeenCalledWith({
+          state: state.query,
+          title: state.query.search,
+          url: 'mocked-new-url',
+          replace: false,
+        });
+      });
+
+      it('does not update URL or fetch sidebar counts when conditions are not met', async () => {
+        let getters = { currentScope: 'blobs' };
+        state.searchType = 'not-zoekt';
+
+        await actions.setQuery({ state, commit, getters }, payload);
+
+        expect(setUrlParams).not.toHaveBeenCalled();
+        expect(updateHistory).not.toHaveBeenCalled();
+        expect(fetchSidebarCountSpy).not.toHaveBeenCalled();
+
+        setUrlParams.mockClear();
+        updateHistory.mockClear();
+        fetchSidebarCountSpy.mockClear();
+
+        state.searchType = 'zoekt';
+        getters = { currentScope: 'not-blobs' };
+
+        await actions.setQuery({ state, commit, getters }, payload);
+
+        expect(setUrlParams).not.toHaveBeenCalled();
+        expect(updateHistory).not.toHaveBeenCalled();
+        expect(fetchSidebarCountSpy).not.toHaveBeenCalled();
+
+        setUrlParams.mockClear();
+        updateHistory.mockClear();
+        fetchSidebarCountSpy.mockClear();
+
+        getters = { currentScope: 'blobs' };
+        window.gon.features.zoektMultimatchFrontend = false;
+
+        await actions.setQuery({ state, commit, getters }, payload);
+
+        expect(setUrlParams).not.toHaveBeenCalled();
+        expect(updateHistory).not.toHaveBeenCalled();
+        expect(fetchSidebarCountSpy).not.toHaveBeenCalled();
       });
+    });
+
+    describe.each`
+      payload
+      ${{ key: REGEX_PARAM, value: true }}
+      ${{ key: REGEX_PARAM, value: { random: 'test' } }}
+    `('setQuery with REGEX_PARAM', ({ payload }) => {
+      describe(`when query param is ${payload.key}`, () => {
+        beforeEach(() => {
+          storeUtils.setDataToLS = jest.fn();
+          window.gon = { features: { zoektMultimatchFrontend: false } };
+          const getters = { currentScope: 'not-blobs' };
+          actions.setQuery({ state, commit: jest.fn(), getters }, payload);
+        });
 
-      it(`setsItem in local storage`, () => {
-        expect(storeUtils.setDataToLS).toHaveBeenCalledWith(LS_REGEX_HANDLE, expect.anything());
+        it(`setsItem in local storage`, () => {
+          expect(storeUtils.setDataToLS).toHaveBeenCalledWith(LS_REGEX_HANDLE, expect.anything());
+        });
       });
     });
   });
@@ -201,7 +286,12 @@ describe('Global Search Store Actions', () => {
   describe('applyQuery', () => {
     beforeEach(() => {
       setWindowLocation('https://test/');
-      jest.spyOn(urlUtils, 'visitUrl').mockReturnValue({});
+      jest.spyOn(urlUtils, 'visitUrl').mockImplementation(() => {});
+      jest
+        .spyOn(urlUtils, 'setUrlParams')
+        .mockReturnValue(
+          'https://test/?scope=issues&state=all&group_id=1&language%5B%5D=C&language%5B%5D=JavaScript&label_name%5B%5D=Aftersync&label_name%5B%5D=Brist&search=*',
+        );
     });
 
     it('calls visitUrl and setParams with the state.query', async () => {
@@ -355,17 +445,29 @@ describe('Global Search Store Actions', () => {
     });
   });
 
-  describe('fetchSidebarCount uses wild card seach', () => {
+  describe('fetchSidebarCount uses wild card search', () => {
     beforeEach(() => {
-      state.navigation = MOCK_NAVIGATION;
-      state.urlQuery.search = '';
+      state.navigation = noActiveItems;
+      state.query = { search: '' };
+      state.urlQuery = { search: '' };
+
+      jest.spyOn(urlUtils, 'setUrlParams').mockImplementation((params) => {
+        return `http://test.host/search/count?search=${params.search || '*'}`;
+      });
+
+      storeUtils.skipBlobESCount = jest.fn().mockReturnValue(true);
+
+      mock.onGet().reply(HTTP_STATUS_OK, MOCK_ENDPOINT_RESPONSE);
     });
 
     it('should use wild card', async () => {
-      await testAction({ action: actions.fetchSidebarCount, state, expectedMutations: [] });
-      expect(mock.history.get[0].url).toBe('http://test.host/search/count?scope=projects&search=*');
-      expect(mock.history.get[3].url).toBe(
-        'http://test.host/search/count?scope=merge_requests&search=*',
+      const commit = jest.fn();
+
+      await actions.fetchSidebarCount({ commit, state });
+
+      expect(urlUtils.setUrlParams).toHaveBeenCalledWith(
+        expect.objectContaining({ search: '*' }),
+        expect.anything(),
       );
     });
   });
@@ -409,16 +511,16 @@ describe('Global Search Store Actions', () => {
         {
           payload: {
             key: 'label_name',
-            value: ['Aftersync', 'Brist'],
+            value: ['Aftersync'],
           },
           type: 'SET_QUERY',
         },
         {
-          payload: true,
+          payload: false,
           type: 'SET_SIDEBAR_DIRTY',
         },
       ];
-      return testAction(actions.closeLabel, { key: '60' }, state, expectedResult, []);
+      return testAction(actions.closeLabel, { title: 'Brist' }, state, expectedResult, []);
     });
   });
 
diff --git a/spec/frontend/search/store/utils_spec.js b/spec/frontend/search/store/utils_spec.js
index 014f963002e1c2626fe37d316b1d085962d586b9..ffb0c80ed06e083bd407ef61d8dcc75c9b1e927e 100644
--- a/spec/frontend/search/store/utils_spec.js
+++ b/spec/frontend/search/store/utils_spec.js
@@ -1,3 +1,4 @@
+// rspec spec/frontend/fixtures/search_navigation.rb to generate these files
 import subItemActive from 'test_fixtures/search_navigation/sub_item_active.json';
 import noActiveItems from 'test_fixtures/search_navigation/no_active_items.json';
 import partialNavigationActive from 'test_fixtures/search_navigation/partial_navigation_active.json';
@@ -17,6 +18,7 @@ import {
   injectRegexSearch,
   scopeCrawler,
   skipBlobESCount,
+  buildDocumentTitle,
 } from '~/search/store/utils';
 import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
 import { TEST_HOST } from 'helpers/test_constants';
@@ -447,4 +449,46 @@ describe('Global Search Store Utils', () => {
       expect(skipBlobESCount(state, SCOPE_BLOB)).toBe(false);
     });
   });
+
+  describe('buildDocumentTitle', () => {
+    const SEARCH_WINDOW_TITLE = `Search`; // Make sure this matches your actual constant
+    let originalTitle;
+
+    beforeEach(() => {
+      originalTitle = document.title;
+    });
+
+    afterEach(() => {
+      document.title = originalTitle;
+    });
+
+    it('returns original title when document title does not include search title', () => {
+      document.title = 'GitLab';
+      expect(buildDocumentTitle('test')).toBe('test');
+    });
+
+    it('prepends new title when document title starts with search title', () => {
+      document.title = `${SEARCH_WINDOW_TITLE} · GitLab`;
+      const result = buildDocumentTitle('test');
+      expect(result).toBe(`test · ${SEARCH_WINDOW_TITLE} · GitLab`);
+    });
+
+    it('prepends new title when document title starts with dot and search title', () => {
+      document.title = ` · ${SEARCH_WINDOW_TITLE} · GitLab`;
+      const result = buildDocumentTitle('test');
+      expect(result).toBe(`test · ${SEARCH_WINDOW_TITLE} · GitLab`);
+    });
+
+    it('replaces title before search title with new title', () => {
+      document.title = `Issues · ${SEARCH_WINDOW_TITLE} · GitLab`;
+      const result = buildDocumentTitle('test');
+      expect(result).toBe(`test · ${SEARCH_WINDOW_TITLE} · GitLab`);
+    });
+
+    it('handles complex titles correctly', () => {
+      document.title = `Something · With · Dots · ${SEARCH_WINDOW_TITLE} · GitLab`;
+      const result = buildDocumentTitle('test');
+      expect(result).toBe(`test · ${SEARCH_WINDOW_TITLE} · GitLab`);
+    });
+  });
 });
diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js
index ca4023086db3033cb7caeb0d17cc55d8e508b373..da2d3e12ffc66f71038e518dc622c88d92e976ee 100644
--- a/spec/frontend/search/topbar/components/app_spec.js
+++ b/spec/frontend/search/topbar/components/app_spec.js
@@ -229,4 +229,48 @@ describe('GlobalSearchTopbar', () => {
       });
     });
   });
+
+  describe('search computed property setter', () => {
+    describe.each`
+      FF                                    | scope       | searchType    | debounced
+      ${{ zoektMultimatchFrontend: true }}  | ${'blobs'}  | ${'zoekt'}    | ${true}
+      ${{ zoektMultimatchFrontend: false }} | ${'blobs'}  | ${'zoekt'}    | ${false}
+      ${{ zoektMultimatchFrontend: true }}  | ${'issues'} | ${'zoekt'}    | ${false}
+      ${{ zoektMultimatchFrontend: true }}  | ${'blobs'}  | ${'advanced'} | ${false}
+    `(
+      'when isMultiMatch is $debounced (FF: $FF, scope: $scope, searchType: $searchType)',
+      ({ FF, scope, searchType, debounced }) => {
+        beforeEach(() => {
+          getterSpies.currentScope = jest.fn(() => scope);
+          actionSpies.setQuery.mockClear();
+
+          createComponent({
+            featureFlag: FF,
+            initialState: { searchType },
+          });
+
+          wrapper.vm.debouncedSetQuery = jest.fn();
+        });
+
+        it(`${debounced ? 'calls debouncedSetQuery' : 'calls setQuery directly'}`, () => {
+          findGlSearchBox().vm.$emit('input', 'new search value');
+
+          if (debounced) {
+            expect(actionSpies.setQuery).not.toHaveBeenCalled();
+          } else {
+            expect(actionSpies.setQuery).toHaveBeenCalled();
+
+            const lastCallArgs = actionSpies.setQuery.mock.calls[0];
+            const payload = lastCallArgs[lastCallArgs.length - 1];
+            expect(payload).toEqual(
+              expect.objectContaining({
+                key: 'search',
+                value: 'new search value',
+              }),
+            );
+          }
+        });
+      },
+    );
+  });
 });
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 36544a75648d5d4503eb95e98c2c9345e8f5e9d4..782d49f4968233972321e78bbf910a28c019fca1 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -63,8 +63,10 @@ def simple_sanitize(str)
       shared_examples 'for users' do
         let_it_be(:another_user) { create(:user, name: 'Jane Doe') }
         let(:term) { 'jane' }
+        let_it_be(:project) { create(:project, developers: user) }
 
         it 'returns users matching the term' do
+          project.add_developer(another_user)
           result = search_autocomplete_opts(term)
           expect(result.size).to eq(1)
           expect(result.first[:id]).to eq(another_user.id)
@@ -97,21 +99,29 @@ def simple_sanitize(str)
 
             it 'includes users with matching public emails' do
               public_email_user
+              project.add_developer(public_email_user)
+
               expect(ids).to include(public_email_user.id)
             end
 
             it 'includes users in forbidden states' do
               banned_user
+              project.add_developer(banned_user)
+
               expect(ids).to include(banned_user.id)
             end
 
             it 'includes users without matching public emails but with matching private emails' do
               private_email_user
+              project.add_developer(private_email_user)
+
               expect(ids).to include(private_email_user.id)
             end
 
             it 'includes users matching on secondary email' do
               secondary_email
+              project.add_developer(user_with_other_email)
+
               expect(ids).to include(secondary_email.user_id)
             end
           end
@@ -123,21 +133,29 @@ def simple_sanitize(str)
 
             it 'includes users with matching public emails' do
               public_email_user
+              project.add_developer(public_email_user)
+
               expect(ids).to include(public_email_user.id)
             end
 
             it 'does not include users in forbidden states' do
               banned_user
+              project.add_developer(banned_user)
+
               expect(ids).not_to include(banned_user.id)
             end
 
             it 'does not include users without matching public emails but with matching private emails' do
               private_email_user
+              project.add_developer(private_email_user)
+
               expect(ids).not_to include(private_email_user.id)
             end
 
             it 'does not include users matching on secondary email' do
               secondary_email
+              project.add_developer(secondary_email)
+
               expect(ids).not_to include(secondary_email.user_id)
             end
           end
@@ -146,6 +164,12 @@ def simple_sanitize(str)
         context 'with limiting' do
           let_it_be(:users) { create_list(:user, 6, name: 'Jane Doe') }
 
+          before do
+            users.each do |user|
+              project.add_developer(user)
+            end
+          end
+
           it 'only returns the first 5 users' do
             result = search_autocomplete_opts(term)
             expect(result.size).to eq(5)
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index b15af98e1d9b550a8c4ec0651b329855d5753026..42971c087fc7f41d724117cf86eae117dbd01a73 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -7,7 +7,7 @@
   include SearchHelpers
   using RSpec::Parameterized::TableSyntax
 
-  let_it_be(:user) { create(:user) }
+  let_it_be(:user) { create(:user, username: 'foobar') }
   let_it_be(:project) { create(:project, name: 'foo') }
   let_it_be(:issue) { create(:issue, project: project, title: 'foo') }
   let_it_be(:milestone) { create(:milestone, project: project, title: 'foo') }
@@ -313,18 +313,166 @@
     end
 
     describe '#users' do
+      subject(:user_search_result) { results.objects('users') }
+
+      let_it_be(:another_user) { create(:user, username: 'barfoo') }
+      let_it_be(:group) { create(:group) }
+
       it 'does not call the UsersFinder when the current_user is not allowed to read users list' do
         allow(Ability).to receive(:allowed?).and_return(false)
 
-        expect(UsersFinder).not_to receive(:new).with(user, { search: 'foo', use_minimum_char_limit: false }).and_call_original
+        expect(UsersFinder).not_to receive(:new)
 
-        results.objects('users')
+        user_search_result
       end
 
       it 'calls the UsersFinder' do
-        expect(UsersFinder).to receive(:new).with(user, { search: 'foo', use_minimum_char_limit: false }).and_call_original
+        expected_params = {
+          search: 'foo',
+          use_minimum_char_limit: false
+        }
+
+        expect(UsersFinder).to receive(:new).with(user, expected_params).and_call_original
+
+        user_search_result
+      end
+
+      context 'when the autocomplete filter is added' do
+        let(:filters) { { autocomplete: true } }
+
+        shared_examples 'returns users' do
+          it 'returns the current_user since they match the query' do
+            expect(user_search_result).to match_array(user)
+          end
+
+          context 'when another user belongs to a project the current_user belongs to' do
+            before do
+              project.add_developer(another_user)
+            end
+
+            it 'includes the other user' do
+              expect(user_search_result).to match_array([user, another_user])
+            end
+          end
+
+          context 'when another user belongs to a group' do
+            before do
+              group.add_developer(another_user)
+            end
+
+            it 'does not include the other user' do
+              expect(user_search_result).not_to include(another_user)
+            end
+
+            context 'when the current_user also belongs to that group' do
+              before do
+                group.add_developer(user)
+              end
+
+              it 'includes the other user' do
+                expect(user_search_result).to match_array([user, another_user])
+              end
+            end
+
+            context 'when the current_user belongs to a parent of the group' do
+              let_it_be(:parent_group) { create(:group) }
+              let_it_be(:group) { create(:group, parent: parent_group) }
+
+              before do
+                parent_group.add_developer(user)
+              end
+
+              it 'includes the other user' do
+                expect(user_search_result).to match_array([user, another_user])
+              end
+            end
+
+            context 'when the current_user belongs to a group that is shared by the group' do
+              let_it_be_with_reload(:shared_with_group) { create(:group) }
+              let_it_be_with_reload(:group_group_link) do
+                create(
+                  :group_group_link,
+                  group_access: ::Gitlab::Access::GUEST,
+                  shared_group: group,
+                  shared_with_group: shared_with_group
+                )
+              end
+
+              before do
+                shared_with_group.add_developer(user)
+              end
+
+              it 'includes the other user' do
+                expect(user_search_result).to match_array([user, another_user])
+              end
+            end
+
+            context 'when the current_user belongs to a child of the group' do
+              let_it_be(:child_group) { create(:group, parent: group) }
+
+              before do
+                child_group.add_developer(user)
+              end
+
+              it 'includes the other user' do
+                expect(user_search_result).to match_array([user, another_user])
+              end
+            end
+          end
+
+          context 'when another user is a guest of a private group' do
+            let_it_be(:private_group) { create(:group, :private) }
+
+            before do
+              private_group.add_guest(another_user)
+            end
 
-        results.objects('users')
+            it 'does not include the other user' do
+              expect(user_search_result).to match_array(user)
+            end
+
+            context 'when the current_user is a guest of the private group' do
+              before do
+                private_group.add_guest(user)
+              end
+
+              it 'includes the other user' do
+                expect(user_search_result).to match_array([user, another_user])
+              end
+            end
+
+            context 'when the current_user is a guest of the public parent of the private group' do
+              let_it_be(:public_parent_group) { create(:group, :public) }
+              let_it_be(:private_group) { create(:group, :private, parent: public_parent_group) }
+
+              before do
+                public_parent_group.add_guest(user)
+              end
+
+              it 'includes the other user' do
+                expect(user_search_result).to match_array([user, another_user])
+              end
+            end
+          end
+        end
+
+        context 'when users_search_scoped_to_authorized_namespaces_basic_search is enabled' do
+          before do
+            stub_feature_flags(users_search_scoped_to_authorized_namespaces_basic_search: true)
+            stub_feature_flags(users_search_scoped_to_authorized_namespaces_basic_search_by_ids: false)
+          end
+
+          include_examples 'returns users'
+        end
+
+        context 'when users_search_scoped_to_authorized_namespaces_basic_search_by_ids is enabled' do
+          before do
+            stub_feature_flags(users_search_scoped_to_authorized_namespaces_basic_search_by_ids: true)
+            stub_feature_flags(users_search_scoped_to_authorized_namespaces_basic_search: false)
+          end
+
+          include_examples 'returns users'
+        end
       end
     end
   end
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index b9151964628254a4b34024202cfe065d84fd82f6..d4bf82f536a67b3e8f0b6ba7c8b840643527439f 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -9,15 +9,16 @@ def fill_in_search(text)
   end
 
   def submit_search(query)
-    # Forms directly on the search page
     if page.has_css?('.search-page-form')
       search_form = '.search-page-form'
-    # Open search modal from super sidebar
+
     else
       find_by_testid('super-sidebar-search-button').click
       search_form = '#super-sidebar-search-modal'
     end
 
+    wait_for_all_requests
+
     page.within(search_form) do
       field = find_field('search')
       field.click