diff --git a/resources/views/emails/curated-register/request-details-from-user.blade.php b/resources/views/emails/curated-register/request-details-from-user.blade.php
index 59c26c7cd..718fa2e66 100644
--- a/resources/views/emails/curated-register/request-details-from-user.blade.php
+++ b/resources/views/emails/curated-register/request-details-from-user.blade.php
@@ -8,7 +8,7 @@ To help us process your registration application, we require more information.
Our onboarding team have requested the following details:
@component('mail::panel')
-
{!! $activity->message !!}
+{!! nl2br(e($activity->message)) !!}
@endcomponent
Reply with your response
diff --git a/tests/Feature/CuratedRegisterEmailEscapeTest.php b/tests/Feature/CuratedRegisterEmailEscapeTest.php
index ad2f467f2..fd1a2d98e 100644
--- a/tests/Feature/CuratedRegisterEmailEscapeTest.php
+++ b/tests/Feature/CuratedRegisterEmailEscapeTest.php
@@ -2,6 +2,7 @@
use App\Mail\CuratedRegisterNotifyAdmin;
use App\Mail\CuratedRegisterNotifyAdminUserResponse;
+use App\Mail\CuratedRegisterRequestDetailsFromUser;
use App\Models\CuratedRegister;
use App\Models\CuratedRegisterActivity;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
@@ -43,3 +44,27 @@ it('escapes user-provided content in the admin notification emails', function ()
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('