Security Advisory: Admin Panel Server-Side RBAC Not Enforced (v1.0.6)
Security Advisory: Admin Panel Server-Side RBAC Not Enforced
Version affected: ≤ 1.0.6
Severity: 🔴 Critical
Status: Disclosure & Remediation Guidance
Affected file: src/app/dashboard/admin/page.tsx
What Was Found
During a review of the v1.0.6 release, a missing access-control enforcement was identified on the Admin Panel route (/dashboard/admin).
The page guard was implemented as follows:
// src/app/dashboard/admin/page.tsx
if (!session?.user) {
redirect('/login');
}
// In production, check RBAC role here via lib/auth.ts
// TODO: enforce owner/admin role
The conditional block only checks whether a session exists — i.e., whether the user is logged in — and contains a commented-out, never-implemented TODO for the actual role check. As a result, any authenticated user can access the Admin Panel regardless of their organisation role.
What Is Exposed
Authenticated users with no elevated privileges can:
- Access the full Admin Panel UI at
/dashboard/admin - Trigger and manage batch job controls
- View system monitoring dashboards and data
This is a horizontal privilege escalation issue: a standard user (e.g. a regular landlord account) has the same effective access to the admin surface as a designated owner or admin.
Root Cause
The role check was deferred with a TODO comment referencing lib/auth.ts but was never completed before the route was shipped. The client-side application may render role-gated UI elements correctly (e.g. hiding the admin navigation link from non-admin users), but this does not protect the route itself — any user who knows or guesses the URL can navigate there directly.
Recommended Fix
The following logic should be added server-side in src/app/dashboard/admin/page.tsx, replacing the existing incomplete guard:
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { getUserOrgMembership } from '@/lib/org';
import { redirect } from 'next/navigation';
export default async function AdminPage() {
const session = await getServerSession(authOptions);
if (!session?.user) {
redirect('/login');
}
const membership = await getUserOrgMembership(session.user.id);
if (!membership || !['owner', 'admin'].includes(membership.role)) {
redirect('/dashboard');
}
// Safe to render admin content below
...
}
Key requirements
| Requirement | Detail |
|---|---|
| Server-side only | This check must run in a Next.js Server Component or getServerSideProps — never rely solely on client-side guards. |
| Fetch fresh membership | Do not trust role data stored in the session token alone; fetch the current membership record from the database. |
| Redirect, don't 404 | Redirecting to /dashboard avoids leaking whether the route exists while providing a safe fallback. |
| Accepted roles | Only owner and admin org roles should be permitted access. |
Interim Mitigation
If you cannot deploy the fix immediately, consider adding a Next.js middleware rule to restrict the /dashboard/admin path:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/dashboard/admin')) {
// Block access entirely until server-side RBAC is implemented
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
export const config = {
matcher: ['/dashboard/admin/:path*'],
};
This will block all users from the admin route until the proper fix is deployed. It is a blunt instrument — use it only as a temporary stopgap.
Why Client-Side Guards Are Not Sufficient
It may appear that role-gating the navigation link (e.g. only rendering an "Admin" menu item for admins) is enough. It is not. Client-side RoleGate components:
- Can be bypassed by typing the URL directly into the browser.
- Can be bypassed by users who disable or intercept JavaScript.
- Rely on role data in the client session, which may be stale.
Server-side enforcement is the only reliable access-control boundary for a Next.js App Router application.
Disclosure Timeline
| Date | Event |
|---|---|
| v1.0.6 release | Vulnerability identified and disclosed in release notes |
| Immediate | Remediation guidance published (this document) |
This advisory applies to all instances of the application running versions up to and including 1.0.6 that have not applied the server-side RBAC fix described above.