diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py
--- a/rhodecode/apps/_base/__init__.py
+++ b/rhodecode/apps/_base/__init__.py
@@ -22,9 +22,9 @@ import time
 import logging
 import operator
 
-from pyramid.httpexceptions import HTTPFound
+from pyramid.httpexceptions import HTTPFound, HTTPForbidden
 
-from rhodecode.lib import helpers as h
+from rhodecode.lib import helpers as h, diffs
 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
 from rhodecode.model import repo
@@ -208,10 +208,14 @@ class RepoAppView(BaseAppView):
         c.repository_requirements_missing = False
         try:
             self.rhodecode_vcs_repo = self.db_repo.scm_instance()
+            self.path_filter = PathFilter(self.rhodecode_vcs_repo.get_path_permissions(c.auth_user.username))
         except RepositoryRequirementError as e:
             c.repository_requirements_missing = True
             self._handle_missing_requirements(e)
             self.rhodecode_vcs_repo = None
+            self.path_filter = None
+
+        c.path_filter = self.path_filter # used by atom_feed_entry.mako
 
         if (not c.repository_requirements_missing
             and self.rhodecode_vcs_repo is None):
@@ -229,9 +233,57 @@ class RepoAppView(BaseAppView):
         f_path = matchdict.get('f_path')
         if f_path:
             # fix for multiple initial slashes that causes errors for GIT
-            return f_path.lstrip('/')
+            return self.path_filter.assert_path_permissions(f_path.lstrip('/'))
+
+        return self.path_filter.assert_path_permissions(default)
+
+
+class PathFilter(object):
+
+    # Expects and instance of BasePathPermissionChecker or None
+    def __init__(self, permission_checker):
+        self.permission_checker = permission_checker
+
+    def assert_path_permissions(self, path):
+        if path and self.permission_checker and not self.permission_checker.has_access(path):
+            raise HTTPForbidden()
+        return path
 
-        return default
+    def filter_patchset(self, patchset):
+        if not self.permission_checker or not patchset:
+            return patchset, False
+        had_filtered = False
+        filtered_patchset = []
+        for patch in patchset:
+            filename = patch.get('filename', None)
+            if not filename or self.permission_checker.has_access(filename):
+                filtered_patchset.append(patch)
+            else:
+                had_filtered = True
+        if had_filtered:
+            if isinstance(patchset, diffs.LimitedDiffContainer):
+                filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
+            return filtered_patchset, True
+        else:
+            return patchset, False
+
+    def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
+        filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
+        result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
+        result.has_hidden_changes = has_hidden_changes
+        return result
+
+    def get_raw_patch(self, diff_processor):
+        if self.permission_checker is None:
+            return diff_processor.as_raw()
+        elif self.permission_checker.has_full_access:
+            return diff_processor.as_raw()
+        else:
+            return '# Repository has user-specific filters, raw patch generation is disabled.'
+
+    @property
+    def is_enabled(self):
+        return self.permission_checker is not None
 
 
 class RepoGroupAppView(BaseAppView):
diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py
--- a/rhodecode/apps/repository/views/repo_commits.py
+++ b/rhodecode/apps/repository/views/repo_commits.py
@@ -272,13 +272,13 @@ class RepoCommitsView(RepoAppView):
                     source_node_getter=_node_getter(commit1),
                     target_node_getter=_node_getter(commit2),
                     comments=inline_comments)
-                diffset = diffset.render_patchset(
-                    _parsed, commit1.raw_id, commit2.raw_id)
+                diffset = self.path_filter.render_patchset_filtered(
+                    diffset, _parsed, commit1.raw_id, commit2.raw_id)
 
                 c.changes[commit.raw_id] = diffset
             else:
                 # downloads/raw we only need RAW diff nothing else
-                diff = diff_processor.as_raw()
+                diff = self.path_filter.get_raw_patch(diff_processor)
                 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
 
         # sort comments by how they were generated
diff --git a/rhodecode/apps/repository/views/repo_compare.py b/rhodecode/apps/repository/views/repo_compare.py
--- a/rhodecode/apps/repository/views/repo_compare.py
+++ b/rhodecode/apps/repository/views/repo_compare.py
@@ -309,8 +309,8 @@ class RepoCompareView(RepoAppView):
             source_node_getter=_node_getter(source_commit),
             target_node_getter=_node_getter(target_commit),
         )
-        c.diffset = diffset.render_patchset(
-            _parsed, source_ref, target_ref)
+        c.diffset = self.path_filter.render_patchset_filtered(
+            diffset, _parsed, source_ref, target_ref)
 
         c.preview_mode = merge
         c.source_commit = source_commit
diff --git a/rhodecode/apps/repository/views/repo_feed.py b/rhodecode/apps/repository/views/repo_feed.py
--- a/rhodecode/apps/repository/views/repo_feed.py
+++ b/rhodecode/apps/repository/views/repo_feed.py
@@ -90,13 +90,15 @@ class RepoFeedView(RepoAppView):
         _renderer = self.request.get_partial_renderer(
             'rhodecode:templates/feed/atom_feed_entry.mako')
         diff_processor, parsed_diff, limited_diff = self._changes(commit)
+        filtered_parsed_diff, has_hidden_changes = self.path_filter.filter_patchset(parsed_diff)
         return _renderer(
             'body',
             commit=commit,
-            parsed_diff=parsed_diff,
+            parsed_diff=filtered_parsed_diff,
             limited_diff=limited_diff,
             feed_include_diff=self.feed_include_diff,
             diff_processor=diff_processor,
+            has_hidden_changes=has_hidden_changes
         )
 
     def _set_timezone(self, date, tzinfo=pytz.utc):
@@ -122,8 +124,7 @@ class RepoFeedView(RepoAppView):
         """
         self.load_default_context()
 
-        @cache_region('long_term')
-        def _generate_feed(cache_key):
+        def _generate_feed():
             feed = Atom1Feed(
                 title=self.title % self.db_repo_name,
                 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
@@ -146,12 +147,18 @@ class RepoFeedView(RepoAppView):
 
             return feed.mime_type, feed.writeString('utf-8')
 
-        invalidator_context = CacheKey.repo_context_cache(
-            _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
+        @cache_region('long_term')
+        def _generate_feed_and_cache(cache_key):
+            return _generate_feed()
 
-        with invalidator_context as context:
-            context.invalidate()
-            mime_type, feed = context.compute()
+        if self.path_filter.is_enabled:
+            invalidator_context = CacheKey.repo_context_cache(
+                _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
+            with invalidator_context as context:
+                context.invalidate()
+                mime_type, feed = context.compute()
+        else:
+            mime_type, feed = _generate_feed()
 
         response = Response(feed)
         response.content_type = mime_type
@@ -169,8 +176,7 @@ class RepoFeedView(RepoAppView):
         """
         self.load_default_context()
 
-        @cache_region('long_term')
-        def _generate_feed(cache_key):
+        def _generate_feed():
             feed = Rss201rev2Feed(
                 title=self.title % self.db_repo_name,
                 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
@@ -193,12 +199,19 @@ class RepoFeedView(RepoAppView):
 
             return feed.mime_type, feed.writeString('utf-8')
 
-        invalidator_context = CacheKey.repo_context_cache(
-            _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
+        @cache_region('long_term')
+        def _generate_feed_and_cache(cache_key):
+            return _generate_feed()
 
-        with invalidator_context as context:
-            context.invalidate()
-            mime_type, feed = context.compute()
+        if self.path_filter.is_enabled:
+            invalidator_context = CacheKey.repo_context_cache(
+                _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
+
+            with invalidator_context as context:
+                context.invalidate()
+                mime_type, feed = context.compute()
+        else:
+            mime_type, feed = _generate_feed()
 
         response = Response(feed)
         response.content_type = mime_type
diff --git a/rhodecode/apps/repository/views/repo_files.py b/rhodecode/apps/repository/views/repo_files.py
--- a/rhodecode/apps/repository/views/repo_files.py
+++ b/rhodecode/apps/repository/views/repo_files.py
@@ -426,7 +426,7 @@ class RepoFilesView(RepoAppView):
                                       context=line_context)
             diff = diffs.DiffProcessor(_diff, format='gitdiff')
 
-            response = Response(diff.as_raw())
+            response = Response(self.path_filter.get_raw_patch(diff))
             response.content_type = 'text/plain'
             response.content_disposition = (
                 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
@@ -442,7 +442,7 @@ class RepoFilesView(RepoAppView):
                                       context=line_context)
             diff = diffs.DiffProcessor(_diff, format='gitdiff')
 
-            response = Response(diff.as_raw())
+            response = Response(self.path_filter.get_raw_patch(diff))
             response.content_type = 'text/plain'
             charset = self._get_default_encoding(c)
             if charset:
diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py
--- a/rhodecode/apps/repository/views/repo_pull_requests.py
+++ b/rhodecode/apps/repository/views/repo_pull_requests.py
@@ -231,8 +231,8 @@ class RepoPullRequestsView(RepoAppView, 
             target_node_getter=_node_getter(source_commit),
             comments=display_inline_comments
         )
-        diffset = diffset.render_patchset(
-            _parsed, target_commit.raw_id, source_commit.raw_id)
+        diffset = self.path_filter.render_patchset_filtered(
+            diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
 
         return diffset
 
diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py
--- a/rhodecode/lib/vcs/backends/base.py
+++ b/rhodecode/lib/vcs/backends/base.py
@@ -637,6 +637,17 @@ class BaseRepository(object):
         warnings.warn("Use in_memory_commit instead", DeprecationWarning)
         return self.in_memory_commit
 
+    #
+    def get_path_permissions(self, username):
+        """
+
+        Returns a path permission checker or None if not supported
+
+        :param username: session user name
+        :return: an instance of BasePathPermissionChecker or None
+        """
+        return None
+
 
 class BaseCommit(object):
     """
@@ -1618,3 +1629,13 @@ class DiffChunk(object):
         self.header = match.groupdict()
         self.diff = chunk[match.end():]
         self.raw = chunk
+
+
+class BasePathPermissionChecker(object):
+
+    def __init__(self, username, has_full_access = False):
+        self.username = username
+        self.has_full_access = has_full_access
+
+    def has_access(self, path):
+        raise NotImplemented()
diff --git a/rhodecode/templates/codeblocks/diffs.mako b/rhodecode/templates/codeblocks/diffs.mako
--- a/rhodecode/templates/codeblocks/diffs.mako
+++ b/rhodecode/templates/codeblocks/diffs.mako
@@ -132,7 +132,9 @@ collapse_all = len(diffset.files) > coll
         </h2>
     </div>
 
-    %if not diffset.files:
+    %if diffset.has_hidden_changes:
+        <p class="empty_data">${_('Some changes may be hidden')}</p>
+    %elif not diffset.files:
         <p class="empty_data">${_('No files')}</p>
     %endif
 
diff --git a/rhodecode/templates/feed/atom_feed_entry.mako b/rhodecode/templates/feed/atom_feed_entry.mako
--- a/rhodecode/templates/feed/atom_feed_entry.mako
+++ b/rhodecode/templates/feed/atom_feed_entry.mako
@@ -17,6 +17,10 @@
     tag: ${tag} <br/>
 % endfor
 
+% if has_hidden_changes:
+    Has hidden changes<br/>
+% endif
+
 commit: <a href="${h.route_url('repo_commit', repo_name=c.rhodecode_db_repo.repo_name, commit_id=commit.raw_id)}">${h.show_id(commit)}</a>
 <pre>
 ${h.urlify_commit_message(commit.message)}
@@ -29,6 +33,6 @@ commit: <a href="${h.route_url('repo_com
 % endfor
 
 % if feed_include_diff:
-${diff_processor.as_raw()}
+${c.path_filter.get_raw_patch(diff_processor)}
 % endif
 </pre>