username = 'attacker'; $verify->email = 'attacker@example.com'; $verify->reason_to_join = "\nsecond line"; $verify->save(); $applicationHtml = (new CuratedRegisterNotifyAdmin($verify))->render(); expect($applicationHtml)->not->toContain(''); expect($applicationHtml)->toContain('<script>'); // Line breaks preserved via nl2br. expect($applicationHtml)->toContain('register_id = $verify->id; $activity->message = ''; $activity->save(); $responseHtml = (new CuratedRegisterNotifyAdminUserResponse($activity))->render(); expect($responseHtml)->not->toContain(''); expect($responseHtml)->toContain('<script>'); }); /* | request-details-from-user is rendered as an HTTP response by the admin | previewDetailsMessageShow endpoint, which reflects a GET `message` param into | $activity->message. It must escape the message to prevent reflected XSS, the | same way its sibling admin templates do. */ it('escapes the message in the request-details-from-user mailable', function () { $verify = new CuratedRegister; $verify->username = 'attacker'; $verify->email = 'attacker@example.com'; $verify->save(); $activity = new CuratedRegisterActivity; $activity->register_id = $verify->id; $activity->message = ''; $activity->save(); $detailsHtml = (new CuratedRegisterRequestDetailsFromUser($verify, $activity))->render(); // The raw tag and its onerror handler must not survive as live HTML. expect($detailsHtml)->not->toContain('