diff --git a/trac/versioncontrol/svn_authz.py b/trac/versioncontrol/svn_authz.py
--- a/trac/versioncontrol/svn_authz.py
+++ b/trac/versioncontrol/svn_authz.py
@@ -30,12 +30,6 @@
 
 
 def parent_iter(path):
-    path = path.strip('/')
-    if path:
-        path = '/' + path + '/'
-    else:
-        path = '/'
-
     while 1:
         yield path
         if path == '/':
@@ -141,32 +135,48 @@
     # IPermissionPolicy methods
 
     def check_permission(self, action, username, resource, perm):
+        if username == 'anonymous':
+            usernames = ('$anonymous', '*')
+        else:
+            usernames = (username, '$authenticated', '*')
         if action == 'FILE_VIEW' or action == 'BROWSER_VIEW':
             authz, users = self._get_authz_info()
             if authz is None:
                 return False
             if resource is None:
-                return users is True or username in users
+                return bool(users & set(usernames))
             if resource.realm == 'source':
                 modules = [resource.parent.id or self.authz_module_name]
                 if modules[0]:
                     modules.append('')
-                for p in parent_iter(resource.id):
+                path = '/' + resource.id.strip('/')
+                if path != '/':
+                    path += '/'
+                
+                # Walk from resource up parent directories
+                for spath in parent_iter(path):
                     for module in modules:
-                        section = authz.get(module, {}).get(p, {})
-                        result = section.get(username)
-                        if result is not None:
-                            return result
-                        result = section.get('*')
-                        if result is not None:
-                            return result
-                return False
+                        section = authz.get(module, {}).get(spath)
+                        if section:
+                            for user in usernames:
+                                result = section.get(user)
+                                if result is not None:
+                                    return result
+                
+                # Allow access to parent directories of allowed resources
+                if any(section.get(user) is True
+                       for module in modules
+                       for spath, section in authz.get(module, {}).iteritems()
+                       if spath.startswith(path)
+                       for user in usernames):
+                    return True
+
         elif action == 'CHANGESET_VIEW':
             authz, users = self._get_authz_info()
             if authz is None:
                 return False
             if resource is None:
-                return users is True or username in users
+                return bool(users & set(usernames))
             if resource.realm == 'changeset':
                 rm = RepositoryManager(self.env)
                 repos = rm.get_repository(resource.parent.id)
@@ -193,10 +203,10 @@
             self.log.info('Parsing authz file: %s' % self.authz_file)
             try:
                 self._authz = parse(read_file(self.authz_file))
-                users = set(user for module in self._authz.itervalues()
-                            for path in module.itervalues()
-                            for user, result in path.iteritems() if result)
-                self._users = '*' in users or users
+                self._users = set(user for module in self._authz.itervalues()
+                                  for path in module.itervalues()
+                                  for user, result in path.iteritems()
+                                  if result)
             except Exception, e:
                 self._authz = None
                 self._users = set()
diff --git a/trac/versioncontrol/tests/svn_authz.py b/trac/versioncontrol/tests/svn_authz.py
--- a/trac/versioncontrol/tests/svn_authz.py
+++ b/trac/versioncontrol/tests/svn_authz.py
@@ -148,6 +148,12 @@
 [/wildcard]
 * = r
 
+# Special tokens
+[/special/anonymous]
+$anonymous = r
+[/special/authenticated]
+$authenticated = r
+
 # Groups
 [/groups_a]
 @group1 = r
@@ -198,9 +204,9 @@
         self.assertEqual(result, check)
         
     def test_default_permission(self):
-        # By default, no permission is granted
-        self.assertPermission(False, 'joe', '', '/not_defined')
-        self.assertPermission(False, 'jane', 'repo', '/not/defined/either')
+        # By default, permissions are undecided
+        self.assertPermission(None, 'joe', '', '/not_defined')
+        self.assertPermission(None, 'jane', 'repo', '/not/defined/either')
 
     def test_read_write(self):
         # Allow 'r' and 'rw' entries, deny 'w' and empty entries
@@ -225,7 +231,7 @@
     def test_module_usage(self):
         # If a module name is specified, the rules are specific to the module
         self.assertPermission(True, 'user', 'module', '/module_a')
-        self.assertPermission(False, 'user', 'module', '/module_b')
+        self.assertPermission(None, 'user', 'module', '/module_b')
         # If a module is specified, but the configuration contains a non-module
         # path, the non-module path can still apply
         self.assertPermission(True, 'user', 'module', '/module_c')
@@ -233,10 +239,20 @@
         self.assertPermission(False, 'user', 'module', '/module_d')
 
     def test_wildcard(self):
-        # The * wildcard matches all users
+        # The * wildcard matches all users, including anonymous
+        self.assertPermission(True, 'anonymous', '', '/wildcard')
         self.assertPermission(True, 'joe', '', '/wildcard')
         self.assertPermission(True, 'jane', '', '/wildcard')
 
+    def test_special_tokens(self):
+        # The $anonymous token matches only anonymous users
+        self.assertPermission(True, 'anonymous', '', '/special/anonymous')
+        self.assertPermission(None, 'user', '', '/special/anonymous')
+        # The $authenticated token matches all authenticated users
+        self.assertPermission(None, 'anonymous', '', '/special/authenticated')
+        self.assertPermission(True, 'joe', '', '/special/authenticated')
+        self.assertPermission(True, 'jane', '', '/special/authenticated')
+
     def test_groups(self):
         # Groups are specified in a separate section and used with an @ prefix
         self.assertPermission(True, 'user', '', '/groups_a')
diff --git a/trac/versioncontrol/web_ui/browser.py b/trac/versioncontrol/web_ui/browser.py
--- a/trac/versioncontrol/web_ui/browser.py
+++ b/trac/versioncontrol/web_ui/browser.py
@@ -26,7 +26,7 @@
 from trac.mimeview.api import Mimeview, is_binary, \
                               IHTMLPreviewAnnotator, Context
 from trac.perm import IPermissionRequestor
-from trac.resource import ResourceNotFound
+from trac.resource import Resource, ResourceNotFound
 from trac.util import embedded_numbers
 from trac.util.compat import any
 from trac.util.datefmt import http_date, utc
@@ -505,6 +505,10 @@
             except TracError, err:
                 entry = (reponame, repoinfo, None, None,
                          exception_to_unicode(err))
+            if entry[-1] is not None:   # Check permission in case of error
+                root = Resource('repository', reponame).child('source', '/')
+                if 'BROWSER_VIEW' not in context.perm(root):
+                    continue
             repositories.append(entry)
 
         # Ordering of repositories

