MDL Shield

Guest Redirect

local_guestredirect

Print Report
Plugin Information

Local plugin that provides a dedicated URL (/local/guestredirect/index.php?id=<courseid>) which logs a not-logged-in visitor in as the site guest user and redirects them to a course with guest access enabled. It works without the global autologinguests or guestloginbutton settings, so sites can keep forcelogin behaviour.

Version:2026100501
Release:v5.3-r2
Reviewed for:5.3
Privacy API
Unit Tests
Behat Tests
Reviewed:2026-10-25
8 files·593 lines
Grade Justification

The plugin is small and does one job: start a guest session for an anonymous visitor, then hand off to core. I traced the security-relevant decisions into core and found them sound:

  • Course access is enforced by require_login($course, true) straight after the guest session starts. Core checks course visibility, site policy, maintenance mode and guest enrolment (including guest passwords).
  • Session fixation is not possible. complete_user_login() calls \core\session\manager::login_user(), which regenerates the session ID and destroys the old session.
  • Guest access detection uses enrol_get_instances($courseid, true). That call only returns enabled instances of enrolment plugins that are enabled site-wide.
  • The missing sesskey on the GET login is a documented, deliberate choice. Its only effect is that an anonymous visitor gets a zero-privilege guest session. That is the same thing core's autologinguests path does inside require_login().

The one defect is a low-severity robustness issue. If the site guest account is missing, guest_user() returns false, and passing that to the strictly typed login_user(\stdClass $user) causes a fatal TypeError. Core's own guest auto-login handles this case by redirecting to the login page. There are no security vulnerabilities, and the plugin ships PHPUnit and Behat tests and a correct null privacy provider.

AI Summary

local_guestredirect is a small local plugin of about 150 lines of production PHP across index.php, one helper class, settings, a privacy provider and a language file, plus PHPUnit and Behat tests. When it is switched on, it gives anonymous visitors a link that logs them in as the site guest user and sends them to a course where guest access is enabled.

The security posture is good. The plugin starts a guest session only when the visitor is not already logged in and the course has an enabled guest enrolment instance. It then calls require_login($course, true) straight away, so core enforces course visibility, guest passwords, site policy and maintenance mode. Session fixation is handled by core, which regenerates the session ID on login. The GET request has no sesskey check, but this is documented as intentional and gives no more than core's autologinguests path already does.

The only finding is a low-severity robustness gap. The plugin does not handle guest_user() returning false on a site where the guest account is missing, so such a site gets a fatal error instead of a redirect to the login page.

Admins should be aware that switching the plugin on applies site-wide. Every course that has guest access enabled becomes reachable by anonymous visitors through this URL, even when forcelogin is on and the guest login button is hidden.

Findings

code qualityLow
Unchecked guest_user() result can cause a fatal TypeError

login_as_guest() passes the result of guest_user() straight to complete_user_login(). In core, guest_user() returns false when no user record matches $CFG->siteguest. complete_user_login() then calls \core\session\manager::login_user(\stdClass $user), whose parameter is strictly typed, so a missing guest account ends the request with an uncaught TypeError. Core's own guest auto-login in require_login() handles the same situation by redirecting to the login page.

Risk Assessment

Low risk. This only happens on a site whose guest account has been removed or whose siteguest setting is broken. The result is an error page instead of a clean redirect, and no data is exposed.

Context

index.php calls helper::login_as_guest() for anonymous visitors once the kill switch is on and the course has an enabled guest enrolment instance. Core's equivalent path in require_login() checks the result of get_complete_user_data('id', $CFG->siteguest) and redirects to the login page if it fails.

Identified Code
$user = guest_user();
complete_user_login($user);
Suggested Fix
if (!$user = guest_user()) {
    // Misconfigured site guest, behave like core's autologinguests path.
    redirect(get_login_url());
}
complete_user_login($user);
Additional AI Notes

Site-wide scope of the switch. When local_guestredirect/enable is on, every course with an enabled guest enrolment instance can be reached by anonymous visitors through /local/guestredirect/index.php?id=N. This holds even with forcelogin on and the guest login button hidden. Course IDs are sequential, so they are easy to guess. The setting description says this, but admins who expect only courses whose links they hand out to be reachable should check which courses have guest access enabled before switching the plugin on.

Deliberate login without a CSRF token. The guest login runs on a plain GET request with no sesskey, and the code comment explains why. I checked this against core: it only ever runs for visitors who are not logged in, the account it logs into has no privileges, and core's autologinguests path inside require_login() does the same thing. A forged request can only turn an anonymous visitor into a guest. It cannot log out a real user or replace their session.

Error for unknown course IDs. get_course() uses MUST_EXIST, so an invalid id shows a core "record not found" error to anonymous visitors. Core's course/view.php behaves the same way before its require_login() call, so this reveals nothing beyond what core already reveals.

This review was generated by an AI system and may contain inaccuracies. Findings should be verified by a human reviewer before acting on them.