All Docs
FeaturesCSI Teachable Replacement AppUpdated March 13, 2026

Security Notice: Admin Panel RBAC Gate Disabled in v1.0.25

Security Notice: Admin Panel RBAC Gate Disabled in v1.0.25

Severity: High
Affected versions: All versions up to and including v1.0.25
Affected route: /dashboard/admin
Affected file: src/app/dashboard/admin/page.tsx


What Happened

During a code review for v1.0.25, it was identified that the role-based access control (RBAC) check on the admin panel page has been left commented out. The intended guard — which restricts access to users with the owner or admin role — is not being executed:

// const member = await getCurrentOrgMember(orgId);
// if (!member || !["owner", "admin"].includes(member.role)) redirect("/dashboard");

As a result, any authenticated user in your organisation can navigate to /dashboard/admin and view the batch jobs panel and system statistics, regardless of their assigned role.

Who Is Affected

This affects all multi-tenant deployments where:

  • Users with roles other than owner or admin (e.g. standard members, learners) have active accounts.
  • The admin panel exposes sensitive operational data such as batch import job status or platform-wide system stats.

Risk

In a B2B multi-tenant SaaS context, the admin panel is intended exclusively for organisation owners and administrators. Without the RBAC gate:

  • Regular members can view batch job queues and system statistics they should not have access to.
  • There is a potential for information disclosure across tenant operational boundaries.
  • No write-access or destructive actions have been confirmed as exposed, but the surface area should be treated as sensitive.

Recommended Remediation

Uncomment and update the RBAC check in src/app/dashboard/admin/page.tsx. The x-org-id cookie is already set by the middleware layer, so the fix reads the organisation context from cookies, queries the orgMembers table, and redirects non-admins:

import { cookies } from 'next/headers';
import { db } from '@/lib/db';
import { orgMembers } from '@/lib/db/schema';
import { and, eq } from 'drizzle-orm';
import { redirect } from 'next/navigation';

// Inside your async page component, after session validation:
const orgId = (await cookies()).get('x-org-id')?.value;

if (orgId) {
  const [m] = await db
    .select()
    .from(orgMembers)
    .where(
      and(
        eq(orgMembers.orgId, orgId),
        eq(orgMembers.userId, session.user.id)
      )
    );

  if (!m || !['owner', 'admin'].includes(m.role)) {
    redirect('/dashboard');
  }
}

What This Fix Does

  1. Reads orgId from the x-org-id cookie, which is reliably set by the existing middleware on every authenticated request.
  2. Queries orgMembers to find the current user's membership record for that organisation.
  3. Checks the role — if the user is not an owner or admin, or if no membership record is found, they are immediately redirected to /dashboard.
  4. Allows access only for confirmed owner and admin role holders.

Verification

After applying the fix, verify the following:

  • A user with role member or learner is redirected to /dashboard when attempting to access /dashboard/admin.
  • A user with role owner can access /dashboard/admin without redirection.
  • A user with role admin can access /dashboard/admin without redirection.
  • An unauthenticated user is redirected to the login page (existing session middleware behaviour).

Background: RBAC in the Platform

This platform uses an organisation-scoped membership model. Each user belongs to one or more organisations and holds a role within each (owner, admin, or member). The x-org-id cookie identifies the active organisation context for a given session and is set automatically by request middleware — it does not need to be passed manually through page props.

The orgMembers table is the authoritative source for role lookups and should be queried server-side on any route that exposes privileged functionality.


This notice was published as part of the v1.0.25 release. Apply the remediation above as soon as possible and redeploy.