diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php
--- a/src/applications/phriction/controller/PhrictionDocumentController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentController.php
@@ -1,8 +1,5 @@
 <?php
 
-/**
- * @group phriction
- */
 final class PhrictionDocumentController
   extends PhrictionController {
 
@@ -118,7 +115,7 @@
         $new_doc = id(new PhrictionDocumentQuery())
           ->setViewer($user)
           ->withIDs(array($new_doc_id))
-          ->exectueOne();
+          ->executeOne();
 
         $slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug());
 
diff --git a/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php b/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php
--- a/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php
+++ b/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php
@@ -25,8 +25,8 @@
     array $phids) {
 
     return id(new PhrictionDocumentQuery())
-      ->needContent(true)
-      ->withPHIDs($phids);
+      ->withPHIDs($phids)
+      ->needContent(true);
   }
 
   public function loadHandles(
diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php
--- a/src/applications/phriction/query/PhrictionDocumentQuery.php
+++ b/src/applications/phriction/query/PhrictionDocumentQuery.php
@@ -49,21 +49,97 @@
   }
 
   protected function loadPage() {
-    $document = new PhrictionDocument();
-    $conn_r = $document->establishConnection('r');
+    $table = new PhrictionDocument();
+    $conn_r = $table->establishConnection('r');
 
     $rows = queryfx_all(
       $conn_r,
       'SELECT * FROM %T %Q %Q %Q',
-      $document->getTableName(),
+      $table->getTableName(),
       $this->buildWhereClause($conn_r),
       $this->buildOrderClause($conn_r),
       $this->buildLimitClause($conn_r));
 
-    return $document->loadAllFromArray($rows);
+    $documents = $table->loadAllFromArray($rows);
+
+    if ($documents) {
+      $ancestor_slugs = array();
+      foreach ($documents as $key => $document) {
+        $document_slug = $document->getSlug();
+        foreach (PhabricatorSlug::getAncestry($document_slug) as $ancestor) {
+          $ancestor_slugs[$ancestor][] = $key;
+        }
+      }
+
+      if ($ancestor_slugs) {
+        $ancestors = queryfx_all(
+          $conn_r,
+          'SELECT * FROM %T WHERE slug IN (%Ls)',
+          $document->getTableName(),
+          array_keys($ancestor_slugs));
+        $ancestors = $table->loadAllFromArray($ancestors);
+        $ancestors = mpull($ancestors, null, 'getSlug');
+
+        foreach ($ancestor_slugs as $ancestor_slug => $document_keys) {
+          $ancestor = idx($ancestors, $ancestor_slug);
+          foreach ($document_keys as $document_key) {
+            $documents[$document_key]->attachAncestor(
+              $ancestor_slug,
+              $ancestor);
+          }
+        }
+      }
+    }
+
+    return $documents;
   }
 
   protected function willFilterPage(array $documents) {
+    // To view a Phriction document, you must also be able to view all of the
+    // ancestor documents. Filter out documents which have ancestors that are
+    // not visible.
+
+    $document_map = array();
+    foreach ($documents as $document) {
+      $document_map[$document->getSlug()] = $document;
+      foreach ($document->getAncestors() as $key => $ancestor) {
+        if ($ancestor) {
+          $document_map[$key] = $ancestor;
+        }
+      }
+    }
+
+    $filtered_map = $this->applyPolicyFilter(
+      $document_map,
+      array(PhabricatorPolicyCapability::CAN_VIEW));
+
+    // Filter all of the documents where a parent is not visible.
+    foreach ($documents as $document_key => $document) {
+      // If the document itself is not visible, filter it.
+      if (!isset($filtered_map[$document->getSlug()])) {
+        $this->didRejectResult($documents[$document_key]);
+        unset($documents[$document_key]);
+        continue;
+      }
+
+      // If an ancestor exists but is not visible, filter the document.
+      foreach ($document->getAncestors() as $ancestor_key => $ancestor) {
+        if (!$ancestor) {
+          continue;
+        }
+
+        if (!isset($filtered_map[$ancestor_key])) {
+          $this->didRejectResult($documents[$document_key]);
+          unset($documents[$document_key]);
+          break;
+        }
+      }
+    }
+
+    if (!$documents) {
+      return $documents;
+    }
+
     if ($this->needContent) {
       $contents = id(new PhrictionContent())->loadAllWhere(
         'id IN (%Ld)',
diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php
--- a/src/applications/phriction/storage/PhrictionDocument.php
+++ b/src/applications/phriction/storage/PhrictionDocument.php
@@ -1,8 +1,5 @@
 <?php
 
-/**
- * @group phriction
- */
 final class PhrictionDocument extends PhrictionDAO
   implements
     PhabricatorPolicyInterface,
@@ -16,6 +13,7 @@
   protected $status;
 
   private $contentObject = self::ATTACHABLE;
+  private $ancestors = array();
 
   // TODO: This should be `self::ATTACHABLE`, but there are still a lot of call
   // sites which load PhrictionDocuments directly.
@@ -84,6 +82,19 @@
     return (bool)$this->getProject();
   }
 
+  public function getAncestors() {
+    return $this->ancestors;
+  }
+
+  public function getAncestor($slug) {
+    return $this->assertAttachedKey($this->ancestors, $slug);
+  }
+
+  public function attachAncestor($slug, $ancestor) {
+    $this->ancestors[$slug] = $ancestor;
+    return $this;
+  }
+
   public static function isProjectSlug($slug) {
     $slug = PhabricatorSlug::normalize($slug);
     $prefix = 'projects/';
@@ -119,6 +130,7 @@
     if ($this->hasProject()) {
       return $this->getProject()->getPolicy($capability);
     }
+
     return PhabricatorPolicies::POLICY_USER;
   }
 
@@ -134,6 +146,14 @@
       return pht(
         "This is a project wiki page, and inherits the project's policies.");
     }
+
+    switch ($capability) {
+      case PhabricatorPolicyCapability::CAN_VIEW:
+        return pht(
+          'To view a wiki document, you must also be able to view all '.
+          'of its parents.');
+    }
+
     return null;
   }
 
diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
--- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
+++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
@@ -302,19 +302,32 @@
   private function getPolicyFilter() {
     $filter = new PhabricatorPolicyFilter();
     $filter->setViewer($this->viewer);
-    if (!$this->capabilities) {
-      $capabilities = array(
-        PhabricatorPolicyCapability::CAN_VIEW,
-      );
-    } else {
-      $capabilities = $this->capabilities;
-    }
+    $capabilities = $this->getRequiredCapabilities();
     $filter->requireCapabilities($capabilities);
     $filter->raisePolicyExceptions($this->shouldRaisePolicyExceptions());
 
     return $filter;
   }
 
+  protected function getRequiredCapabilities() {
+    if ($this->capabilities) {
+      return $this->capabilities;
+    }
+
+    return array(
+      PhabricatorPolicyCapability::CAN_VIEW,
+    );
+  }
+
+  protected function applyPolicyFilter(array $objects, array $capabilities) {
+    if ($this->shouldDisablePolicyFiltering()) {
+      return $objects;
+    }
+    $filter = $this->getPolicyFilter();
+    $filter->requireCapabilities($capabilities);
+    return $filter->apply($objects);
+  }
+
   protected function didRejectResult(PhabricatorPolicyInterface $object) {
     $this->getPolicyFilter()->rejectObject(
       $object,