Area: Admin deep-dive (commerce/config) (audit p15b) · Surface: /admin/courses/offers/{id}/edit (convert delivery forms) · Dimension: Law 12 / functional correctness (CSRF token name+source mismatch) · Severity: critical
Both delivery-conversion admin actions for course offers are completely broken from the UI. The hidden CSRF input uses the wrong field name and pulls from a session key that is never populated, so validateCsrf() rejects every submission with a 403. The correct pattern (used everywhere else in the same file, e.g. form.php:98) is `name="_csrf_token" value="<?= $e($csrfToken) ?>"`.
Evidence
templates/admin/courses/form.php:422 and :431 emit `<input type="hidden" name="_csrf" value="<?= htmlspecialchars($_SESSION['csrf'] ?? '', ...) ?>">`. But BaseController.php:491 reads the token from `$_POST['_csrf_token']` (or HTTP_X_CSRF_TOKEN), and the real session token lives in `$_SESSION['csrf_token']` (BaseController.php:425-428), not `$_SESSION['csrf']`. So the field has BOTH the wrong name (`_csrf` vs `_csrf_token`) AND an empty value (`$_SESSION['csrf']` is never set). AdminCourseSalesController.php:457 (convertToLearn) and :503 (convertToMoodle) both call $this->validateCsrf() first thing. validateCsrf() (BaseController.php:489-501) does `hash_equals($this->csrfToken(), $_POST['_csrf_token'] ?? '')` -> mismatch -> http_response_code(403); echo 'Invalid or missing security token. Please refresh and try again.'; exit. These are native form POSTs (no fetch/XHR interception). Routes are live: routes.php:2082-2083. Every click of 'Convert to mobieusLearn' or 'Revert to Moodle delivery' returns a bare 403 page.
Suggested fix. Replace both inputs with the standard `<input type="hidden" name="_csrf_token" value="<?= $e($csrfToken) ?>">` (the template already has $csrfToken available at form.php:27 and uses it correctly at line 98). Grep the file for any other `name="_csrf"` occurrences.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus