diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 504daf2662e12a278589a225236add6521a4ca55..595e34821af7d13a9eaacbf67483253701ed1daa 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -24,14 +24,27 @@ class NamespaceSetting < ApplicationRecord
   chronic_duration_attr :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval
   chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
 
-  NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal,
-                               :lock_delayed_project_removal, :resource_access_token_creation_allowed,
-                               :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap,
-                               :setup_for_company, :jobs_to_be_done, :runner_token_expiration_interval, :enabled_git_access_protocol,
-                               :subgroup_runner_token_expiration_interval, :project_runner_token_expiration_interval].freeze
+  NAMESPACE_SETTINGS_PARAMS = %i[
+    default_branch_name
+    delayed_project_removal
+    lock_delayed_project_removal
+    resource_access_token_creation_allowed
+    prevent_sharing_groups_outside_hierarchy
+    new_user_signups_cap
+    setup_for_company
+    jobs_to_be_done
+    runner_token_expiration_interval
+    enabled_git_access_protocol
+    subgroup_runner_token_expiration_interval
+    project_runner_token_expiration_interval
+  ].freeze
 
   self.primary_key = :namespace_id
 
+  def self.allowed_namespace_settings_params
+    NAMESPACE_SETTINGS_PARAMS
+  end
+
   sanitizes! :default_branch_name
 
   def prevent_sharing_groups_outside_hierarchy
diff --git a/app/services/groups/base_service.rb b/app/services/groups/base_service.rb
index 06136aff50ec1dde58e339b3ded8ccd4cbbbf15e..9705f3a560d9228dc0e24ac37cb46f2ed4a544f4 100644
--- a/app/services/groups/base_service.rb
+++ b/app/services/groups/base_service.rb
@@ -13,11 +13,11 @@ def initialize(group, user, params = {})
     private
 
     def handle_namespace_settings
-      settings_params = params.slice(*::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS)
+      settings_params = params.slice(*::NamespaceSetting.allowed_namespace_settings_params)
 
       return if settings_params.empty?
 
-      ::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS.each do |nsp|
+      ::NamespaceSetting.allowed_namespace_settings_params.each do |nsp|
         params.delete(nsp)
       end
 
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 639f7c68c40c71a1273a3b74a03b7b439f9ae696..35716f7742a82cdf43069e933f80b20b3b2ae435 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -13,7 +13,7 @@ def execute
       remove_unallowed_params
       set_visibility_level
 
-      @group = Group.new(params.except(*::NamespaceSetting::NAMESPACE_SETTINGS_PARAMS))
+      @group = Group.new(params.except(*::NamespaceSetting.allowed_namespace_settings_params))
 
       @group.build_namespace_settings
       handle_namespace_settings
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 233dca33bb88a5e6fdd6e6fea7d592242924b0e7..56df8f93113a48b9429693191da2d8816619614e 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -10,6 +10,9 @@ en:
         target: Target issue
       group:
         path: Group URL
+      namespace_setting:
+        unique_project_download_limit: "Number of projects"
+        unique_project_download_limit_interval_in_seconds: "Interval (seconds)"
       member:
         user: "The member's email address"
         invite_email: "The member's email address"
diff --git a/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e821cb17a2f6d6421a2a8dd855637e58488e7cc
--- /dev/null
+++ b/db/migrate/20220613054349_add_unique_project_download_limit_settings_to_namespace_settings.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddUniqueProjectDownloadLimitSettingsToNamespaceSettings < Gitlab::Database::Migration[2.0]
+  enable_lock_retries!
+
+  def change
+    add_column :namespace_settings, :unique_project_download_limit, :smallint,
+      default: 0, null: false
+    add_column :namespace_settings, :unique_project_download_limit_interval_in_seconds, :integer,
+      default: 0, null: false
+  end
+end
diff --git a/db/schema_migrations/20220613054349 b/db/schema_migrations/20220613054349
new file mode 100644
index 0000000000000000000000000000000000000000..1c3806a80c8b885d84907187fdf045003b3f4263
--- /dev/null
+++ b/db/schema_migrations/20220613054349
@@ -0,0 +1 @@
+4c3e4852614dd1a59d63809c40417887794bcbbcf8d3ea3a96f8846e2bd5f795
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index f697bacdfc51ac46f902b9b7d14af64b2d28816b..2013a434bab1e63651653b9bdf6acff85c431722 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -17550,6 +17550,8 @@ CREATE TABLE namespace_settings (
     project_runner_token_expiration_interval integer,
     exclude_from_free_user_cap boolean DEFAULT false NOT NULL,
     enabled_git_access_protocol smallint DEFAULT 0 NOT NULL,
+    unique_project_download_limit smallint DEFAULT 0 NOT NULL,
+    unique_project_download_limit_interval_in_seconds integer DEFAULT 0 NOT NULL,
     CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255))
 );
 
diff --git a/ee/app/controllers/groups/settings/reporting_controller.rb b/ee/app/controllers/groups/settings/reporting_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..705c4067deeadc8b12c7926d08e2533d551982a9
--- /dev/null
+++ b/ee/app/controllers/groups/settings/reporting_controller.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Groups
+  module Settings
+    class ReportingController < Groups::ApplicationController
+      layout 'group_settings'
+
+      before_action :check_feature_availability
+      before_action :authorize_admin_group!
+
+      feature_category :insider_threat
+      urgency :low
+
+      def show
+      end
+
+      def update
+        if Groups::UpdateService.new(@group, current_user, group_params).execute
+          notice = _('Group "%{group_name}" was successfully updated.' % { group_name: @group.name })
+
+          redirect_to group_settings_reporting_path(@group), notice: notice
+        else
+          render action: "show"
+        end
+      end
+
+      private
+
+      def group_params
+        params.require(:group).permit(%i[
+          unique_project_download_limit
+          unique_project_download_limit_interval_in_seconds
+        ])
+      end
+
+      def check_feature_availability
+        render_404 unless group.unique_project_download_limit_enabled?
+      end
+    end
+  end
+end
diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb
index 9d72dda8d51cbc49ba80d42fb6d3771a6da6319c..53cd6da39bb6ddb78ddfe45ff0449579d1ba1566 100644
--- a/ee/app/models/ee/group.rb
+++ b/ee/app/models/ee/group.rb
@@ -177,6 +177,12 @@ def enforced_sso?
       def repository_read_only?
         !!namespace_settings&.repository_read_only?
       end
+
+      def unique_project_download_limit_enabled?
+        root? &&
+          ::Feature.enabled?(:limit_unique_project_downloads_per_namespace_user, self) &&
+          licensed_feature_available?(:unique_project_download_limit)
+      end
     end
 
     class_methods do
diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb
index 92d63050df5e590cddc981560241ddd8b763069b..d63e58c88106b1219280f0c82031cc14d7a662bf 100644
--- a/ee/app/models/ee/namespace_setting.rb
+++ b/ee/app/models/ee/namespace_setting.rb
@@ -5,6 +5,13 @@ module NamespaceSetting
     extend ActiveSupport::Concern
 
     prepended do
+      validates :unique_project_download_limit,
+        numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 10_000 },
+        presence: true
+      validates :unique_project_download_limit_interval_in_seconds,
+        numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 10.days.to_i },
+        presence: true
+
       validate :user_cap_allowed, if: -> { enabling_user_cap? }
 
       before_save :set_prevent_sharing_groups_outside_hierarchy, if: -> { user_cap_enabled? }
@@ -46,5 +53,19 @@ def user_cap_enabled?
         new_user_signups_cap.present? && namespace.root?
       end
     end
+
+    class_methods do
+      extend ::Gitlab::Utils::Override
+
+      EE_NAMESPACE_SETTINGS_PARAMS = %i[
+        unique_project_download_limit
+        unique_project_download_limit_interval_in_seconds
+      ].freeze
+
+      override :allowed_namespace_settings_params
+      def allowed_namespace_settings_params
+        super + EE_NAMESPACE_SETTINGS_PARAMS
+      end
+    end
   end
 end
diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb
index 5f520d44559e395ce2ffd16e7ad9a988ec2f9ee9..f404571964d5a217d7f90e3d7910dd8003e5a872 100644
--- a/ee/app/models/gitlab_subscriptions/features.rb
+++ b/ee/app/models/gitlab_subscriptions/features.rb
@@ -216,6 +216,7 @@ class Features
       stale_runner_cleanup_for_namespace
       status_page
       subepics
+      unique_project_download_limit
       vulnerability_auto_fix
       vulnerability_finding_signatures
     ].freeze
diff --git a/ee/app/views/groups/settings/reporting/show.html.haml b/ee/app/views/groups/settings/reporting/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..2e207d24c642ad4ecce56ede883042b4f94a485b
--- /dev/null
+++ b/ee/app/views/groups/settings/reporting/show.html.haml
@@ -0,0 +1,25 @@
+- title = s_("GroupSettings|Reporting")
+- breadcrumb_title title
+- page_title title
+
+%h4.settings-title
+  = s_('GroupSettings|Project download rate limit')
+%p
+  = s_('GroupSettings|Automatically ban users who download more than the specified number of projects within the specified interval.')
+
+= form_errors(@group.namespace_settings)
+
+= gitlab_ui_form_for @group, url: group_settings_reporting_path(@group) do |f|
+  .form-group
+    = f.label :unique_project_download_limit, s_('GroupSettings|Number of projects'), class: 'label-bold'
+    = f.number_field :unique_project_download_limit, value: @group.namespace_settings&.unique_project_download_limit, class: 'form-control gl-form-input'
+    .form-text.text-muted
+      = s_("GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned. Set to 0 to disable limiting.")
+
+  .form-group
+    = f.label :unique_project_download_limit_interval_in_seconds, s_('GroupSettings|Interval (seconds)'), class: 'label-bold'
+    = f.number_field :unique_project_download_limit_interval_in_seconds, value: @group.namespace_settings&.unique_project_download_limit_interval_in_seconds, class: 'form-control gl-form-input'
+    .form-text.text-muted
+      = s_('GroupSettings|Set to 0 to disable limiting.')
+
+  = f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-4'
diff --git a/ee/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml b/ee/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9e2735672fb06102d15ba9d24278370477ea80f6
--- /dev/null
+++ b/ee/config/feature_flags/development/limit_unique_project_downloads_per_namespace_user.yml
@@ -0,0 +1,8 @@
+---
+name: limit_unique_project_downloads_per_namespace_user
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89996
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365724
+milestone: '15.2'
+type: development
+group: group::anti-abuse
+default_enabled: false
diff --git a/ee/config/routes/group.rb b/ee/config/routes/group.rb
index 3487f2a64b38fd88999831029df95218ede82192..20aef35be02de661e4a7a7c97c35846cb03c04a2 100644
--- a/ee/config/routes/group.rb
+++ b/ee/config/routes/group.rb
@@ -7,6 +7,10 @@
         constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
     draw :wiki
 
+    namespace :settings do
+      resource :reporting, only: [:show, :update], controller: 'reporting'
+    end
+
     resources :group_members, only: [], concerns: :access_requestable do
       patch :override, on: :member
 
diff --git a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb
index 3b59a60eed7d0689d20981c50055e8f60ac11701..347f5aefb3bb4b759f4593b1157341dad88c8ec0 100644
--- a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb
+++ b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb
@@ -18,6 +18,7 @@ def configure_menu_items
             add_item(saml_group_links_menu_item)
             add_item(usage_quotas_menu_item)
             add_item(billing_menu_item)
+            add_item(reporting_menu_item)
 
             true
           end
@@ -133,6 +134,19 @@ def administration_nav_item_disabled?
               ::Feature.disabled?(:group_administration_nav_item, context.group)
             end
           end
+
+          def reporting_menu_item
+            unless context.group.unique_project_download_limit_enabled?
+              return ::Sidebars::NilMenuItem.new(item_id: :reporting)
+            end
+
+            ::Sidebars::MenuItem.new(
+              title: s_('GroupSettings|Reporting'),
+              link: group_settings_reporting_path(context.group),
+              active_routes: { path: 'reporting#show' },
+              item_id: :reporting
+            )
+          end
         end
       end
     end
diff --git a/ee/spec/features/groups/settings/reporting_spec.rb b/ee/spec/features/groups/settings/reporting_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..36009d0cd2a200222f6585381d44c887d72d1843
--- /dev/null
+++ b/ee/spec/features/groups/settings/reporting_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Group reporting settings' do
+  let_it_be(:user) { create(:user) }
+
+  let(:group) { create(:group) }
+  let(:feature_flag_enabled) { true }
+  let(:licensed_feature_available) { true }
+  let(:current_limit) { 1 }
+  let(:current_interval) { 9 }
+
+  before do
+    stub_feature_flags(limit_unique_project_downloads_per_namespace_user: feature_flag_enabled)
+    stub_licensed_features(unique_project_download_limit: licensed_feature_available)
+
+    sign_in(user)
+
+    group.add_owner(user)
+
+    group.namespace_settings.update!(
+      unique_project_download_limit: current_limit,
+      unique_project_download_limit_interval_in_seconds: current_interval
+    )
+
+    visit group_settings_reporting_path(group)
+  end
+
+  it 'displays the side bar menu item' do
+    page.within('.shortcuts-settings') do
+      expect(page).to have_link 'Reporting', href: group_settings_reporting_path(group)
+    end
+  end
+
+  it 'updates the settings' do
+    limit_label = s_('GroupSettings|Number of projects')
+    interval_label = s_('GroupSettings|Interval (seconds)')
+
+    expect(page).to have_field(limit_label, with: current_limit)
+    expect(page).to have_field(interval_label, with: current_interval)
+
+    new_limit = 5
+    new_interval = 300
+
+    fill_in(limit_label, with: new_limit)
+    fill_in(interval_label, with: new_interval)
+
+    click_button 'Save changes'
+
+    group.reload
+
+    expect(group.namespace_settings.unique_project_download_limit).to eq new_limit
+    expect(group.namespace_settings.unique_project_download_limit_interval_in_seconds).to eq new_interval
+
+    expect(page).to have_field(limit_label, with: new_limit)
+    expect(page).to have_field(interval_label, with: new_interval)
+  end
+
+  it 'displays validation errors' do
+    fill_in s_('GroupSettings|Number of projects'), with: -1
+    fill_in s_('GroupSettings|Interval (seconds)'), with: -1
+
+    click_button 'Save changes'
+
+    expect(page).to have_content('Number of projects must be greater than or equal to 0')
+    expect(page).to have_content('Interval (seconds) must be greater than or equal to 0')
+  end
+end
diff --git a/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb b/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb
index d28d1f508456ec9cfab946a053ca26649465b72c..1779b7d48aac363464eda5b716cbdc767ac8b0b0 100644
--- a/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb
+++ b/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb
@@ -183,5 +183,22 @@
         specify { is_expected.to be_nil }
       end
     end
+
+    describe 'Reporting menu' do
+      let(:item_id) { :reporting }
+      let(:feature_enabled) { true }
+
+      before do
+        allow(group).to receive(:unique_project_download_limit_enabled?) { feature_enabled }
+      end
+
+      it { is_expected.to be_present }
+
+      context 'when feature is not enabled' do
+        let(:feature_enabled) { false }
+
+        it { is_expected.to be_nil }
+      end
+    end
   end
 end
diff --git a/ee/spec/models/ee/group_spec.rb b/ee/spec/models/ee/group_spec.rb
index 52a0c42ec17e6083b0c4be47783aa02c5728646f..12be8c6c50d41ec03a5553b079db391e0aef72b5 100644
--- a/ee/spec/models/ee/group_spec.rb
+++ b/ee/spec/models/ee/group_spec.rb
@@ -2542,4 +2542,40 @@ def webhook_headers
 
     it { is_expected.to contain_exactly(cluster_agent_for_project, cluster_agent_for_project_in_subgroup) }
   end
+
+  describe '#unique_project_download_limit_enabled?' do
+    let_it_be(:group) { create(:group) }
+
+    let(:feature_flag_enabled) { true }
+    let(:licensed_feature_available) { true }
+
+    before do
+      stub_feature_flags(limit_unique_project_downloads_per_namespace_user: feature_flag_enabled)
+      stub_licensed_features(unique_project_download_limit: licensed_feature_available)
+    end
+
+    subject { group.unique_project_download_limit_enabled? }
+
+    it { is_expected.to eq true }
+
+    context 'when feature flag is disabled' do
+      let(:feature_flag_enabled) { false }
+
+      it { is_expected.to eq false }
+    end
+
+    context 'when licensed feature is not available' do
+      let(:licensed_feature_available) { false }
+
+      it { is_expected.to eq false }
+    end
+
+    context 'when sub-group' do
+      let(:subgroup) { create(:group, parent: group) }
+
+      subject { subgroup.unique_project_download_limit_enabled? }
+
+      it { is_expected.to eq false }
+    end
+  end
 end
diff --git a/ee/spec/models/namespace_setting_spec.rb b/ee/spec/models/namespace_setting_spec.rb
index 0076c55437f8f118f961cd795e6ed618855ee3a4..1d415b8dd0d181c06b45999b564977fa17faae37 100644
--- a/ee/spec/models/namespace_setting_spec.rb
+++ b/ee/spec/models/namespace_setting_spec.rb
@@ -6,6 +6,25 @@
   let(:group) { create(:group) }
   let(:setting) { group.namespace_settings }
 
+  describe 'validations' do
+    subject(:settings) { group.namespace_settings }
+
+    it { is_expected.to validate_presence_of(:unique_project_download_limit) }
+    it { is_expected.to validate_presence_of(:unique_project_download_limit_interval_in_seconds) }
+    it {
+      is_expected.to validate_numericality_of(:unique_project_download_limit)
+        .only_integer
+        .is_greater_than_or_equal_to(0)
+        .is_less_than_or_equal_to(10_000)
+    }
+    it {
+      is_expected.to validate_numericality_of(:unique_project_download_limit_interval_in_seconds)
+        .only_integer
+        .is_greater_than_or_equal_to(0)
+        .is_less_than_or_equal_to(10.days.to_i)
+    }
+  end
+
   describe '#prevent_forking_outside_group?' do
     context 'with feature available' do
       before do
@@ -220,4 +239,13 @@
       end
     end
   end
+
+  describe '.allowed_namespace_settings_params' do
+    it 'includes attributes used for limiting unique project downloads' do
+      expect(described_class.allowed_namespace_settings_params).to include(*%i[
+        unique_project_download_limit
+        unique_project_download_limit_interval_in_seconds
+      ])
+    end
+  end
 end
diff --git a/ee/spec/requests/groups/settings/reporting_controller_spec.rb b/ee/spec/requests/groups/settings/reporting_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2c8db608b1ee6d2b6d946a2af4c22d01504d51c8
--- /dev/null
+++ b/ee/spec/requests/groups/settings/reporting_controller_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::Settings::ReportingController, type: :request do
+  let_it_be(:user) { create(:user) }
+
+  let(:group) { create(:group) }
+  let(:feature_flag_enabled) { true }
+  let(:licensed_feature_available) { true }
+
+  before do
+    stub_feature_flags(limit_unique_project_downloads_per_namespace_user: feature_flag_enabled)
+    stub_licensed_features(unique_project_download_limit: licensed_feature_available)
+
+    sign_in(user)
+  end
+
+  shared_examples 'renders 404' do
+    it 'renders 404' do
+      expect(response).to have_gitlab_http_status(:not_found)
+    end
+  end
+
+  shared_examples '404 when feature is unavailable' do
+    before do
+      subject
+    end
+
+    context 'when feature flag is disabled' do
+      let(:feature_flag_enabled) { false }
+
+      it_behaves_like 'renders 404'
+    end
+
+    context 'when feature flag is disabled' do
+      let(:licensed_feature_available) { false }
+
+      it_behaves_like 'renders 404'
+    end
+
+    context 'when subgroup' do
+      let(:group) { create(:group, parent: create(:group)) }
+
+      it_behaves_like 'renders 404'
+    end
+  end
+
+  describe 'GET /groups/:group_id/-/settings/reporting' do
+    subject(:request) { get group_settings_reporting_path(group) }
+
+    context 'when user is owner' do
+      before do
+        group.add_owner(user)
+      end
+
+      it 'renders show with 200 status code' do
+        request
+
+        expect(response).to have_gitlab_http_status(:ok)
+        expect(response).to render_template(:show)
+      end
+
+      it_behaves_like '404 when feature is unavailable'
+    end
+
+    context 'when user is not owner' do
+      before do
+        group.add_maintainer(user)
+      end
+
+      it 'renders a 404' do
+        request
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+  end
+
+  describe 'PATCH #update' do
+    let(:params) { { group: { unique_project_download_limit: 10 } } }
+
+    subject(:request) do
+      patch(group_settings_reporting_path(group), params: params)
+    end
+
+    context 'when user is not an owner' do
+      before do
+        group.add_maintainer(user)
+      end
+
+      it 'renders a 404' do
+        request
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+
+    context 'when user is an owner' do
+      before do
+        group.add_owner(user)
+      end
+
+      it_behaves_like '404 when feature is unavailable'
+
+      it 'redirects back to show page' do
+        request
+
+        expect(response).to redirect_to(group_settings_reporting_path(group))
+        expect(flash[:notice]).to include("Group \"#{group.name}\" was successfully updated.")
+      end
+
+      context 'update failed' do
+        let(:params) { { group: { unique_project_download_limit: -1 } } }
+
+        it 're-renders show template' do
+          request
+
+          expect(response).not_to have_gitlab_http_status(:redirect)
+          expect(response).to render_template(:show)
+        end
+      end
+    end
+  end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1a5e83516dc2c23a505370450b1eb11c6b749c40..3504f8a06cf337914b43ada88696dd8f6ad2de53 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18014,6 +18014,9 @@ msgstr ""
 msgid "Group"
 msgstr ""
 
+msgid "Group \"%{group_name}\" was successfully updated."
+msgstr ""
+
 msgid "Group %{group_name} couldn't be exported."
 msgstr ""
 
@@ -18452,6 +18455,9 @@ msgstr ""
 msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
 msgstr ""
 
+msgid "GroupSettings|Automatically ban users who download more than the specified number of projects within the specified interval."
+msgstr ""
+
 msgid "GroupSettings|Available only on the top-level group. Applies to all subgroups. Groups already shared with a group outside %{group} are still shared unless removed manually."
 msgstr ""
 
@@ -18509,9 +18515,15 @@ msgstr ""
 msgid "GroupSettings|If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
 msgstr ""
 
+msgid "GroupSettings|Interval (seconds)"
+msgstr ""
+
 msgid "GroupSettings|Members cannot invite groups outside of %{group} and its subgroups"
 msgstr ""
 
+msgid "GroupSettings|Number of projects"
+msgstr ""
+
 msgid "GroupSettings|Organizations and contacts can be created and associated with issues."
 msgstr ""
 
@@ -18530,9 +18542,15 @@ msgstr ""
 msgid "GroupSettings|Prevent forking setting was not saved"
 msgstr ""
 
+msgid "GroupSettings|Project download rate limit"
+msgstr ""
+
 msgid "GroupSettings|Projects in %{group} cannot be shared with other groups"
 msgstr ""
 
+msgid "GroupSettings|Reporting"
+msgstr ""
+
 msgid "GroupSettings|Select a subgroup to use as the source for custom project templates for this group."
 msgstr ""
 
@@ -18551,9 +18569,15 @@ msgstr ""
 msgid "GroupSettings|Set the initial name and protections for the default branch of new repositories created in the group."
 msgstr ""
 
+msgid "GroupSettings|Set to 0 to disable limiting."
+msgstr ""
+
 msgid "GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found."
 msgstr ""
 
+msgid "GroupSettings|The maximum number of unique projects a user can download within the specified interval before they're banned. Set to 0 to disable limiting."
+msgstr ""
+
 msgid "GroupSettings|The projects in this subgroup can be selected as templates for new projects created in the group. %{link_start}Learn more.%{link_end}"
 msgstr ""