Documentation Help Center: Category-Level Permission Inheritance and Access Hardening
Executive summary
The in-app Help/Documentation center now lets a category's required permissions cascade to every page nested beneath it, so restricting a category automatically restricts its children. This release also closes a security gap where users could reach a restricted documentation page by typing its URL directly, and fixes a regression that wrongly blocked legitimate pages not shown in the sidebar. Reached the QA environment.
Why this was needed
Previously each documentation page enforced only its own permissions, and the page's direct-URL access check did not consult the permission-filtered sidebar tree. That meant a page sitting under a restricted category could still be opened by anyone who knew or guessed its link, and there was no clean way to lock down a whole category at once. Separately, a recent change caused pages that exist but are not listed in the sidebar to incorrectly return a 404 for authorized users.
Client / user impact
- Administrators can now secure an entire documentation category in one place; all pages under it inherit that requirement automatically (additive: a child needs the parent's permissions plus its own).
- Unauthorized users no longer see restricted categories or their pages in the sidebar, and can no longer bypass that by navigating to the page URL directly.
- Authorized users continue to see exactly what they are entitled to, including valid pages that are intentionally not listed in the sidebar.
- Search and normal page browsing behavior are unchanged for permitted content.
Technical scope
Changes are scoped to the mail app's documentation feature (apps/mail/src):
services/documentation.permissions.ts(+89): adds shared utilitiescalculateEffectivePermissions(deduped union of inherited + item + doc-page permissions, AND model),extractSidebarItemPermissions, andgetPermissionsToPassToChildren(passes inherited + item permissions down, but not page-specific permissions).services/documentation.server.services.ts:filterNavigationTreeByPermissions/filterSidebarItemByPermissionsnow thread aninheritedPermissionsaccumulator through the recursion and use the shared utilities.components/documentation/DocumentationSidebar.tsx: mirrors the same inheritance logic in the client-side safety-net filter.app/(dashboard)/help/[...slug]/page.tsx: the direct-URL access check now uses the permission-filtered navigation tree; if a page is filtered out it returns notFound, while truly unlisted pages fall back to checking their own doc-page permissions. Adds explicit fail-closed handling: if either navigation-tree fetch errors, access is denied rather than falling through.
Risk & mitigation
Permission logic is security-sensitive and now governs both sidebar visibility and direct-URL access, so a logic error could either over-expose or over-restrict content. The additive inheritance model and fail-closed-on-fetch-error behavior bias toward denying access, which is the safer default but could hide legitimate content if a navigation-tree fetch transiently fails. Mitigation: server-side filtering remains the primary enforcement with the client filter as a backup, the shared utilities are deduplicated and unit-friendly, and changes are confined to the documentation feature with no backend/API contract changes.
QA validation focus
- Set a permission on a category and confirm the entire category plus all child pages are hidden for an unauthorized user, and fully visible for an authorized user.
- Verify nested categories accumulate permissions correctly (a child requires parent + its own).
- Attempt direct-URL access to a restricted page as an unauthorized user and confirm it returns not-found (no bypass).
- Confirm individual pages still honor their own doc-page permissions.
- Confirm an unlisted page (not present in the sidebar) is accessible to a user who holds its required permissions, and blocked for one who does not.
- Confirm documentation search results render correctly and do not surface restricted items.