diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 097caf67a6550ced012111db689f13e66902544b..e07fdb702fc7c7d18cc39103f59df99e7947214c 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -41,6 +41,7 @@ module Gitlab
     autoload :IssueReferenceFilter,         'gitlab/markdown/issue_reference_filter'
     autoload :LabelReferenceFilter,         'gitlab/markdown/label_reference_filter'
     autoload :MergeRequestReferenceFilter,  'gitlab/markdown/merge_request_reference_filter'
+    autoload :RedactorFilter,               'gitlab/markdown/redactor_filter'
     autoload :RelativeLinkFilter,           'gitlab/markdown/relative_link_filter'
     autoload :SanitizationFilter,           'gitlab/markdown/sanitization_filter'
     autoload :SnippetReferenceFilter,       'gitlab/markdown/snippet_reference_filter'
diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9faee4567aef12c2ef03b3dc21e2ce9e9c955d28
--- /dev/null
+++ b/lib/gitlab/markdown/redactor_filter.rb
@@ -0,0 +1,66 @@
+require 'gitlab/markdown'
+require 'html/pipeline/filter'
+
+module Gitlab
+  module Markdown
+    # HTML filter that removes references to records that the current user does
+    # not have permission to view.
+    #
+    # Expected to be run in its own post-processing pipeline.
+    #
+    class RedactorFilter < HTML::Pipeline::Filter
+      def call
+        doc.css('a.gfm').each do |node|
+          unless user_can_reference?(node)
+            node.replace(node.text)
+          end
+        end
+
+        doc
+      end
+
+      def user_can_reference?(node)
+        if node.has_attribute?('data-group-id')
+          user_can_reference_group?(node.attr('data-group-id'))
+        elsif node.has_attribute?('data-project-id')
+          user_can_reference_project?(node.attr('data-project-id'))
+        elsif node.has_attribute?('data-user-id')
+          user_can_reference_user?(node.attr('data-user-id'))
+        else
+          false
+        end
+      end
+
+      def user_can_reference_group?(id)
+        group = Group.find(id)
+
+        group && can?(:read_group, group)
+      end
+
+      def user_can_reference_project?(id)
+        project = Project.find(id)
+
+        project && can?(:read_project, project)
+      end
+
+      def user_can_reference_user?(id)
+        # Permit all user reference links
+        true
+      end
+
+      private
+
+      def abilities
+        Ability.abilities
+      end
+
+      def can?(ability, object)
+        abilities.allowed?(current_user, ability, object)
+      end
+
+      def current_user
+        context[:current_user]
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/markdown/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a6cf3c64236268869271fb10129b00ec236afdb3
--- /dev/null
+++ b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+  describe RedactorFilter do
+    include ActionView::Helpers::UrlHelper
+    include FilterSpecHelper
+
+    it 'ignores non-GFM links' do
+      html = %(See <a href="https://google.com/">Google</a>)
+      doc = filter(html, current_user: double)
+
+      expect(doc.css('a').length).to eq 1
+    end
+
+    def reference_link(data)
+      link_to('text', '', class: 'gfm', data: data)
+    end
+
+    context 'with data-group-id' do
+      it 'removes unpermitted Group references' do
+        user = create(:user)
+        group = create(:group)
+
+        link = reference_link(group_id: group.id)
+        doc = filter(link, current_user: user)
+
+        expect(doc.css('a').length).to eq 0
+      end
+
+      it 'allows permitted Group references' do
+        user = create(:user)
+        group = create(:group)
+        group.add_developer(user)
+
+        link = reference_link(group_id: group.id)
+        doc = filter(link, current_user: user)
+
+        expect(doc.css('a').length).to eq 1
+      end
+    end
+
+    context 'with data-project-id' do
+      it 'removes unpermitted Project references' do
+        user = create(:user)
+        project = create(:empty_project)
+
+        link = reference_link(project_id: project.id)
+        doc = filter(link, current_user: user)
+
+        expect(doc.css('a').length).to eq 0
+      end
+
+      it 'allows permitted Project references' do
+        user = create(:user)
+        project = create(:empty_project)
+        project.team << [user, :master]
+
+        link = reference_link(project_id: project.id)
+        doc = filter(link, current_user: user)
+
+        expect(doc.css('a').length).to eq 1
+      end
+    end
+
+    context 'with data-user-id' do
+      it 'allows any User reference' do
+        user = create(:user)
+
+        link = reference_link(user_id: user.id)
+        doc = filter(link)
+
+        expect(doc.css('a').length).to eq 1
+      end
+    end
+  end
+end