diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index a0706eaa46cfc309930e4be73be040063ab29744..dd8c5d49cf44db89b260ba564349d0a4eea51ec6 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -18,6 +18,7 @@ class IssuePolicy < IssuablePolicy
     prevent :read_issue_iid
     prevent :update_issue
     prevent :admin_issue
+    prevent :create_note
   end
 
   rule { locked }.policy do
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index 777f933cdcd839fb69c3ae03e52ea372bbf29787..16e67a06d5652a23829b05704ade87d84f4a8d64 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -28,5 +28,8 @@ class PersonalSnippetPolicy < BasePolicy
 
   rule { anonymous }.prevent :comment_personal_snippet
 
-  rule { can?(:comment_personal_snippet) }.enable :award_emoji
+  rule { can?(:comment_personal_snippet) }.policy do
+    enable :create_note
+    enable :award_emoji
+  end
 end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
index 7dafa33bb99ed16892293164f962b5ea6bc58148..e5e005cee6d3927a46fc5c2065f60bb633c78ba6 100644
--- a/app/policies/project_snippet_policy.rb
+++ b/app/policies/project_snippet_policy.rb
@@ -43,4 +43,6 @@ class ProjectSnippetPolicy < BasePolicy
     enable :update_project_snippet
     enable :admin_project_snippet
   end
+
+  rule { ~can?(:read_project_snippet) }.prevent :create_note
 end
diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb
index 7b92fe6fe14a91a9ab31a2d998e8081cfc7c1072..bae98ede5612ff5edd646dde4d6b2f37c9f5b8c3 100644
--- a/app/services/notes/build_service.rb
+++ b/app/services/notes/build_service.rb
@@ -9,7 +9,7 @@ module Notes
       if in_reply_to_discussion_id.present?
         discussion = find_discussion(in_reply_to_discussion_id)
 
-        unless discussion
+        unless discussion && can?(current_user, :create_note, discussion.noteable)
           note = Note.new
           note.errors.add(:base, 'Discussion to reply to cannot be found')
           return note
@@ -34,19 +34,8 @@ module Notes
       if project
         project.notes.find_discussion(discussion_id)
       else
-        discussion = Note.find_discussion(discussion_id)
-        noteable = discussion.noteable
-
-        return nil unless noteable_without_project?(noteable)
-
-        discussion
+        Note.find_discussion(discussion_id)
       end
     end
-
-    def noteable_without_project?(noteable)
-      return true if noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
-
-      false
-    end
   end
 end
diff --git a/changelogs/unreleased/security-2779-fix-email-comment-permissions-check.yml b/changelogs/unreleased/security-2779-fix-email-comment-permissions-check.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2f76064d8a48252b13f47142b6d10b4d15abb861
--- /dev/null
+++ b/changelogs/unreleased/security-2779-fix-email-comment-permissions-check.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent unauthorized replies when discussion is locked or confidential
+merge_request:
+author:
+type: security
diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb
index ba9730d268589cce04208dfcbdb2ec5dcc5e0e8f..d8f4be8ada1c70e22acef14c35680a578b820300 100644
--- a/lib/gitlab/email/handler/reply_processing.rb
+++ b/lib/gitlab/email/handler/reply_processing.rb
@@ -56,7 +56,7 @@ module Gitlab
             raise ProjectNotFound unless author.can?(:read_project, project)
           end
 
-          raise UserNotAuthorizedError unless author.can?(permission, project || noteable)
+          raise UserNotAuthorizedError unless author.can?(permission, try(:noteable) || project)
         end
 
         def verify_record!(record:, invalid_exception:, record_name:)
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index b1f48c15c21bfb3f1d25911198b922232f4222e8..e5420ea6bea44dc852542b5bfb2f167399f7d3d7 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -118,6 +118,43 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
     end
   end
 
+  shared_examples "checks permissions on noteable" do
+    context "when user has access" do
+      before do
+        project.add_reporter(user)
+      end
+
+      it "creates a comment" do
+        expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+      end
+    end
+
+    context "when user does not have access" do
+      it "raises UserNotAuthorizedError" do
+        expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError)
+      end
+    end
+  end
+
+  context "when discussion is locked" do
+    before do
+      noteable.update_attribute(:discussion_locked, true)
+    end
+
+    it_behaves_like "checks permissions on noteable"
+  end
+
+  context "when issue is confidential" do
+    let(:issue) { create(:issue, project: project) }
+    let(:note) { create(:note, noteable: issue, project: project) }
+
+    before do
+      issue.update_attribute(:confidential, true)
+    end
+
+    it_behaves_like "checks permissions on noteable"
+  end
+
   context "when everything is fine" do
     before do
       setup_attachment
diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb
index 5ec04b99957080d317f8c0af7e1438e623f0044f..677613b798005d7d4fc1e28ed4e02a1b00faaf1c 100644
--- a/spec/models/sent_notification_spec.rb
+++ b/spec/models/sent_notification_spec.rb
@@ -48,7 +48,7 @@ describe SentNotification do
     let(:note) { create(:diff_note_on_merge_request) }
 
     it 'creates a new SentNotification' do
-      expect { described_class.record_note(note, user.id) }.to change { described_class.count }.by(1)
+      expect { described_class.record_note(note, note.author.id) }.to change { described_class.count }.by(1)
     end
   end
 
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 3809692b37357e0dfb2e285d394a7c0744ef8ab5..d7036cc4171c916d483d4eeeb375bed4dd5d77c8 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -14,6 +14,13 @@ describe PersonalSnippetPolicy do
     ]
   end
 
+  let(:comment_permissions) do
+    [
+      :comment_personal_snippet,
+      :create_note
+    ]
+  end
+
   def permissions(user)
     described_class.new(user, snippet)
   end
@@ -26,7 +33,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_allowed(:read_personal_snippet)
-        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*comment_permissions)
         is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -37,7 +44,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_allowed(:read_personal_snippet)
-        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*comment_permissions)
         is_expected.to be_allowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -48,7 +55,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_allowed(:read_personal_snippet)
-        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*comment_permissions)
         is_expected.to be_allowed(:award_emoji)
         is_expected.to be_allowed(*author_permissions)
       end
@@ -63,7 +70,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
-        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*comment_permissions)
         is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -74,7 +81,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_allowed(:read_personal_snippet)
-        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*comment_permissions)
         is_expected.to be_allowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -85,7 +92,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
-        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*comment_permissions)
         is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -96,7 +103,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_allowed(:read_personal_snippet)
-        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*comment_permissions)
         is_expected.to be_allowed(:award_emoji)
         is_expected.to be_allowed(*author_permissions)
       end
@@ -111,7 +118,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
-        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*comment_permissions)
         is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -122,7 +129,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
-        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*comment_permissions)
         is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -133,7 +140,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_disallowed(:read_personal_snippet)
-        is_expected.to be_disallowed(:comment_personal_snippet)
+        is_expected.to be_disallowed(*comment_permissions)
         is_expected.to be_disallowed(:award_emoji)
         is_expected.to be_disallowed(*author_permissions)
       end
@@ -144,7 +151,7 @@ describe PersonalSnippetPolicy do
 
       it do
         is_expected.to be_allowed(:read_personal_snippet)
-        is_expected.to be_allowed(:comment_personal_snippet)
+        is_expected.to be_allowed(*comment_permissions)
         is_expected.to be_allowed(:award_emoji)
         is_expected.to be_allowed(*author_permissions)
       end
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index 4d32e06b553d0c7204382ccc9266190efd536f0a..d6329e845799c0ff28681a22b035bb2108f9bbe3 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -41,7 +41,7 @@ describe ProjectSnippetPolicy do
       subject { abilities(regular_user, :public) }
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -50,7 +50,7 @@ describe ProjectSnippetPolicy do
       subject { abilities(external_user, :public) }
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -70,7 +70,7 @@ describe ProjectSnippetPolicy do
       subject { abilities(regular_user, :internal) }
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -79,7 +79,7 @@ describe ProjectSnippetPolicy do
       subject { abilities(external_user, :internal) }
 
       it do
-        expect_disallowed(:read_project_snippet)
+        expect_disallowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -92,7 +92,7 @@ describe ProjectSnippetPolicy do
       end
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -112,7 +112,7 @@ describe ProjectSnippetPolicy do
       subject { abilities(regular_user, :private) }
 
       it do
-        expect_disallowed(:read_project_snippet)
+        expect_disallowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -123,7 +123,7 @@ describe ProjectSnippetPolicy do
       subject { described_class.new(regular_user, snippet) }
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_allowed(*author_permissions)
       end
     end
@@ -136,7 +136,7 @@ describe ProjectSnippetPolicy do
       end
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -149,7 +149,7 @@ describe ProjectSnippetPolicy do
       end
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_disallowed(*author_permissions)
       end
     end
@@ -158,7 +158,7 @@ describe ProjectSnippetPolicy do
       subject { abilities(create(:admin), :private) }
 
       it do
-        expect_allowed(:read_project_snippet)
+        expect_allowed(:read_project_snippet, :create_note)
         expect_allowed(*author_permissions)
       end
     end
diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb
index ff85c261cd422c3f9337ff9d391f6e5f4ee91c04..9aaccb4bffe04b7cc9cc9fc51efa1221ca452dc1 100644
--- a/spec/services/notes/build_service_spec.rb
+++ b/spec/services/notes/build_service_spec.rb
@@ -45,6 +45,15 @@ describe Notes::BuildService do
         end
       end
 
+      context 'when user has no access to discussion' do
+        it 'sets an error' do
+          another_user = create(:user)
+          new_note = described_class.new(project, another_user, note: 'Test', in_reply_to_discussion_id: note.discussion_id).execute
+
+          expect(new_note.errors[:base]).to include('Discussion to reply to cannot be found')
+        end
+      end
+
       context 'personal snippet note' do
         def reply(note, user = nil)
           user ||= create(:user)
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 80b015d4cd0fbb8c015a23ddf3072d3f5bc82585..1b9ba42cfd6e3e67339ddeb5a75f604447a6bfca 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -127,6 +127,10 @@ describe Notes::CreateService do
         create(:diff_note_on_merge_request, noteable: merge_request, project: project_with_repo)
       end
 
+      before do
+        project_with_repo.add_maintainer(user)
+      end
+
       context 'when eligible to have a note diff file' do
         let(:new_opts) do
           opts.merge(in_reply_to_discussion_id: nil,