Area: Cross-cutting infra (audit p14) · Surface: GateMiddleware -> GateAccessResolver role handling · Dimension: security · Severity: minor
OWASP A01. The mobieusGate access resolver has an intentional admin/staff bypass (role >= 4 → never gated), but the value driving it is read from a session key that is never populated. The bypass therefore never triggers via the middleware: a logged-in Administrator or Super Admin hitting a read_gated surface is treated as role 0 and shown the paywall instead of being let through. This is a correctness/access-policy bug — the gate behaves more restrictively than intended for staff, and the resolver's role logic is effectively dead through this call path.
Evidence
platform/src/Middleware/GateMiddleware.php:44 — `$userRole = (int) ($_SESSION['user_role'] ?? 0);` then GateMiddleware.php:55 passes it to `GateAccessResolver::resolve($path, $userId, $userRole)`. GateAccessResolver.php:21-23 grants unconditional access for `$userRole >= 4`. But `grep -rn "_SESSION\['user_role'\] =" src/` returns NO writes anywhere — login/auth code stores `$_SESSION['user_id']` and reads role from the DB (e.g. AuthMiddleware.php:200, AdminMiddleware.php:51), never `$_SESSION['user_role']`. So `$userRole` is always 0 in this path.
Suggested fix. Source the role from the authoritative place rather than a non-existent session key — e.g. look up the user row by `$_SESSION['user_id']` (as AdminMiddleware does) and pass the DB role, or have AuthMiddleware persist a validated role into the session if a cached value is desired. Cross-check the same `user_role` assumption in BaseHelpdeskAdminController.php:42 which also reads `$_SESSION['user_role'] ?? 5`.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus