@@ -18,6 +18,32 @@ import {
1818} from '@evtivity/database' ;
1919import Handlebars from 'handlebars' ;
2020import { decryptString , wrapEmailHtml } from '@evtivity/lib' ;
21+
22+ /**
23+ * Compile a user-provided Handlebars template safely.
24+ * Rejects templates containing block helpers, partials, or subexpressions
25+ * that could be used for code injection. Only simple variable interpolation
26+ * ({{var}} and {{{var}}}) is allowed.
27+ */
28+ function safeCompile ( template : string ) : HandlebarsTemplateDelegate {
29+ // Block helpers: {{#each}}, {{#if}}, {{#with}}, {{#unless}}, {{#lookup}}, etc.
30+ if ( / \{ \{ # / . test ( template ) ) {
31+ throw new Error ( 'Block helpers are not allowed in templates' ) ;
32+ }
33+ // Partials: {{> partialName}}
34+ if ( / \{ \{ > / . test ( template ) ) {
35+ throw new Error ( 'Partials are not allowed in templates' ) ;
36+ }
37+ // Subexpressions: {{helper (subexpr)}}
38+ if ( / \{ \{ [ ^ } ] * \( / . test ( template ) ) {
39+ throw new Error ( 'Subexpressions are not allowed in templates' ) ;
40+ }
41+ // Lookup/log helpers: {{lookup}}, {{log}}
42+ if ( / \{ \{ \s * ( l o o k u p | l o g | h e l p e r M i s s i n g | b l o c k H e l p e r M i s s i n g ) \b / . test ( template ) ) {
43+ throw new Error ( 'Dangerous helpers are not allowed in templates' ) ;
44+ }
45+ return Handlebars . compile ( template ) ;
46+ }
2147import { zodSchema } from '../lib/zod-schema.js' ;
2248import { paginationQuery } from '../lib/pagination.js' ;
2349import nodemailer from 'nodemailer' ;
@@ -1047,12 +1073,8 @@ export function notificationRoutes(app: FastifyInstance): void {
10471073 email : 'john.doe@example.com' ,
10481074 } ;
10491075
1050- const renderedSubject = body . subject
1051- ? Handlebars . compile ( body . subject ) ( sampleVariables )
1052- : null ;
1053- let renderedBodyHtml = body . bodyHtml
1054- ? Handlebars . compile ( body . bodyHtml ) ( sampleVariables )
1055- : null ;
1076+ const renderedSubject = body . subject ? safeCompile ( body . subject ) ( sampleVariables ) : null ;
1077+ let renderedBodyHtml = body . bodyHtml ? safeCompile ( body . bodyHtml ) ( sampleVariables ) : null ;
10561078
10571079 if ( body . channel === 'email' && renderedBodyHtml != null ) {
10581080 renderedBodyHtml = wrapEmailHtml (
0 commit comments